mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-12 18:45:33 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12a130e8c2 | ||
|
|
3cbe16f2cb |
No files matched your search
@@ -3194,15 +3194,22 @@ static uint8_t *command_memory_get_pointer(
|
||||
|
||||
/* What RUN_UNTIL, a PRESS that waits after it and a STEPS list wait on:
|
||||
* the byte at @lo, or with @wide the value @hi * 256 + @lo, from two
|
||||
* bytes that need not be adjacent. */
|
||||
* bytes that need not be adjacent. With @diff, that less the value at
|
||||
* @sub_lo, or @sub_hi:@sub_lo: how far apart two things are, signed. */
|
||||
struct command_cond
|
||||
{
|
||||
unsigned hi;
|
||||
unsigned lo;
|
||||
unsigned sub_hi;
|
||||
unsigned sub_lo;
|
||||
unsigned mask; /* "& <mask>": the bits compared; 0 for all */
|
||||
unsigned want;
|
||||
int want;
|
||||
uint8_t op; /* enum cmd_until_op */
|
||||
bool wide;
|
||||
bool sub_wide;
|
||||
bool diff;
|
||||
/* a stop: fires only on a frame it holds after one it did not */
|
||||
bool edge;
|
||||
};
|
||||
|
||||
/* "<address>[:<address>]" into @c, @end after it: 0, or 1 where the
|
||||
@@ -3225,20 +3232,34 @@ static int command_cond_address(const char *s, struct command_cond *c,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool command_cond_read(const struct command_cond *c,
|
||||
/* The byte at @lo, or with @wide the value @hi * 256 + @lo. */
|
||||
static bool command_cond_value(unsigned hi, unsigned lo, bool wide,
|
||||
unsigned *out, char *why, size_t why_len)
|
||||
{
|
||||
unsigned avail = 0;
|
||||
const rarch_system_info_t *sys = &runloop_state_get_ptr()->system;
|
||||
const uint8_t *lo = command_memory_get_pointer(sys, c->lo, &avail, 0,
|
||||
const uint8_t *l = command_memory_get_pointer(sys, lo, &avail, 0,
|
||||
why, why_len);
|
||||
const uint8_t *hi = NULL;
|
||||
if (!lo || (c->wide && !(hi = command_memory_get_pointer(sys, c->hi,
|
||||
const uint8_t *h = NULL;
|
||||
if (!l || (wide && !(h = command_memory_get_pointer(sys, hi,
|
||||
&avail, 0, why, why_len))))
|
||||
return false;
|
||||
*out = hi ? ((unsigned)*hi << 8) | *lo : *lo;
|
||||
*out = h ? ((unsigned)*h << 8) | *l : *l;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool command_cond_read(const struct command_cond *c,
|
||||
int *out, char *why, size_t why_len)
|
||||
{
|
||||
unsigned a = 0;
|
||||
unsigned b = 0;
|
||||
if ( !command_cond_value(c->hi, c->lo, c->wide, &a, why, why_len)
|
||||
|| (c->diff && !command_cond_value(c->sub_hi, c->sub_lo,
|
||||
c->sub_wide, &b, why, why_len)))
|
||||
return false;
|
||||
if (c->mask)
|
||||
*out &= c->mask;
|
||||
a &= c->mask;
|
||||
*out = (int)a - (int)b;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3291,9 +3312,9 @@ static size_t command_watch_lines(char *s, size_t len)
|
||||
i++)
|
||||
{
|
||||
const struct command_watch_entry *e = &command_watches[i];
|
||||
unsigned now = 0;
|
||||
int now = 0;
|
||||
if (command_cond_read(&e->where, &now, reason, sizeof(reason)))
|
||||
snprintf(value, sizeof(value), "%u", now);
|
||||
snprintf(value, sizeof(value), "%d", now);
|
||||
else
|
||||
strlcpy(value, "none", sizeof(value));
|
||||
if (e->where.wide)
|
||||
@@ -3338,15 +3359,17 @@ static bool command_cond_number(const char *s, char **end,
|
||||
return errno != ERANGE;
|
||||
}
|
||||
|
||||
/* "<address>[:<address>] <condition> <value>", @end after the value;
|
||||
* false with what was wrong in @why. */
|
||||
/* "<address>[:<address>] [- <address>[:<address>]] <condition>
|
||||
* <value>", @end after the value; false with what was wrong in @why. */
|
||||
static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
char **end, char *why, size_t why_len)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned long num;
|
||||
unsigned long max;
|
||||
const char *sp;
|
||||
int bad;
|
||||
bool neg = false;
|
||||
|
||||
memset(c, 0, sizeof(*c));
|
||||
if ((bad = command_cond_address(s, c, end)))
|
||||
@@ -3359,6 +3382,32 @@ static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
}
|
||||
while (**end == ' ')
|
||||
(*end)++;
|
||||
/* "485 - 47e": the second's value taken from the first's, signed,
|
||||
* so how far an enemy is from the player is one condition */
|
||||
if (**end == '-')
|
||||
{
|
||||
struct command_cond sub;
|
||||
memset(&sub, 0, sizeof(sub));
|
||||
if (command_cond_address(*end + 1, &sub, end))
|
||||
{
|
||||
strlcpy(why, "a hexadecimal address, or two as high:low, after "
|
||||
"the \"-\"", why_len);
|
||||
return false;
|
||||
}
|
||||
c->sub_hi = sub.hi;
|
||||
c->sub_lo = sub.lo;
|
||||
c->sub_wide = sub.wide;
|
||||
c->diff = true;
|
||||
while (**end == ' ')
|
||||
(*end)++;
|
||||
}
|
||||
max = (c->wide || c->sub_wide) ? 65535UL : 255UL;
|
||||
if (**end == '&' && c->diff)
|
||||
{
|
||||
strlcpy(why, "a mask compares one address's bits, not a difference",
|
||||
why_len);
|
||||
return false;
|
||||
}
|
||||
/* "& 0x40" or "& 64": only those bits, as a game keeps its flags */
|
||||
if (**end == '&')
|
||||
{
|
||||
@@ -3397,17 +3446,29 @@ static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
}
|
||||
while (*sp == ' ')
|
||||
sp++;
|
||||
/* "== 112" or "== 0x70", as a mask is given */
|
||||
if ( !command_cond_number(sp, end, &num)
|
||||
|| num > (c->wide ? 65535UL : 255UL))
|
||||
/* a difference may be below zero: "<= -16" */
|
||||
if (c->diff && *sp == '-')
|
||||
{
|
||||
strlcpy(why, c->wide
|
||||
? "a value from 0 to 65535 (0xFFFF), which is what two bytes hold"
|
||||
: "a value from 0 to 255 (0xFF), which is what a byte holds",
|
||||
why_len);
|
||||
neg = true;
|
||||
sp++;
|
||||
}
|
||||
/* "== 112" or "== 0x70", as a mask is given */
|
||||
if (!command_cond_number(sp, end, &num) || num > max)
|
||||
{
|
||||
if (c->diff)
|
||||
strlcpy(why, max > 255UL
|
||||
? "a value from -65535 to 65535, which is what two values "
|
||||
"of two bytes differ by"
|
||||
: "a value from -255 to 255, which is what two bytes "
|
||||
"differ by", why_len);
|
||||
else
|
||||
strlcpy(why, c->wide
|
||||
? "a value from 0 to 65535 (0xFFFF), which is what two bytes hold"
|
||||
: "a value from 0 to 255 (0xFF), which is what a byte holds",
|
||||
why_len);
|
||||
return false;
|
||||
}
|
||||
c->want = (unsigned)num;
|
||||
c->want = neg ? -(int)num : (int)num;
|
||||
c->op = (uint8_t)i;
|
||||
return true;
|
||||
}
|
||||
@@ -3415,7 +3476,7 @@ static bool command_cond_parse(const char *s, struct command_cond *c,
|
||||
/* What @c's memory holds now, looked up afresh as WATCH's bytes are,
|
||||
* since a core may move its memory while the content runs; false where
|
||||
* nothing maps it, with the reason as line clients read it in @why. */
|
||||
static bool command_cond_met(const struct command_cond *c, unsigned now)
|
||||
static bool command_cond_met(const struct command_cond *c, int now)
|
||||
{
|
||||
switch (c->op)
|
||||
{
|
||||
@@ -3437,7 +3498,7 @@ static bool command_cond_mapped(command_t *cmd, const char *name,
|
||||
const struct command_cond *c)
|
||||
{
|
||||
char reason[128];
|
||||
unsigned now = 0;
|
||||
int now = 0;
|
||||
if (command_cond_read(c, &now, reason, sizeof(reason)))
|
||||
return true;
|
||||
command_reply_error(cmd, name, "%s", command_memory_why(reason));
|
||||
@@ -3505,12 +3566,15 @@ static struct
|
||||
unsigned left; /* frames left in a RUN or PRESS step */
|
||||
unsigned budget; /* frames left for the whole job */
|
||||
unsigned ran; /* frames run so far */
|
||||
unsigned value; /* what the last condition read held */
|
||||
int value; /* what the last condition read held */
|
||||
unsigned port; /* the player whose buttons are held */
|
||||
uint16_t held; /* the buttons the job let go of, by bit */
|
||||
uint8_t answer; /* enum cmd_job_answer */
|
||||
unsigned nstops;
|
||||
unsigned stopped; /* which stop held, from 1 */
|
||||
/* whether each stop held on the frame before: one that becomes
|
||||
* fires only where it did not */
|
||||
bool stop_was[COMMAND_STOPS_MAX];
|
||||
/* a frame with the buttons let go between two presses of the same
|
||||
* one, so the game sees two taps rather than one long press */
|
||||
bool gap;
|
||||
@@ -3632,7 +3696,7 @@ static void command_run_frames_end(const char *how)
|
||||
case CMD_ANSWER_UNTIL:
|
||||
snprintf(first, sizeof(first), "%s %u\n", how,
|
||||
command_run_frames_st.ran);
|
||||
snprintf(last, sizeof(last), "VALUE %u\n",
|
||||
snprintf(last, sizeof(last), "VALUE %d\n",
|
||||
command_run_frames_st.value);
|
||||
command_run_frames_answer(first, last, false);
|
||||
break;
|
||||
@@ -3777,8 +3841,14 @@ static void command_run_frames_enter(bool safe)
|
||||
}
|
||||
else
|
||||
{
|
||||
int now = 0;
|
||||
command_run_frames_st.stops[s->idx - 1] = s->cond;
|
||||
command_run_frames_st.nstops = s->idx;
|
||||
/* what it holds as it takes over, so one already true when
|
||||
* the list starts waits for it to stop holding first */
|
||||
command_run_frames_st.stop_was[s->idx - 1] =
|
||||
command_cond_read(&s->cond, &now, why, sizeof(why))
|
||||
&& command_cond_met(&s->cond, now);
|
||||
}
|
||||
command_run_frames_st.cur++;
|
||||
continue;
|
||||
@@ -3927,7 +3997,7 @@ void command_run_frames_between(void)
|
||||
void command_run_frames_tick(void)
|
||||
{
|
||||
char why[128];
|
||||
unsigned now = 0;
|
||||
int now = 0;
|
||||
unsigned i;
|
||||
struct command_step *s;
|
||||
|
||||
@@ -3948,10 +4018,17 @@ void command_run_frames_tick(void)
|
||||
}
|
||||
if (command_cond_met(&command_run_frames_st.stops[i], now))
|
||||
{
|
||||
command_run_frames_st.stopped = i + 1;
|
||||
command_run_frames_end("STOPPED");
|
||||
return;
|
||||
bool was = command_run_frames_st.stop_was[i];
|
||||
command_run_frames_st.stop_was[i] = true;
|
||||
if (!command_run_frames_st.stops[i].edge || !was)
|
||||
{
|
||||
command_run_frames_st.stopped = i + 1;
|
||||
command_run_frames_end("STOPPED");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
command_run_frames_st.stop_was[i] = false;
|
||||
}
|
||||
s = &command_run_frames_st.steps[command_run_frames_st.cur];
|
||||
if (command_run_frames_st.gap)
|
||||
@@ -4284,12 +4361,14 @@ bool command_set_input(command_t *cmd, const char *arg)
|
||||
* With nothing, what is watched now. Each label and address watches the
|
||||
* byte there under that label, carried by RUN_FRAMES, PRESS and
|
||||
* RUN_UNTIL; a label and "off" stops watching it. Every pair is taken
|
||||
* or none is, so a refusal leaves the list as it was.
|
||||
* or none is, so a refusal leaves the list as it was. The limit is on
|
||||
* the list they leave, so an off later in the request makes room.
|
||||
*
|
||||
* Answers with the list either way, so a client sees what it has. */
|
||||
bool command_watch(command_t *cmd, const char *arg)
|
||||
{
|
||||
struct command_watch_entry next[COMMAND_WATCH_MAX];
|
||||
/* room past the limit for those an off further on takes back out */
|
||||
struct command_watch_entry next[2 * COMMAND_WATCH_MAX];
|
||||
char reply[64 + COMMAND_WATCH_MAX * COMMAND_WATCH_LINE];
|
||||
char label[sizeof(next[0].label)];
|
||||
char reason[128];
|
||||
@@ -4299,7 +4378,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
unsigned i;
|
||||
const char *sp;
|
||||
|
||||
memcpy(next, command_watches, sizeof(next));
|
||||
memcpy(next, command_watches, sizeof(command_watches));
|
||||
while (arg && *arg)
|
||||
{
|
||||
while (*arg == ' ')
|
||||
@@ -4346,7 +4425,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
else
|
||||
{
|
||||
struct command_cond where;
|
||||
unsigned now = 0;
|
||||
int now = 0;
|
||||
char *end = NULL;
|
||||
memset(&where, 0, sizeof(where));
|
||||
/* high:low, two bytes as one value, as conditions take them */
|
||||
@@ -4358,7 +4437,7 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
return false;
|
||||
}
|
||||
/* only a label that is not watched yet needs room */
|
||||
if (i == count && count >= COMMAND_WATCH_MAX)
|
||||
if (i == count && count >= ARRAY_SIZE(next))
|
||||
{
|
||||
command_reply_error(cmd, "WATCH", "at most %u bytes watched "
|
||||
"at once", (unsigned)COMMAND_WATCH_MAX);
|
||||
@@ -4378,7 +4457,13 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
strlcpy(next[i].label, label, sizeof(next[i].label));
|
||||
}
|
||||
}
|
||||
memcpy(command_watches, next, sizeof(next));
|
||||
if (count > COMMAND_WATCH_MAX)
|
||||
{
|
||||
command_reply_error(cmd, "WATCH", "at most %u bytes watched "
|
||||
"at once", (unsigned)COMMAND_WATCH_MAX);
|
||||
return false;
|
||||
}
|
||||
memcpy(command_watches, next, sizeof(command_watches));
|
||||
command_watch_count = count;
|
||||
|
||||
_len = command_watch_lines(reply, sizeof(reply));
|
||||
@@ -4388,11 +4473,12 @@ bool command_watch(command_t *cmd, const char *arg)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* RUN_UNTIL <address> <condition> <value> [max frames]
|
||||
/* RUN_UNTIL <address> [- <address>] <condition> <value> [max frames]
|
||||
*
|
||||
* Runs the content until the byte at a system address meets what was
|
||||
* asked, then answers with how many frames that took and the value it
|
||||
* found. Where a client would otherwise run a few frames, read the
|
||||
* found. With "- <address>" it is that byte less the other, signed:
|
||||
* how far apart two things are. Where a client would otherwise run a few frames, read the
|
||||
* byte, and go round again - which is where a field run spent most of
|
||||
* its calls - this is one request.
|
||||
*
|
||||
@@ -4604,7 +4690,9 @@ static void command_steps_report(command_t *cmd, long idle,
|
||||
* <player> <controls> <frames>" and "until <condition>", in order, then
|
||||
* answers with how it ended and what WATCH watches. A "stop
|
||||
* <condition>" entry ends the list after any frame where it holds, so a
|
||||
* death does not play out the rest, and "checkpoint" keeps the state
|
||||
* death does not play out the rest; "stop becomes <condition>" only
|
||||
* after one where it starts to, so one already true does not end the
|
||||
* list at its first frame. "checkpoint" keeps the state
|
||||
* STATE rollback returns to. The whole list runs no longer than a wait
|
||||
* does; past that it answers WAITING and the step it was on, and the
|
||||
* next request carries on from there.
|
||||
@@ -4662,9 +4750,10 @@ bool command_steps(command_t *cmd, const char *arg)
|
||||
for (entry = list; entry; entry = next)
|
||||
{
|
||||
struct command_step *s;
|
||||
struct command_cond *c = NULL;
|
||||
char *end = NULL;
|
||||
struct command_cond *c = NULL;
|
||||
char *end = NULL;
|
||||
size_t n;
|
||||
bool edge = false;
|
||||
if ((next = strchr(entry, ';')))
|
||||
*next++ = '\0';
|
||||
while (*entry == ' ')
|
||||
@@ -4686,6 +4775,13 @@ bool command_steps(command_t *cmd, const char *arg)
|
||||
}
|
||||
c = &stops[nstops++];
|
||||
entry += 5;
|
||||
/* "stop becomes <condition>": only once it starts to hold, so
|
||||
* one true as the list starts does not end it at once */
|
||||
if (!strncmp(entry, "becomes ", 8))
|
||||
{
|
||||
entry += 8;
|
||||
edge = true;
|
||||
}
|
||||
}
|
||||
else if (count >= COMMAND_STEPS_MAX)
|
||||
{
|
||||
@@ -4785,6 +4881,7 @@ bool command_steps(command_t *cmd, const char *arg)
|
||||
command_reply_error(cmd, "STEPS", "%s", why);
|
||||
return false;
|
||||
}
|
||||
c->edge = edge;
|
||||
while (*end == ' ')
|
||||
end++;
|
||||
if (*end)
|
||||
@@ -6091,11 +6188,23 @@ bool command_get_status(command_t *cmd, const char* arg)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* READ_CORE_MEMORY <address> <bytes> [<address> <bytes>]...
|
||||
*
|
||||
* Each range a line of its own, in the order asked, so the arrays a
|
||||
* step needs - positions, health - are one request rather than one
|
||||
* apiece. Where nothing maps one, that line alone is the answer. */
|
||||
#define COMMAND_READ_RANGES_MAX 16
|
||||
|
||||
bool command_read_memory(command_t *cmd, const char *arg)
|
||||
{
|
||||
char* reply = NULL;
|
||||
char* reply_at = NULL;
|
||||
const uint8_t* data = NULL;
|
||||
unsigned int addrs[COMMAND_READ_RANGES_MAX];
|
||||
unsigned int counts[COMMAND_READ_RANGES_MAX];
|
||||
unsigned int nranges = 0;
|
||||
unsigned int total = 0;
|
||||
unsigned int r;
|
||||
unsigned int nbytes = 0;
|
||||
unsigned int alloc_size = 0;
|
||||
unsigned int address = -1;
|
||||
@@ -6104,27 +6213,48 @@ bool command_read_memory(command_t *cmd, const char *arg)
|
||||
runloop_state_t *runloop_st = runloop_state_get_ptr();
|
||||
const rarch_system_info_t* sys_info= &runloop_st->system;
|
||||
|
||||
while (arg && *arg)
|
||||
{
|
||||
char *end = NULL;
|
||||
unsigned long num;
|
||||
if (nranges >= COMMAND_READ_RANGES_MAX)
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"at most %u ranges", (unsigned)COMMAND_READ_RANGES_MAX);
|
||||
return false;
|
||||
}
|
||||
if (!command_parse_address(arg, &address, &end) || *end != ' ')
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"a hexadecimal address, a space and a byte count");
|
||||
return false;
|
||||
}
|
||||
num = strtoul(end + 1, NULL, 10);
|
||||
num = strtoul(end + 1, &end, 10);
|
||||
nbytes = num <= COMMAND_READ_NBYTES_MAX ? (unsigned)num : 0;
|
||||
if (nbytes == 0)
|
||||
if (nbytes == 0 || total + nbytes > COMMAND_READ_NBYTES_MAX)
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"a byte count from 1 to %u", COMMAND_READ_NBYTES_MAX);
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY", nranges
|
||||
? "byte counts from 1, %u in all"
|
||||
: "a byte count from 1 to %u", COMMAND_READ_NBYTES_MAX);
|
||||
return false;
|
||||
}
|
||||
addrs[nranges] = address;
|
||||
counts[nranges] = nbytes;
|
||||
nranges++;
|
||||
total += nbytes;
|
||||
/* another address and count, or the end of the line */
|
||||
for (arg = end; *arg && (unsigned char)*arg <= ' '; arg++)
|
||||
;
|
||||
}
|
||||
if (!nranges)
|
||||
{
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY",
|
||||
"a hexadecimal address, a space and a byte count");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure large enough to return all requested bytes or an error message */
|
||||
alloc_size = 96 + nbytes * 3;
|
||||
alloc_size = 96 * nranges + total * 3;
|
||||
reply = (char*)malloc(alloc_size);
|
||||
|
||||
if (!reply)
|
||||
@@ -6132,43 +6262,53 @@ bool command_read_memory(command_t *cmd, const char *arg)
|
||||
command_reply_error(cmd, "READ_CORE_MEMORY", "out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
reply_at = reply + snprintf(reply, alloc_size - 1, "READ_CORE_MEMORY %x", address);
|
||||
|
||||
if ((data = command_memory_get_pointer(
|
||||
sys_info, address, &max_bytes,
|
||||
0, reply_at, alloc_size - strlen(reply))))
|
||||
for (r = 0; r < nranges; r++)
|
||||
{
|
||||
char why[128];
|
||||
char *end = reply_at;
|
||||
unsigned int read = 0;
|
||||
/* on into the region that follows, as a core that splits its RAM
|
||||
* maps it; where nothing maps the rest, how much was read */
|
||||
while (data)
|
||||
char *line = reply + _len;
|
||||
address = addrs[r];
|
||||
nbytes = counts[r];
|
||||
reply_at = line + snprintf(line, alloc_size - _len - 1,
|
||||
"READ_CORE_MEMORY %x", address);
|
||||
|
||||
if ((data = command_memory_get_pointer(
|
||||
sys_info, address, &max_bytes,
|
||||
0, reply_at, alloc_size - (size_t)(reply_at - reply))))
|
||||
{
|
||||
unsigned int n = nbytes - read < max_bytes
|
||||
? nbytes - read : max_bytes;
|
||||
if (!n)
|
||||
break;
|
||||
end = command_hex_bytes(end, data, n);
|
||||
read += n;
|
||||
if (read >= nbytes)
|
||||
break;
|
||||
data = command_memory_get_pointer(sys_info, address + read,
|
||||
&max_bytes, 0, why, sizeof(why));
|
||||
char why[128];
|
||||
char *end = reply_at;
|
||||
unsigned int read = 0;
|
||||
/* on into the region that follows, as a core that splits its RAM
|
||||
* maps it; where nothing maps the rest, how much was read */
|
||||
while (data)
|
||||
{
|
||||
unsigned int n = nbytes - read < max_bytes
|
||||
? nbytes - read : max_bytes;
|
||||
if (!n)
|
||||
break;
|
||||
end = command_hex_bytes(end, data, n);
|
||||
read += n;
|
||||
if (read >= nbytes)
|
||||
break;
|
||||
data = command_memory_get_pointer(sys_info, address + read,
|
||||
&max_bytes, 0, why, sizeof(why));
|
||||
}
|
||||
*end++ = '\n';
|
||||
if (read < nbytes)
|
||||
end += snprintf(end, alloc_size - (size_t)(end - reply),
|
||||
"TRUNCATED %u\n", read);
|
||||
_len = (size_t)(end - reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* command_memory_get_pointer() has put the reason in the reply,
|
||||
* in the form line clients have always read: this range's line
|
||||
* alone */
|
||||
_len = strlen(line);
|
||||
memmove(reply, line, _len + 1);
|
||||
cmd->error = true;
|
||||
break;
|
||||
}
|
||||
*end++ = '\n';
|
||||
if (read < nbytes)
|
||||
end += snprintf(end, alloc_size - (size_t)(end - reply),
|
||||
"TRUNCATED %u\n", read);
|
||||
_len = (size_t)(end - reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* command_memory_get_pointer() has put the reason in the reply,
|
||||
* in the form line clients have always read */
|
||||
_len = strlen(reply);
|
||||
cmd->error = true;
|
||||
}
|
||||
|
||||
cmd->replier(cmd, reply, _len);
|
||||
|
||||
@@ -603,6 +603,11 @@ struct cmd_rows
|
||||
/* The reply may carry a "PREVIOUS ..." line: how a queued STEPS no
|
||||
* reply has told of ended. */
|
||||
bool previous;
|
||||
/* Set, for a record: the field a reply of several such lines - one
|
||||
* for each range READ_CORE_MEMORY was asked - gives them all in, in
|
||||
* order, each with the trailers after it. The record's own fields
|
||||
* are the first. */
|
||||
const char *more;
|
||||
};
|
||||
|
||||
/* A command with its own handler; @arg_desc names the argument, or
|
||||
@@ -876,18 +881,19 @@ static const struct cmd_param cmd_params_watch[] = {
|
||||
{ "watches", "Label and hexadecimal address pairs, as GET_MEMORY_MAP "
|
||||
"gives them: \"x 86 mode 770\"; \"x 6d:86\" reads two bytes as one "
|
||||
"value. A label watched again is replaced; off as its address stops "
|
||||
"it. 16 at most.",
|
||||
"it. 16 at most, counted once the offs are taken.",
|
||||
CMD_PARAM_STRING, false, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_param cmd_params_until[] = {
|
||||
{ "address", "Hexadecimal, as GET_MEMORY_MAP gives it; high:low "
|
||||
"(6d:86) reads two bytes as one value; & 0x40 after it compares "
|
||||
"only those bits.", CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
"only those bits; 485 - 47e compares the first less the second, "
|
||||
"signed.", CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
{ "condition", "How it compares with the value.",
|
||||
CMD_PARAM_STRING, true, NULL, "==|!=|<|>|<=|>=", NULL },
|
||||
{ "value", "0 to 255, or 65535 for high:low.",
|
||||
CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ "value", "0 to 255, or 65535 for high:low; a difference from "
|
||||
"-255, or -65535.", CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ "frames", "Frames to wait at most; a request's nine seconds when "
|
||||
"left out.", CMD_PARAM_INT, false, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
@@ -910,7 +916,8 @@ static const struct cmd_param cmd_params_steps[] = {
|
||||
"<frames>, until <condition> as RUN_UNTIL's, checkpoint, and up to "
|
||||
"four stop <condition> that end it: \"until 86 >= 200; "
|
||||
"press 1 a 20; until 1d == 0; stop e == 11\". 32 at most. A press "
|
||||
"after a press lets go for a frame first.",
|
||||
"after a press lets go for a frame first. stop becomes <condition> "
|
||||
"ends it only once it starts to hold, not where it already does.",
|
||||
CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
{ "queue", "on: answer now, run after those queued, pause when all "
|
||||
"have (idle: ms since). After a queued list ends the next reply says "
|
||||
@@ -972,6 +979,16 @@ static const struct cmd_param cmd_params_read[] = {
|
||||
CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_param cmd_params_read_memory[] = {
|
||||
{ "address", "The address to read from, in hexadecimal.",
|
||||
CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
{ "bytes", "How many.",
|
||||
CMD_PARAM_INT, true, NULL, NULL, NULL },
|
||||
{ "more", "Further ranges in the same request, as address and byte "
|
||||
"count pairs: \"4a2 18 568 18\". Each answers a line, given as "
|
||||
"ranges.", CMD_PARAM_STRING, false, NULL, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_param cmd_params_write[] = {
|
||||
{ "address", "The address to write to, in hexadecimal.",
|
||||
CMD_PARAM_STRING, true, NULL, NULL, NULL },
|
||||
@@ -1212,7 +1229,7 @@ static const struct cmd_trailer cmd_trailers_read[] = {
|
||||
{ "TRUNCATED", "truncated", "read", "Bytes read, short of those asked." },
|
||||
{ NULL }
|
||||
};
|
||||
static const struct cmd_rows cmd_rows_read = { cmd_cols_read, cmd_trailers_read, 0, 0, " " };
|
||||
static const struct cmd_rows cmd_rows_read = { cmd_cols_read, cmd_trailers_read, 0, 0, " ", false, "ranges" };
|
||||
/* "MENU Main Menu", the entries, then which is selected of how many */
|
||||
static const struct cmd_column cmd_cols_menu[] = {
|
||||
{ "index", NULL },
|
||||
@@ -1258,7 +1275,7 @@ static const struct cmd_action_map action_map[] = {
|
||||
{ "RUN_FRAMES", command_run_frames, "<count>", "Pause and advance the content some frames; it finishes paused. One request runs nine seconds of frames at most (540 at 60fps), as PRESS, RUN_UNTIL and STEPS do.", 0, cmd_params_run_frames, &cmd_rows_frames },
|
||||
{ "SET_INPUT", command_set_input, "<player> <control>[+<control>...] <value>", "Hold RetroPad buttons or stick axes for the core, over the player's own input, until set again or released. The core is given exactly what is held: opposing directions are not cleaned and the D-Pad is not turned with the picture. Under netplay the player is this machine's, and what is held is sent like a press. Answers with the player, the buttons held and the axes held, none for either where nothing is.", CMD_INFO_NO_TOOL, cmd_params_input, &cmd_rows_input },
|
||||
{ "PRESS", command_press, "<player> <control>[+<control>...] <frames> [until <condition>]", "Press buttons for some frames and let go; with until, run on until a condition holds. What INPUT holds stays held. Answers with what WATCH watches; finishes paused.", 0, cmd_params_press, &cmd_rows_press },
|
||||
{ "RUN_UNTIL", command_run_until, "<address>[:<address>] <condition> <value> [max frames]", "Run until memory meets a condition. Answers met or waiting (ask again) with the frames run, what WATCH watches and the value.", 0, cmd_params_until, &cmd_rows_until },
|
||||
{ "RUN_UNTIL", command_run_until, "<address>[:<address>] [- <address>[:<address>]] <condition> <value> [max frames]", "Run until memory meets a condition. Answers met or waiting (ask again) with the frames run, what WATCH watches and the value.", 0, cmd_params_until, &cmd_rows_until },
|
||||
{ "STEPS", command_steps, "<step>[; <step>...][|on]", "Play a stretch in one request, or queued to play on unpaused. Answers with how it ended and what WATCH watches.", 0, cmd_params_steps, &cmd_rows_steps },
|
||||
{ "WATCH", command_watch, "[<label> <address|off>]...", "Watch bytes under labels: every step answers with them, so needs no read after. With none, answers each value, in decimal. Cleared when content closes.", 0, cmd_params_watch, &cmd_rows_watch },
|
||||
{ "RELEASE_INPUT", command_release_input, "[player]", "Let go of everything SET_INPUT holds, for one player, or every player when given none. Answers as GET_INPUT does, for that player or the first.", CMD_INFO_NO_TOOL, cmd_params_input_release, &cmd_rows_input },
|
||||
@@ -1275,7 +1292,7 @@ static const struct cmd_action_map action_map[] = {
|
||||
{ "READ_CORE_RAM", command_read_ram, "<address> <number of bytes>", "Read bytes from core memory at an achievement address (needs a game with achievements).", CMD_INFO_READ_ONLY | CMD_INFO_NO_TOOL, cmd_params_read },
|
||||
{ "WRITE_CORE_RAM", command_write_ram, "<address> <byte1> <byte2> ...", "Write bytes to core memory at an achievement address (needs a game with achievements).", CMD_INFO_DESTRUCTIVE | CMD_INFO_NO_TOOL, cmd_params_write },
|
||||
#endif
|
||||
{ "READ_CORE_MEMORY", command_read_memory, "<address> <number of bytes>", "Read bytes from core memory at a system address. Answers with the address and the bytes, in hexadecimal.", CMD_INFO_READ_ONLY, cmd_params_read, &cmd_rows_read },
|
||||
{ "READ_CORE_MEMORY", command_read_memory, "<address> <number of bytes> [<address> <number of bytes>]...", "Read bytes from core memory at a system address. Answers with the address and the bytes, in hexadecimal; a line for each range asked.", CMD_INFO_READ_ONLY, cmd_params_read_memory, &cmd_rows_read },
|
||||
{ "GET_MEMORY_MAP", command_get_memory_map, "No argument", "The core's memory map: start, length, select and disconnect in hexadecimal, as READ_CORE_MEMORY takes them. A core with none lists the RAM it hands over, in space core: system RAM from 0, save RAM after.", CMD_INFO_READ_ONLY, NULL, &cmd_rows_memory },
|
||||
{ "WRITE_CORE_MEMORY",command_write_memory, "<address> <byte1> <byte2> ...", "Write bytes to core memory at a system address. Answers with the address and how many bytes were written.", CMD_INFO_DESTRUCTIVE, cmd_params_write },
|
||||
|
||||
|
||||
+25
-2
@@ -143,6 +143,15 @@ goes on into the next; where nothing maps the rest it stops there and
|
||||
says so, `TRUNCATED 2` after the bytes, given as `truncated: true,
|
||||
read: 2`.
|
||||
|
||||
`READ_CORE_MEMORY 47e 18 4a2 18 568 18` reads several ranges in one
|
||||
request (`more: "4a2 18 568 18"` over MCP), up to sixteen and 16384
|
||||
bytes in all: a line for each, in the order asked, each with its own
|
||||
`TRUNCATED` after it where it was cut short. The record's fields are
|
||||
the first range's, and `ranges` gives every one, the first too, as
|
||||
`{"address", "bytes"}` with `truncated` and `read` where cut short.
|
||||
Where nothing maps one of them, its line alone is the answer, as a
|
||||
single range's is.
|
||||
|
||||
## Results that are rows
|
||||
|
||||
The replies that are lines of tab-separated fields - `OPTION`,
|
||||
@@ -446,7 +455,9 @@ its place in the list when its address changes; with nothing watched
|
||||
it answers `WATCH none`. Values are decimal, 0 to 255 - or to 65535
|
||||
for a `high:low` watch - as `RUN_UNTIL` takes them; addresses are hexadecimal. A label is up to 15 characters,
|
||||
with no tab or newline in it. `x 6d:86` watches two bytes as one value,
|
||||
high:low, as conditions read them. Sixteen at once. Each is checked when it is
|
||||
high:low, as conditions read them. Sixteen at once, counted on the
|
||||
list the request leaves: `WATCH lives 4d e13h off` with sixteen watched
|
||||
adds one and drops one. Each is checked when it is
|
||||
set - an address nothing maps is refused there -
|
||||
and read afresh on every reply, so a core that moves its memory is
|
||||
followed; one that stops mapping a watched address answers `none` for
|
||||
@@ -473,6 +484,14 @@ x 7e0086 52
|
||||
VALUE 5
|
||||
```
|
||||
|
||||
`<address> - <address>` compares the first's value less the second's,
|
||||
signed, each one byte or `high:low`: `485 - 47e <= 32` waits until an
|
||||
enemy at x `485` is no more than 32 to the right of the player at
|
||||
`47e`, walking in until it is in range. The value may be below zero, from -255 to 255, or -65535 to
|
||||
65535 where either side is `high:low`, and `VALUE` answers the
|
||||
difference, `VALUE -12`. Both addresses must be mapped, and a mask is
|
||||
refused on one.
|
||||
|
||||
Where a client would otherwise run a few frames, read the byte and go
|
||||
round again, this is one request. The address is the kind
|
||||
`GET_MEMORY_MAP` gives and `READ_CORE_MEMORY` takes, and one nothing
|
||||
@@ -493,7 +512,11 @@ told it failed.
|
||||
step is `run <frames>`, `press <player> <controls> <frames>`, `until
|
||||
<condition>` or `checkpoint`, in order; up to four `stop <condition>`
|
||||
entries anywhere in the list end it after any frame where one holds - a
|
||||
death, the level's end - so neither plays out the rest. `checkpoint`
|
||||
death, the level's end - so neither plays out the rest. `stop becomes
|
||||
<condition>` ends it only after a frame where the condition starts to
|
||||
hold: one already true when the list starts - `stop becomes 412 == 4`
|
||||
while the player is still reeling - waits for it to stop holding and
|
||||
hold again, rather than ending the list at its first frame. `checkpoint`
|
||||
keeps the state `STATE rollback` returns to, where the list has got to,
|
||||
so a stretch can mark its own retry point:
|
||||
|
||||
|
||||
+60
-14
@@ -523,6 +523,17 @@ static void mcp_schema_rows(rjsonwriter_t *w, const struct cmd_rows *rows,
|
||||
MCP_RAW(w, "}");
|
||||
}
|
||||
}
|
||||
/* every line of a reply of several records, the first too */
|
||||
if (rows->more)
|
||||
{
|
||||
MCP_RAW(w, ",");
|
||||
rjsonwriter_add_string(w, rows->more);
|
||||
MCP_RAW(w, ":{\"type\":\"array\",\"items\":{\"type\":\"object\"}");
|
||||
if (describe)
|
||||
MCP_RAW(w, ",\"description\":\"Where several were asked: each, "
|
||||
"as the fields above give the first.\"");
|
||||
MCP_RAW(w, "}");
|
||||
}
|
||||
MCP_RAW(w, "}");
|
||||
/* a record's own fields, or only the rows, are always there */
|
||||
if (rows->seps)
|
||||
@@ -1037,13 +1048,14 @@ static int mcp_trailer(const struct cmd_trailer *tr, size_t n,
|
||||
}
|
||||
if (line[wl] != ' ')
|
||||
continue;
|
||||
/* the number ends the line: "PRESS 1 right+a 4" ran 4 */
|
||||
/* the number ends the line: "PRESS 1 right+a 4" ran 4; a
|
||||
* difference waited on may be below zero, "VALUE -3" */
|
||||
for (k = len; k > wl + 1 && line[k - 1] != ' '; k--)
|
||||
;
|
||||
for (j = k; j < len; j++)
|
||||
for (j = k < len && line[k] == '-' ? k + 1 : k; j < len; j++)
|
||||
if (line[j] < '0' || line[j] > '9')
|
||||
break;
|
||||
if (j < len || k == len)
|
||||
if (j < len || k == len || line[len - 1] == '-')
|
||||
continue;
|
||||
*num = line + k;
|
||||
*num_len = len - k;
|
||||
@@ -1115,17 +1127,20 @@ static void mcp_structured_previous(rjsonwriter_t *w, const char *p,
|
||||
MCP_RAW(w, "}");
|
||||
}
|
||||
|
||||
/* A record's fields from the line the command opens with its own
|
||||
* name: split by rows->seps in turn, the last field taking the rest,
|
||||
* and the trailers on the lines after it. */
|
||||
static void mcp_structured_record(rjsonwriter_t *w,
|
||||
/* A record's fields from the line at @text, which the command opens
|
||||
* with its own name: split by rows->seps in turn, the last field taking
|
||||
* the rest, and the trailers on the lines after it. Where rows->more
|
||||
* is set, another line opening with the name ends those and is
|
||||
* returned; otherwise @text_end. */
|
||||
static const char *mcp_record_fields(rjsonwriter_t *w,
|
||||
const struct cmd_rows *rows, const char *name, const char *text,
|
||||
size_t len)
|
||||
const char *text_end)
|
||||
{
|
||||
const struct cmd_column *col;
|
||||
const char *end = text + len;
|
||||
const char *end = text_end;
|
||||
const char *p = text;
|
||||
const char *eol = (const char*)memchr(text, '\n', len);
|
||||
const char *eol = (const char*)memchr(text, '\n',
|
||||
(size_t)(text_end - text));
|
||||
size_t name_len = strlen(name);
|
||||
size_t nseps = strlen(rows->seps);
|
||||
size_t i = 0;
|
||||
@@ -1137,7 +1152,6 @@ static void mcp_structured_record(rjsonwriter_t *w,
|
||||
if ((size_t)(end - p) > name_len && !strncmp(p, name, name_len)
|
||||
&& p[name_len] == ' ')
|
||||
p += name_len + 1;
|
||||
MCP_RAW(w, ",\"structuredContent\":{");
|
||||
for (col = rows->columns; col && col->name && p < end; col++, i++)
|
||||
{
|
||||
const char *stop = end;
|
||||
@@ -1157,23 +1171,55 @@ static void mcp_structured_record(rjsonwriter_t *w,
|
||||
p = stop < end ? stop + 1 : end;
|
||||
}
|
||||
wrote = !first;
|
||||
for (p = end; p < text + len; )
|
||||
for (p = end; p < text_end; )
|
||||
{
|
||||
const char *line = p + 1;
|
||||
const char *num;
|
||||
size_t num_len;
|
||||
int t;
|
||||
size_t ntr = 0;
|
||||
eol = (const char*)memchr(line, '\n', (size_t)(text + len - line));
|
||||
p = eol ? eol : text + len;
|
||||
eol = (const char*)memchr(line, '\n', (size_t)(text_end - line));
|
||||
p = eol ? eol : text_end;
|
||||
num = NULL;
|
||||
num_len = 0;
|
||||
/* the next range's own line */
|
||||
if ( rows->more && (size_t)(p - line) > name_len
|
||||
&& !strncmp(line, name, name_len) && line[name_len] == ' ')
|
||||
return line;
|
||||
while (rows->trailers && rows->trailers[ntr].word)
|
||||
ntr++;
|
||||
if ((t = mcp_trailer(rows->trailers, ntr, line, p, false, true,
|
||||
&num, &num_len)) >= 0)
|
||||
mcp_trailer_fields(w, &rows->trailers[t], num, num_len, &wrote);
|
||||
}
|
||||
return text_end;
|
||||
}
|
||||
|
||||
/* A record, from the line the command opens with its own name; where
|
||||
* there are several such lines, the first, and every one in turn as
|
||||
* rows->more. */
|
||||
static void mcp_structured_record(rjsonwriter_t *w,
|
||||
const struct cmd_rows *rows, const char *name, const char *text,
|
||||
size_t len)
|
||||
{
|
||||
const char *p;
|
||||
MCP_RAW(w, ",\"structuredContent\":{");
|
||||
p = mcp_record_fields(w, rows, name, text, text + len);
|
||||
if (p < text + len)
|
||||
{
|
||||
MCP_RAW(w, ",");
|
||||
rjsonwriter_add_string(w, rows->more);
|
||||
MCP_RAW(w, ":[");
|
||||
for (p = text; p < text + len; )
|
||||
{
|
||||
if (p != text)
|
||||
MCP_RAW(w, ",");
|
||||
MCP_RAW(w, "{");
|
||||
p = mcp_record_fields(w, rows, name, p, text + len);
|
||||
MCP_RAW(w, "}");
|
||||
}
|
||||
MCP_RAW(w, "]");
|
||||
}
|
||||
MCP_RAW(w, "}");
|
||||
}
|
||||
|
||||
|
||||
@@ -376,6 +376,29 @@ int main(int argc, char **argv)
|
||||
reply[0]=0;
|
||||
CHECK(command_read_memory(&cmd,"100e 8")
|
||||
&& !strcmp(reply,"READ_CORE_MEMORY 100e 0E 0F 10 11 12 13 14 15\n"));
|
||||
/* several ranges, a line each, as one range answers */
|
||||
reply[0]=0;
|
||||
CHECK(command_read_memory(&cmd,"1001 2 1010 3")
|
||||
&& !strcmp(reply,"READ_CORE_MEMORY 1001 0F 10\n"
|
||||
"READ_CORE_MEMORY 1010 10 11 12\n"));
|
||||
reply[0]=0;
|
||||
CHECK(command_read_memory(&cmd,"101e 4 1000 1 ")
|
||||
&& !strcmp(reply,"READ_CORE_MEMORY 101e 1E 1F\nTRUNCATED 2\n"
|
||||
"READ_CORE_MEMORY 1000 00\n"));
|
||||
/* one nothing maps is the answer, alone */
|
||||
reply[0]=0;
|
||||
CHECK(!command_read_memory(&cmd,"1000 1 20000 1")
|
||||
&& !strncmp(reply,"READ_CORE_MEMORY 20000 -1",25));
|
||||
cmd.error=false; reply[0]=0;
|
||||
CHECK(!command_read_memory(&cmd,"1000 1 zz")
|
||||
&& strstr(reply,"READ_CORE_MEMORY ERROR"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_read_memory(&cmd,"1000 1 1001")
|
||||
&& strstr(reply,"READ_CORE_MEMORY ERROR"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_read_memory(&cmd,"1000 16384 1000 1")
|
||||
&& strstr(reply,"16384 in all"));
|
||||
cmd.error=false;
|
||||
/* and one outside every region says so, as it always has */
|
||||
reply[0]=0;
|
||||
CHECK(!command_read_memory(&cmd,"20000 1")
|
||||
@@ -667,6 +690,15 @@ int main(int argc, char **argv)
|
||||
&& strstr(reply,"at most 16 bytes watched"));
|
||||
/* room made earlier in the same request counts */
|
||||
CHECK(command_watch(&cmd,"w15 off w16 1009 w16 off w15 100f"));
|
||||
/* an off after it makes room as well: the limit is on the list
|
||||
* the request leaves */
|
||||
reply[0]=0;
|
||||
CHECK(command_watch(&cmd,"w16 1009 w0 off")
|
||||
&& !strncmp(reply,"w1\t",3) && strstr(reply,"w16\t1009\t"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_watch(&cmd,"w17 100a w18 100b w1 off")
|
||||
&& strstr(reply,"at most 16 bytes watched"));
|
||||
CHECK(command_watch(&cmd,"w16 off w0 1000"));
|
||||
/* but replacing one of the sixteen still works */
|
||||
CHECK(command_watch(&cmd,"w0 1005"));
|
||||
for (w = 0; w < 16; w++)
|
||||
@@ -994,6 +1026,78 @@ int main(int argc, char **argv)
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 & 0x+4 == 4") && strstr(reply,"a mask"));
|
||||
cmd.error=false;
|
||||
/* how far apart two bytes are, signed: ram[5] holds 5, ram[6] 6 */
|
||||
reply[0]=0;
|
||||
CHECK(command_run_until(&cmd,"1006 - 1005 == 1") && !strcmp(reply,"MET 0\nVALUE 1\n"));
|
||||
reply[0]=0;
|
||||
CHECK(command_run_until(&cmd,"1005 - 1006 == -1") && !strcmp(reply,"MET 0\nVALUE -1\n"));
|
||||
reply[0]=0;
|
||||
CHECK(command_run_until(&cmd,"1005-1006 < 0") && !strcmp(reply,"MET 0\nVALUE -1\n"));
|
||||
reply[0]=0;
|
||||
CHECK(command_run_until(&cmd,"1006:1007 - 1006:1007 == 0") && !strcmp(reply,"MET 0\nVALUE 0\n"));
|
||||
{
|
||||
char want[64];
|
||||
int v;
|
||||
now=ram7(&cmd); reply[0]=0;
|
||||
v=(int)((now + 2) & 0xff) - 6;
|
||||
snprintf(req,sizeof(req),"1007 - 1006 == %d",v);
|
||||
CHECK(command_run_until(&cmd,req) && !reply[0]);
|
||||
runloop_iterate(); runloop_iterate();
|
||||
snprintf(want,sizeof(want),"MET 2\nVALUE %d\n",v);
|
||||
CHECK(!strcmp(reply,want));
|
||||
}
|
||||
reply[0]=0;
|
||||
CHECK(command_steps(&cmd,"until 1006 - 1005 == 1") && !strcmp(reply,"DONE 0\nSTEP 1\n"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 - == 1") && strstr(reply,"after the \"-\""));
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 - 1005 == 256") && strstr(reply,"-255 to 255"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 - 1005 == -256") && strstr(reply,"-255 to 255"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006:1007 - 1005 == 65536") && strstr(reply,"-65535 to 65535"));
|
||||
/* below zero only for a difference */
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 == -1") && strstr(reply,"0 to 255"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 - 1005 & 1 == 0") && strstr(reply,"a mask"));
|
||||
/* the second address is found before any frame runs, as the first */
|
||||
reply[0]=0;
|
||||
CHECK(!command_run_until(&cmd,"1006 - 99000 == 0") && strstr(reply,"RUN_UNTIL ERROR"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_steps(&cmd,"run 1; stop 1006 - 99000 == 0") && strstr(reply,"STEPS ERROR"));
|
||||
cmd.error=false;
|
||||
/* a stop already holding ends the list at its first frame; one
|
||||
* that becomes true only once it starts to */
|
||||
reply[0]=0;
|
||||
CHECK(command_steps(&cmd,"run 3; stop 1006 == 6"));
|
||||
runloop_iterate();
|
||||
CHECK(!strcmp(reply,"STOPPED 1\nSTOP 1\nSTEP 1\n"));
|
||||
reply[0]=0;
|
||||
CHECK(command_steps(&cmd,"run 3; stop becomes 1006 == 6"));
|
||||
for (i=0;i<3;i++) runloop_iterate();
|
||||
CHECK(!strcmp(reply,"DONE 3\nSTEP 1\n"));
|
||||
now=ram7(&cmd); reply[0]=0;
|
||||
snprintf(req,sizeof(req),"run 10; stop becomes 1007 == %u",(now + 3) & 0xff);
|
||||
CHECK(command_steps(&cmd,req));
|
||||
for (i=0;i<3;i++) runloop_iterate();
|
||||
CHECK(!strcmp(reply,"STOPPED 3\nSTOP 1\nSTEP 1\n"));
|
||||
/* true, false for a frame, then true again: it fires there */
|
||||
now=ram7(&cmd); reply[0]=0;
|
||||
snprintf(req,sizeof(req),"run 10; stop becomes 1007 != %u",(now + 2) & 0xff);
|
||||
CHECK(command_steps(&cmd,req));
|
||||
for (i=0;i<3;i++) runloop_iterate();
|
||||
CHECK(!strcmp(reply,"STOPPED 3\nSTOP 1\nSTEP 1\n"));
|
||||
/* so a checkpoint after a wait is kept, the stop already holding */
|
||||
now=ram7(&cmd); reply[0]=0;
|
||||
snprintf(req,sizeof(req),"until 1007 == %u; checkpoint; run 1; "
|
||||
"stop becomes 1006 == 6",(now + 2) & 0xff);
|
||||
CHECK(command_steps(&cmd,req));
|
||||
for (i=0;i<3;i++) runloop_iterate();
|
||||
CHECK(!strcmp(reply,"DONE 3\nSTEP 3\n"));
|
||||
reply[0]=0;
|
||||
CHECK(!command_steps(&cmd,"run 1; stop becomes") && strstr(reply,"STEPS ERROR"));
|
||||
cmd.error=false;
|
||||
/* any of several stops ends the list */
|
||||
now=ram7(&cmd); reply[0]=0;
|
||||
snprintf(req,sizeof(req),"run 20; stop 1006 == 7; stop 1007 == %u",(now + 2) & 0xff);
|
||||
@@ -1174,6 +1278,13 @@ int main(int argc, char **argv)
|
||||
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
|
||||
reply[0]=0;
|
||||
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
|
||||
/* a queued list's stop that becomes true is read as that list
|
||||
* starts, not as it is queued */
|
||||
CHECK(command_steps(&cmd,"run 2|on"));
|
||||
CHECK(command_steps(&cmd,"run 3; stop becomes 1006 == 6|on"));
|
||||
for (i=0;i<6;i++) runloop_iterate();
|
||||
reply[0]=0;
|
||||
CHECK(command_steps(&cmd,"|on") && !strncmp(reply,"QUEUED 0\nIDLE ",14));
|
||||
/* each queued list runs with its own stops, none where it has
|
||||
* none, and counts its own frames */
|
||||
now=ram7(&cmd); reply[0]=0;
|
||||
|
||||
@@ -200,6 +200,14 @@ static bool act_until(command_t *cmd, const char *arg)
|
||||
cmd->replier(cmd, UNTIL_TEXT, sizeof(UNTIL_TEXT) - 1);
|
||||
return true;
|
||||
}
|
||||
/* a wait on how far apart two bytes are, which may be below zero */
|
||||
#define APART_TEXT "MET 0\nVALUE -3\n"
|
||||
static bool act_apart(command_t *cmd, const char *arg)
|
||||
{
|
||||
(void)arg;
|
||||
cmd->replier(cmd, APART_TEXT, sizeof(APART_TEXT) - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* answers through its destination before it returns, as RUN_UNTIL does
|
||||
* when the byte already holds what was asked */
|
||||
@@ -283,6 +291,15 @@ static bool act_readt(command_t *cmd, const char *arg)
|
||||
cmd->replier(cmd, READT_TEXT, sizeof(READT_TEXT) - 1);
|
||||
return true;
|
||||
}
|
||||
/* a read of several ranges, the second cut short */
|
||||
#define READM_TEXT "READM 1000 AA 0F\nREADM 101e 1E 1F\nTRUNCATED 2\n" \
|
||||
"READM 1010 10\n"
|
||||
static bool act_readm(command_t *cmd, const char *arg)
|
||||
{
|
||||
(void)arg;
|
||||
cmd->replier(cmd, READM_TEXT, sizeof(READM_TEXT) - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* the menu: its title opens the reply, and a closed one says so */
|
||||
#define MENUS_TEXT "MENU Main Menu\nPATH Main Menu > Settings\n" \
|
||||
@@ -381,6 +398,7 @@ static struct cmd_action_map test_actions[] = {
|
||||
{ "LOGR", act_log, "No argument", "Answer rows between two trailers.", CMD_INFO_READ_ONLY, NULL, &test_rows_log },
|
||||
{ "BARE", act_log, "No argument", "Answer trailers and no rows.", CMD_INFO_READ_ONLY, NULL, &test_rows_bare },
|
||||
{ "UNTIL", act_until, "No argument", "Answer as RUN_UNTIL meets.", 0, NULL, &cmd_rows_until },
|
||||
{ "APART", act_apart, "No argument", "Answer as RUN_UNTIL on a difference.", 0, NULL, &cmd_rows_until },
|
||||
{ "NOW", act_now, "No argument", "Answer before returning.", 0, NULL, &cmd_rows_until },
|
||||
{ "WAIT", act_wait, "No argument", "Answer as RUN_UNTIL waits.", 0, NULL, &cmd_rows_until },
|
||||
{ "RUN_FRAMES", act_runf, "No argument", "Answer as RUN_FRAMES does.", 0, NULL, &cmd_rows_frames },
|
||||
@@ -393,6 +411,7 @@ static struct cmd_action_map test_actions[] = {
|
||||
{ "PREVF", act_prevf, "No argument", "Answer as RUN_FRAMES after a queue gave up.", 0, NULL, &cmd_rows_frames },
|
||||
{ "PREVE", act_preve, "No argument", "Answer as STEPS after a queue failed.", 0, NULL, &cmd_rows_steps },
|
||||
{ "READT", act_readt, "No argument", "Answer as a read cut short.", CMD_INFO_READ_ONLY, NULL, &cmd_rows_read },
|
||||
{ "READM", act_readm, "No argument", "Answer as a read of several ranges.", CMD_INFO_READ_ONLY, NULL, &cmd_rows_read },
|
||||
{ "MENUS", act_menus, "No argument", "Answer as MENU does.", 0, NULL, &cmd_rows_menu },
|
||||
{ "MENUC", act_menuc, "No argument", "Answer as MENU does when closed.", 0, NULL, &cmd_rows_menu },
|
||||
{ "MENUD", act_menud, "No argument", "Answer as MENU does over a dialog.", 0, NULL, &cmd_rows_menu },
|
||||
@@ -942,9 +961,10 @@ int main(void)
|
||||
&& strstr(rs, "\"required\":[\"index\",\"label\",\"path\"]")
|
||||
&& strstr(rs, "\"next\":{\"type\":\"integer\""));
|
||||
/* GET_STATUS, READ_CORE_MEMORY, ROWS, LROWS, SHORT, ONE, LOGR,
|
||||
* BARE, UNTIL, NOW, WAIT, RUN_FRAMES, PRESS, PRESSU, STOPS, STEPS,
|
||||
* STEP, PREVS, PREVF, PREVE, READT, MENUS, MENUC and MENUD */
|
||||
check("only the commands that answer rows declare one", n == 24);
|
||||
* BARE, UNTIL, APART, NOW, WAIT, RUN_FRAMES, PRESS, PRESSU, STOPS,
|
||||
* STEPS, STEP, PREVS, PREVF, PREVE, READT, READM, MENUS, MENUC and
|
||||
* MENUD */
|
||||
check("only the commands that answer rows declare one", n == 26);
|
||||
}
|
||||
{
|
||||
/* ROWS describes its columns; LROWS, answering the same ones,
|
||||
@@ -1213,6 +1233,11 @@ int main(void)
|
||||
"{\"label\":\"x\",\"address\":\"7e0086\",\"value\":\"52\"},"
|
||||
"{\"label\":\"mode\",\"address\":\"7e0770\",\"value\":\"8\"}],"
|
||||
"\"met\":12,\"value\":5}"));
|
||||
st = post(cmd, PORT_OPEN, NULL, call("APART", NULL), out, sizeof(out), NULL);
|
||||
check("a difference below zero is a number all the same",
|
||||
st == 200 && json_valid(body_of(out))
|
||||
&& strstr(out, "\"structuredContent\":{\"rows\":[],"
|
||||
"\"met\":0,\"value\":-3}"));
|
||||
st = post(cmd, PORT_OPEN, NULL, call("NOW", NULL), out, sizeof(out), NULL);
|
||||
check("answered before the command returns, it is answered once, whole",
|
||||
st == 200 && json_valid(body_of(out)) && !strstr(out, "Done.")
|
||||
@@ -1267,6 +1292,15 @@ int main(void)
|
||||
st == 200 && json_valid(body_of(out))
|
||||
&& strstr(out, "\"structuredContent\":{\"address\":\"101e\","
|
||||
"\"bytes\":\"1E 1F\",\"truncated\":true,\"read\":2}"));
|
||||
st = post(cmd, PORT_OPEN, NULL, call("READM", NULL), out, sizeof(out), NULL);
|
||||
check("a read of several ranges is the first, and each in ranges",
|
||||
st == 200 && json_valid(body_of(out))
|
||||
&& strstr(out, "\"structuredContent\":{\"address\":\"1000\","
|
||||
"\"bytes\":\"AA 0F\",\"ranges\":["
|
||||
"{\"address\":\"1000\",\"bytes\":\"AA 0F\"},"
|
||||
"{\"address\":\"101e\",\"bytes\":\"1E 1F\","
|
||||
"\"truncated\":true,\"read\":2},"
|
||||
"{\"address\":\"1010\",\"bytes\":\"10\"}]}"));
|
||||
st = post(cmd, PORT_OPEN, NULL, call("MENUS", NULL), out, sizeof(out), NULL);
|
||||
check("the menu's title, path, and three closing lines",
|
||||
st == 200 && json_valid(body_of(out))
|
||||
@@ -1297,6 +1331,9 @@ int main(void)
|
||||
"\"description\":\"The menu is closed.\"}"));
|
||||
check("and a text line is declared a string",
|
||||
m && strstr(m, "\"message\":{\"type\":\"string\","));
|
||||
m = strstr(out, "\"name\":\"READ_CORE_MEMORY\"");
|
||||
check("a read declares its ranges",
|
||||
m && strstr(m, "\"ranges\":{\"type\":\"array\","));
|
||||
}
|
||||
st = post(cmd, PORT_OPEN, NULL, call("PREVS", NULL), out, sizeof(out), NULL);
|
||||
check("a queued STEPS after a stop: its steps dropped, and the stop",
|
||||
|
||||
@@ -182,9 +182,9 @@ check('the rows commands declare an outputSchema: %d' % len(schemas),
|
||||
and 'GET_PLAYLIST' in schemas)
|
||||
# What a model is handed of each tool is its name, description and
|
||||
# input schema: that is context a client pays for before it does
|
||||
# anything, 12055 bytes at 30 tools, held under 15000. Titles,
|
||||
# anything, 12462 bytes at 30 tools, held under 15000. Titles,
|
||||
# annotations and output schemas are for the client and cost the model
|
||||
# nothing, so the whole list has a looser ceiling, 25000 (21844 now),
|
||||
# nothing, so the whole list has a looser ceiling, 25000 (22387 now),
|
||||
# and the 1800 on each tool catches prose an average would not.
|
||||
seen = sum(len(json.dumps({'name': t['name'],
|
||||
'description': t.get('description', ''),
|
||||
@@ -612,6 +612,22 @@ msg, t, _ = call(278, 'RUN_UNTIL', {'address': '1007', 'condition': '!=',
|
||||
sc = msg and msg['result'].get('structuredContent') or {}
|
||||
check('RUN_UNTIL waits on a byte: %s' % sc,
|
||||
'met' in sc and sc.get('value', 0) != 0)
|
||||
msg, t, _ = call(290, 'READ_CORE_MEMORY', {'address': '1000', 'bytes': 2,
|
||||
'more': '1010 1'})
|
||||
sc = msg and msg['result'].get('structuredContent') or {}
|
||||
check('several ranges in one read: %s' % sc,
|
||||
sc.get('address') == '1000' and sc.get('ranges') == [
|
||||
{'address': '1000', 'bytes': '00 01'},
|
||||
{'address': '1010', 'bytes': '10'}])
|
||||
msg, t, _ = call(291, 'RUN_UNTIL', {'address': '1005 - 1006',
|
||||
'condition': '==', 'value': -1})
|
||||
sc = msg and msg['result'].get('structuredContent') or {}
|
||||
check('a condition on how far apart two bytes are: %s' % sc,
|
||||
sc.get('met') == 0 and sc.get('value') == -1)
|
||||
msg, t, _ = call(292, 'STEPS', {'steps': 'run 2; stop becomes 1006 == 6'})
|
||||
sc = msg and msg['result'].get('structuredContent') or {}
|
||||
check('a stop already true does not end the list: %s' % sc,
|
||||
sc.get('done') == 2)
|
||||
|
||||
# A core with no memory map: its RAM, as it hands it over - system RAM
|
||||
# from 0, save RAM straight after - is what every memory tool reads.
|
||||
|
||||
Reference in new issue
Block a user