Compare commits

..
Author SHA1 Message Date
Rob Loach 3cbe16f2cb MCP: two-address conditions, ranges, edge stops 2026-11-10 03:30:37 -05:00
8 changed files with 497 additions and 106 deletions

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
# Future
- NETWORK: Conditions compare how far apart two addresses are (485 - 47e <= 32), STEPS takes stop becomes for a stop that fires only once it starts to hold, READ_CORE_MEMORY reads several ranges in one request, and WATCH counts its limit after the offs in a request
- D3D11: Software-rendering cores draw straight into lent staging textures, copied to the frame texture on the GPU instead of by the CPU
- VULKAN: Keep the pipeline cache on disk between runs, one file per GPU, so stock and slang shader pipelines are not recompiled by the driver on every launch, preset load or driver reinit
- LINUX: Game Mode implemented natively (I/O priority plus a power-profiles-daemon performance hold); Feral libgamemode is no longer loaded and deps/feralgamemode is removed
+217 -77
View File
@@ -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);
+25 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+40 -3
View File
@@ -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",
+18 -2
View File
@@ -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.