diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index a7131a2b..d6385167 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -23,6 +23,10 @@ psxrecomp_add_runtime_target(psx-runtime # (docs/ENHANCEMENTS.md G1.10). Titles get their _pgxp the same way. if(BUILD_TESTING) + add_executable(debug_server_policy_test tests/test_debug_server_policy.c src/debug_server_policy.c) + target_include_directories(debug_server_policy_test PRIVATE include) + target_compile_definitions(debug_server_policy_test PRIVATE PSX_NO_DEBUG_TOOLS=1) + add_test(NAME debug_server_policy_test COMMAND debug_server_policy_test) add_executable(debug_bulk_write_test tests/test_debug_bulk_write.c src/debug_bulk_write.c) target_include_directories(debug_bulk_write_test PRIVATE include) add_test(NAME debug_bulk_write_test COMMAND debug_bulk_write_test) diff --git a/runtime/include/debug_server.h b/runtime/include/debug_server.h index 63bdc66d..99303719 100644 --- a/runtime/include/debug_server.h +++ b/runtime/include/debug_server.h @@ -89,6 +89,9 @@ void debug_server_init(int port); * Reset the mod request before activating each new session's plugins. */ void debug_server_request(int enabled); int debug_server_requested(void); +/* Reconcile listener policy after every session's mod activation. Keeps an + * enabled listener, starts a newly enabled one, and closes a disabled one. */ +void debug_server_apply_request(int port); /* Current listener status for heartbeat diagnostics. error is the platform * socket error captured by init, or 0 when the listener is active. */ diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index b1cd4e5c..d5e4f309 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -475,6 +475,7 @@ set(PSXRECOMP_RUNTIME_SOURCES ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_speed.c ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_debug.c ${PSXRECOMP_ROOT}/runtime/src/debug_bulk_write.c + ${PSXRECOMP_ROOT}/runtime/src/debug_server_policy.c ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_pgxp.c ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_bezel.c ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_ram.c diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index fb5e1063..3a9f8ebc 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -127,24 +127,9 @@ static sock_t s_listen = SOCK_INVALID; static sock_t s_client = SOCK_INVALID; static int s_port = DEFAULT_DEBUG_PORT; static int s_listen_err = 0; /* platform socket error captured by init */ -static int s_requested = 0; - -void debug_server_request(int enabled) -{ - s_requested = enabled != 0; -} - -int debug_server_requested(void) -{ - const char *env = getenv("PSX_DEBUG_SERVER"); - if (env && strcmp(env, "0") == 0) return 0; - if (env && strcmp(env, "1") == 0) return 1; -#ifdef PSX_NO_DEBUG_TOOLS - return s_requested; -#else - return 1; +#ifdef _WIN32 +static int s_wsa_started; #endif -} #define RECV_BUF_SIZE 8192 static PSX_BSS char s_recv_buf[RECV_BUF_SIZE]; @@ -16121,6 +16106,7 @@ malformed: void debug_server_init(int port) { + if (s_listen != SOCK_INVALID) return; if (port > 0) s_port = port; /* Deterministic cold-boot controller probe. Unlike a TCP set_input command, @@ -16218,13 +16204,16 @@ void debug_server_init(int port) #ifdef _WIN32 WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); + s_listen_err = WSAStartup(MAKEWORD(2, 2), &wsa); + if (s_listen_err) return; + s_wsa_started = 1; #endif s_listen = socket(AF_INET, SOCK_STREAM, 0); if (s_listen == SOCK_INVALID) { s_listen_err = sock_error(); fprintf(stdout, "psxrecomp: debug server socket() FAILED\n"); + debug_server_shutdown(); return; } #ifdef _WIN32 @@ -16248,6 +16237,7 @@ void debug_server_init(int port) fprintf(stdout, "psxrecomp: debug server bind(%d) FAILED\n", s_port); sock_close(s_listen); s_listen = SOCK_INVALID; + debug_server_shutdown(); return; } @@ -16276,6 +16266,10 @@ void debug_server_init(int port) s_io_thread = SDL_CreateThread(io_thread_main, "psx-dbg-io", NULL); if (!s_io_thread) s_io_running = 0; } + if (!s_io_thread) { + debug_server_shutdown(); + return; + } if (!s_frame_history) { s_frame_history = (PSXFrameRecord *)calloc(FRAME_HISTORY_CAP, sizeof(PSXFrameRecord)); @@ -16859,8 +16853,15 @@ void debug_server_shutdown(void) sock_close(s_client); s_client = SOCK_INVALID; } + if (s_io_mutex) { SDL_DestroyMutex(s_io_mutex); s_io_mutex = NULL; } + if (s_io_req_cv) { SDL_DestroyCond(s_io_req_cv); s_io_req_cv = NULL; } + if (s_io_resp_cv) { SDL_DestroyCond(s_io_resp_cv); s_io_resp_cv = NULL; } + free(s_resp_buf); + s_resp_buf = NULL; + s_resp_cap = s_resp_len = 0; + s_io_state = IO_IDLE; #ifdef _WIN32 - WSACleanup(); + if (s_wsa_started) { WSACleanup(); s_wsa_started = 0; } #endif } diff --git a/runtime/src/debug_server_policy.c b/runtime/src/debug_server_policy.c new file mode 100644 index 00000000..abf1000b --- /dev/null +++ b/runtime/src/debug_server_policy.c @@ -0,0 +1,30 @@ +#include "debug_server.h" +#include +#include + +static int requested; + +void debug_server_request(int enabled) { requested = enabled != 0; } + +int debug_server_requested(void) +{ + const char *env = getenv("PSX_DEBUG_SERVER"); + if (env && strcmp(env, "0") == 0) return 0; + if (env && strcmp(env, "1") == 0) return 1; +#ifdef PSX_NO_DEBUG_TOOLS + return requested; +#else + return 1; +#endif +} + +void debug_server_apply_request(int port) +{ + int listening = 0; + debug_server_get_status(&listening, NULL, NULL); + if (debug_server_requested()) { + if (!listening) debug_server_init(port); + } else if (listening) { + debug_server_shutdown(); + } +} diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index ccd4314d..61e18f18 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -18844,13 +18844,13 @@ session_reboot: }; memcard_init_slots(memcard_dir_str.c_str(), slots); } + debug_server_apply_request(debug_port); if (!rematch_session) { std::atexit(memcard_flush_all); /* Persist the game's native OPTION settings on any exit path (belt-and- * suspenders; save-on-change already persists them live). No-op if not yet * armed, so it can't overwrite the saved file with boot defaults. */ std::atexit(game_options_save_now); - if (debug_server_requested()) debug_server_init(debug_port); #ifndef PSX_NO_DEBUG_TOOLS host_sampler_start(); /* this is the emulation thread */ #endif diff --git a/runtime/tests/test_debug_server_policy.c b/runtime/tests/test_debug_server_policy.c new file mode 100644 index 00000000..d83978d8 --- /dev/null +++ b/runtime/tests/test_debug_server_policy.c @@ -0,0 +1,41 @@ +#include "debug_server.h" +#include +#include + +static int listening, starts, stops, last_port, failures; +void debug_server_get_status(int *active, int *port, int *error) { + if (active) *active=listening; + if (port) *port=last_port; + if (error) *error=0; +} +void debug_server_init(int port) { ++starts;listening=1;last_port=port; } +void debug_server_shutdown(void) { ++stops;listening=0; } +#define CHECK(x) do { if (!(x)) { fprintf(stderr,"line %d: %s\n",__LINE__,#x);++failures; } } while(0) +static void env_choice(const char *value) { +#ifdef _WIN32 + _putenv_s("PSX_DEBUG_SERVER",value?value:""); +#else + if(value)setenv("PSX_DEBUG_SERVER",value,1);else unsetenv("PSX_DEBUG_SERVER"); +#endif +} +int main(void) { + env_choice(NULL); + debug_server_request(0);debug_server_apply_request(4370); + CHECK(!listening && starts==0 && stops==0); + /* First session was vanilla netplay; the next offline session selects it. */ + debug_server_request(1);debug_server_apply_request(4370); + CHECK(listening && starts==1 && last_port==4370); + debug_server_apply_request(4370); + CHECK(listening && starts==1 && stops==0); + debug_server_request(0);debug_server_apply_request(4370); + CHECK(!listening && stops==1); + debug_server_apply_request(4370);CHECK(stops==1); + debug_server_request(1);env_choice("0");debug_server_apply_request(4370); + CHECK(!listening && starts==1); + debug_server_request(0);env_choice("1");debug_server_apply_request(4488); + CHECK(listening && starts==2 && last_port==4488); + env_choice(NULL);debug_server_apply_request(4488); + CHECK(!listening && stops==2); + if(!failures)puts("PASS debug server session policy: late enable, reuse, disable, overrides"); + return failures?1:0; +}