Compose analog routes with port and post-load admission; regenerate BIOS dispatcher

This commit is contained in:
Matthew Stanley
2026-10-24 09:51:59 -07:00
parent 0f5a9c1dff
commit fee33960cd
4 changed files with 61 additions and 35 deletions
+1 -1
View File
@@ -1 +1 @@
19ca6a92f84105422cbcdf584777ddc955cf76d80f574891d090a88b88780eed
e2bf436a77fe1e353ebd47bde9c1a99231b577c4ffd84170295866a3c8133699
+19
View File
@@ -36,6 +36,13 @@ extern int (*g_psx_bios_hle_hook)(CPUState* cpu, uint32_t phys);
extern uint32_t g_psx_mod_guest_functions;
extern int psx_mod_dispatch_guest_function(CPUState*, uint32_t);
/* Game function-override tier (runtime/include/func_override.h):
* hand-written C registered against a guest address. NULL (the
* default) = no overrides, dispatch identical to a build without
* the tier. Returns 1 when the override handled the call; the
* guest resumes at $ra exactly as if the original ran jr $ra. */
extern int (*g_psx_func_override_hook)(CPUState* cpu, uint32_t phys);
#ifdef PSX_HAS_GAME_DISPATCH
extern int psx_game_address_in_text(uint32_t addr);
#endif
@@ -9111,6 +9118,18 @@ static void psx_dispatch_impl(CPUState* cpu, uint32_t addr, uint32_t stop_addr)
/* Byte-guarded A0/B0/C0 call-vector tail stubs. */
if (!found && psx_bios_try_native_call_stub(cpu, addr))
found = 1;
/* Function-override tier: AFTER the BIOS tiers (an override
* can never shadow a kernel service vector), BEFORE every
* game code backend, so one address-keyed hook covers the
* static EXE, runtime-loaded overlays and dirty RAM alike.
* Handled (rc 1) => the override completed against guest
* state; resume at $ra via the trampoline's normal
* return/tail contract. rc 0 => fall through untouched. */
if (!found && g_psx_func_override_hook &&
g_psx_func_override_hook(cpu, addr & 0x1FFFFFFFu)) {
cpu->pc = cpu->gpr[31];
found = 1;
}
#ifdef PSX_HAS_GAME_DISPATCH
/* Game EXEs can overlap the BIOS shell copy window at
* physical 0x30000-0x5AFFF. If the target belongs to the
+10 -5
View File
@@ -8416,10 +8416,8 @@ static void handle_clear_input(int id, const char *json)
}
if(port>=3) {send_ok(id);return;}
if (port == 0 || port == (int)s_input_route_port) {
s_input_route_active = 0;
input_route_stop(&s_route);
s_input_route_wait_load = 0;
s_input_route_index = 0;
s_input_route_remaining = 0;
}
if (port == 0 || port == 2) {
s_pad_connected_override[1] = -1;
@@ -8502,10 +8500,10 @@ static void handle_input_route_status(int id, const char *json)
uint8_t ax[4] = { 0x80, 0x80, 0x80, 0x80 };
const int axes_live = input_route_frame_axes(&s_route, ax);
send_fmt("{\"id\":%d,\"ok\":true,\"active\":%s,\"steps\":%u,"
"\"index\":%u,\"remaining\":%u,\"axes_live\":%s,"
"\"port\":%u,\"waiting_for_load\":%s,\"index\":%u,\"remaining\":%u,\"axes_live\":%s,"
"\"axes\":[%u,%u,%u,%u]}\n",
id, s_route.active ? "true" : "false",
(unsigned)s_route.count, (unsigned)s_route.index,
(unsigned)s_route.count, s_input_route_port, s_input_route_wait_load?"true":"false", (unsigned)s_route.index,
(unsigned)s_route.remaining, axes_live ? "true" : "false",
ax[0], ax[1], ax[2], ax[3]);
}
@@ -17867,6 +17865,13 @@ int debug_server_get_input_override_port2(void)
return current;
}
int debug_server_get_input_override_extra(unsigned slot) {
if(slot<2 || slot>=8 || !s_extra_active[slot])return -1;
int value=s_extra_buttons[slot];
if(s_extra_frames[slot]>0 && !--s_extra_frames[slot])s_extra_active[slot]=0;
return value;
}
int debug_server_get_axis_override(unsigned char st[4])
{
/* A route step with axes owns the sticks for the poll it drove. */
+31 -29
View File
@@ -11,7 +11,7 @@ import tempfile
def function(source, name):
found = re.search(r"^(?:static )?(?:int|void) " + name + r"\([^\n]*\)\n\{.*?^\}",
found = re.search(r"^(?:static )?(?:int|void) " + name + r"\([^\n]*\)\s*\{.*?^\}",
source, re.M | re.S)
if not found:
raise ValueError("Missing source function: " + name)
@@ -23,10 +23,9 @@ def main():
parser.add_argument('--cc', required=True)
args = parser.parse_args()
source = (Path(__file__).resolve().parents[1] / 'src/debug_server.c').read_text()
declarations = source.split('#define INPUT_ROUTE_MAX_STEPS', 1)[1].split(
'static uint64_t s_input_route_start_frame = 0;', 1)[0]
preamble = r'''
#include <assert.h>
#include "input_route.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -43,6 +42,9 @@ static int s_input_override=-1,s_input_override_p2=-1;
static int s_input_frames,s_input_frames_p2,s_axis_override,s_pad_type_override=-1;
static int s_pad_connected_override[2]={-1,-1};
static int s_extra_active[8],s_extra_frames[8];
static InputRoute s_route;
static unsigned s_input_route_port=1;
static int s_input_route_wait_load;
static unsigned char s_axis_st[4],s_host_axes[4],s_host_lt,s_host_rt;
static int s_host_layer;
static uint16_t s_host_buttons;
@@ -60,7 +62,7 @@ static int json_get_int(const char *json,const char *key,int fallback){
'handle_clear_input', 'handle_input_route_clear', 'handle_input_route_append',
'handle_input_route_start', 'handle_input_route_stop', 'handle_input_route_status',
'input_route_loaded', 'debug_server_note_savestate_loaded',
'input_route_next', 'debug_server_get_input_override',
'input_route_poll_port', 'debug_server_get_input_override',
'debug_server_get_input_override_port2'))
checks = r'''
static void queue(void){
@@ -75,62 +77,62 @@ int main(void){
assert(s_input_override==123&&s_input_override_p2==-1);
s_frame_count=9;
assert(debug_server_get_input_override_port2()==-1);
assert(s_input_route_remaining==2);
assert(s_route.remaining==2);
s_frame_count=10;
assert(debug_server_get_input_override()==123);
assert(s_input_route_remaining==2);
assert(s_route.remaining==2);
assert(debug_server_get_input_override_port2()==32767);
assert(s_input_route_remaining==1);
assert(s_route.remaining==1);
++s_frame_count;
assert(debug_server_get_input_override()==123);
assert(debug_server_get_input_override_port2()==32767);
assert(s_input_route_index==1&&s_input_route_remaining==1);
assert(s_route.index==1&&s_route.remaining==1);
++s_frame_count;
assert(debug_server_get_input_override_port2()==65535);
assert(!s_input_route_active);
assert(!s_route.active);
assert(debug_server_get_input_override_port2()==-1);
queue();handle_input_route_start(1,"{\"port\":2}");
handle_clear_input(1,"{\"port\":1}");assert(s_input_route_active);
handle_clear_input(1,"{\"port\":2}");assert(!s_input_route_active);
handle_clear_input(1,"{\"port\":1}");assert(s_route.active);
handle_clear_input(1,"{\"port\":2}");assert(!s_route.active);
queue();handle_input_route_start(1,"{}");
assert(s_input_route_port==1);s_input_override_p2=456;
assert(debug_server_get_input_override_port2()==456&&s_input_route_remaining==2);
assert(debug_server_get_input_override_port2()==456&&s_route.remaining==2);
assert(debug_server_get_input_override()==32767);
handle_clear_input(1,"{\"port\":2}");assert(s_input_route_active);
handle_clear_input(1,"{\"port\":2}");assert(s_route.active);
handle_input_route_status(1,"{}");assert(strstr(response,"\"port\":1"));
handle_input_route_append(1,"{\"frames\":1,\"buttons\":0}");
assert(strstr(response,"ERROR"));
handle_input_route_stop(1,"{}");assert(!s_input_route_active);
handle_input_route_stop(1,"{}");assert(!s_route.active);
handle_input_route_start(1,"{\"port\":3}");
assert(strstr(response,"ERROR")&&!s_input_route_active);
assert(strstr(response,"ERROR")&&!s_route.active);
handle_input_route_start(1,"{\"port\":2}");
handle_clear_input(1,"{}");assert(!s_input_route_active);
handle_clear_input(1,"{}");assert(!s_route.active);
handle_input_route_clear(1,"{}");handle_input_route_start(1,"{}");
assert(strstr(response,"empty"));
queue();handle_input_route_start(1,"{\"port\":2,\"after_load\":1}");
assert(s_input_route_active&&s_input_route_wait_load);
assert(s_route.active&&s_input_route_wait_load);
assert(strstr(response,"\"waiting_for_load\":true"));
for(unsigned n=0;n<20;++n){++s_frame_count;assert(debug_server_get_input_override_port2()==-1);}
assert(s_input_route_remaining==2);
assert(s_route.remaining==2);
handle_input_route_status(1,"{}");assert(strstr(response,"\"waiting_for_load\":true"));
/* Route admission is independent of optional fingerprint reset. */
debug_server_note_savestate_loaded();assert(!s_input_route_wait_load);
assert(debug_server_get_input_override_port2()==32767&&s_input_route_remaining==1);
assert(debug_server_get_input_override_port2()==32767&&s_route.remaining==1);
debug_server_note_savestate_loaded(); /* An ordinary later load does not restart it. */
assert(s_input_route_remaining==1);
assert(s_route.remaining==1);
assert(debug_server_get_input_override_port2()==32767);
assert(debug_server_get_input_override_port2()==65535&&!s_input_route_active);
assert(debug_server_get_input_override_port2()==65535&&!s_route.active);
for(unsigned port=1;port<=2;++port){
queue();handle_input_route_start(1,port==1?"{\"after_load\":1}":"{\"port\":2,\"after_load\":1}");
handle_clear_input(1,port==1?"{\"port\":2}":"{\"port\":1}");assert(s_input_route_wait_load);
handle_clear_input(1,port==1?"{\"port\":1}":"{\"port\":2}");assert(!s_input_route_wait_load&&!s_input_route_active);
handle_clear_input(1,port==1?"{\"port\":1}":"{\"port\":2}");assert(!s_input_route_wait_load&&!s_route.active);
}
queue();handle_input_route_start(1,"{\"after_load\":1}");
handle_input_route_stop(1,"{}");debug_server_note_savestate_loaded();assert(!s_input_route_active);
handle_input_route_stop(1,"{}");debug_server_note_savestate_loaded();assert(!s_route.active);
queue();handle_input_route_start(1,"{\"after_load\":1}");
handle_input_route_clear(1,"{}");debug_server_note_savestate_loaded();assert(!s_input_route_active);
queue();handle_input_route_start(1,"{\"after_load\":2}");assert(strstr(response,"ERROR")&&!s_input_route_active);
handle_input_route_start(1,"{\"after_load\":1,\"at_frame\":10}");assert(strstr(response,"ERROR")&&!s_input_route_active);
handle_input_route_clear(1,"{}");debug_server_note_savestate_loaded();assert(!s_route.active);
queue();handle_input_route_start(1,"{\"after_load\":2}");assert(strstr(response,"ERROR")&&!s_route.active);
handle_input_route_start(1,"{\"after_load\":1,\"at_frame\":10}");assert(strstr(response,"ERROR")&&!s_route.active);
handle_input_route_start(1,"{\"after_load\":1}");s_fp_reset_on_load=1;s_fp_head=4;s_fp_total=5;
debug_server_note_savestate_loaded();assert(!s_input_route_wait_load&&!s_fp_reset_on_load&&!s_fp_head&&!s_fp_total);
puts("debug input route: P2 timing, isolation, deferred start, clearing and legacy P1 pass");
@@ -139,11 +141,11 @@ int main(void){
'''
with tempfile.TemporaryDirectory(prefix='psx-input-route-') as directory:
path = Path(directory) / 'route.c'
path.write_text(preamble + '\n#define INPUT_ROUTE_MAX_STEPS' + declarations +
'static uint64_t s_input_route_start_frame = 0;\n' + body + checks)
path.write_text(preamble + body + checks,encoding='utf-8',newline='\n')
executable = Path(directory) / 'route.exe'
subprocess.run([args.cc, '-std=c11', '-Wall', '-Wextra', '-Werror', str(path),
'-o', str(executable)], check=True)
'-I',str(Path(__file__).resolve().parents[1]/'include'),
str(Path(__file__).resolve().parents[1]/'src/input_route.c'),'-o', str(executable)], check=True)
subprocess.run([str(executable)], check=True)