#include "mod_runtime.h" #include "func_override.h" #include "mod_netplay.h" #include "host_launch_timing.h" #include "host_overlay.h" #include "host_file_identity.h" #include "mod_packages.h" #include "mod_plugins.h" #include "mod_immediate.h" #include "psx_sha256.h" #include "psx_lobby_client.h" #include "gpu.h" #include "gpu_hd_textures.h" #include "cpu_state.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; static std::array ram; extern "C" int (*g_psx_func_override_hook)(CPUState*, uint32_t); static int failures; static int activation_calls; static int override_activation_calls; static void override_activate() { ++override_activation_calls; } static int override_scope_checks; static int override_body(CPUState*) { char option[16]{}; if (psx_mod_current_option_value("scope",option,sizeof option) && !std::strcmp(option,"owned") && psx_mod_function_entry_active()) ++override_scope_checks; mod_runtime_on_vblank(); if (psx_mod_current_option_value("scope",option,sizeof option) && !std::strcmp(option,"owned") && psx_mod_function_entry_active()) ++override_scope_checks; return 1; } static int plugin_calls; static int restore_calls; static int option_renderer_activations; static void option_renderer_activate(void) { ++option_renderer_activations; } static PsxLobbyMatchCaps test_match_caps; extern "C" const PsxLobbyMatchCaps* psx_lobby_match_caps(void) { return &test_match_caps; } static int game_netplay_entry_hits; static int game_netplay_filter_hits; static int test_netplay_active; extern "C" int psx_netplay_active(void) { return test_netplay_active; } static void test_game_netplay_entry(CPUState*, uint32_t) { game_netplay_entry_hits++; } static int test_game_netplay_filter(CPUState* cpu, uint32_t) { game_netplay_filter_hits++; cpu->gpr[2] = 0x1234u; return 1; } extern "C" uint8_t psx_read_byte(uint32_t address) { return ram[address & 0x1fffffu]; } extern "C" void psx_write_byte(uint32_t address, uint8_t value) { ram[address & 0x1fffffu] = value; } extern "C" uint16_t psx_read_half(uint32_t address) { const uint32_t offset = address & 0x1fffffu; return (uint16_t)(ram[offset] | ((uint16_t)ram[offset + 1] << 8)); } extern "C" void psx_write_half(uint32_t address, uint16_t value) { const uint32_t offset = address & 0x1fffffu; ram[offset] = (uint8_t)value; ram[offset + 1] = (uint8_t)(value >> 8); } /* Side-effect-free peek (memory.c); host-output observation reads. */ extern "C" int psx_peek_word(uint32_t, uint32_t* out) { *out = 0; return 0; } extern "C" uint32_t psx_read_word(uint32_t address) { const uint32_t offset = address & 0x1fffffu; return (uint32_t)ram[offset] | ((uint32_t)ram[offset + 1] << 8) | ((uint32_t)ram[offset + 2] << 16) | ((uint32_t)ram[offset + 3] << 24); } extern "C" void psx_write_word(uint32_t address, uint32_t value) { const uint32_t offset = address & 0x1fffffu; ram[offset] = (uint8_t)value; ram[offset + 1] = (uint8_t)(value >> 8); ram[offset + 2] = (uint8_t)(value >> 16); ram[offset + 3] = (uint8_t)(value >> 24); } /* Mod writes are host stores (memory.c psx_host_write_*). */ extern "C" void psx_host_write_byte(uint32_t address, uint8_t value) { psx_write_byte(address, value); } extern "C" void psx_host_write_half(uint32_t address, uint16_t value) { psx_write_half(address, value); } extern "C" void psx_host_write_word(uint32_t address, uint32_t value) { psx_write_word(address, value); } extern "C" uint32_t psx_mod_memory_alloc(uint32_t, uint32_t) { return 0; } extern "C" uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t, uint32_t) { return 0; } extern "C" void psx_ram_reset_size_request(void) {} static int hd_shutdown_calls, hd_configure_calls, hd_dump_value; static int hud_calls; static void old_session_hud(int, int, void*) { ++hud_calls; } static int hd_replacements_value, hd_reload_calls; static std::string hd_root; extern "C" void gpu_hd_textures_shutdown(void) { ++hd_shutdown_calls; hd_root.clear(); } extern "C" int gpu_hd_textures_configure(const char* root, int replacements, int dump, char*, size_t) { ++hd_configure_calls; hd_root = root; hd_replacements_value = replacements; hd_dump_value = dump; return 1; } extern "C" void gpu_hd_textures_set_dump_enabled(int enabled) { hd_dump_value = enabled; } extern "C" int gpu_hd_textures_active(void) { return !hd_root.empty() && (hd_replacements_value || hd_dump_value); } extern "C" void gpu_hd_textures_get_diag(GpuHdTextureDiag* out) { *out = {}; out->root = hd_root.c_str(); } extern "C" int gpu_hd_textures_reload(char*, size_t) { ++hd_reload_calls; return 1; } static void test_hd_activation(void) { char replacements[16], dump[16]; if (!psx_mod_current_option_value("replacements", replacements, sizeof(replacements)) || !psx_mod_current_option_value("dump", dump, sizeof(dump))) { ++failures; return; } if (!psx_mod_set_hd_texture_pack("pack", std::string(replacements) == "true", std::string(dump) == "true")) ++failures; } extern "C" void psx_projection_reset_session(void) {} extern "C" void gpu_ws_set_native_scene_predicate(int (*)(void)) {} extern "C" int psx_ws_x_margin(void) { return 0; } /* Stand-in for the GPU's display geometry. psx_mod_display_width/height must * report exactly what the presenter reports -- a plugin drawing an overlay * uses this as the screen edge, so a substituted or rounded value would put * HUD elements in the wrong place. */ static GpuDisplayInfo g_test_display; extern "C" void gpu_get_display_info(GpuDisplayInfo* out) { *out = g_test_display; } /* func_override.c doubles: linked in so mod_runtime's package arming/reset * paths run for real. The tier's own behavior is pinned by * test_func_override.c; here the doubles only need to satisfy the link. */ extern "C" uint32_t psx_peek_word_untraced(uint32_t) { return 0; } extern "C" void psx_dispatch_call(struct CPUState*, uint32_t, uint32_t) {} extern "C" { uint64_t psx_cycle_count = 0; uint64_t psx_next_service_cycle = 0; int psx_in_device_service = 0; int g_event_step_conservative = 0; int g_ls_replay_active = 0; uint32_t g_psx_cyc_batch = 0; uint32_t g_psx_cyc_batch_limit = 0; void psx_devices_service_to_now(void) {} void psx_advance_cycles_slow(uint32_t cycles) { psx_cycle_count += cycles; } } extern "C" void dirty_ram_mark_executable_range(uint32_t, uint32_t) {} static int test_game_started = 1; extern "C" int fntrace_is_game_started(void) { return test_game_started; } static int disc_request_calls; static int test_disc_request(int *ready) { ++disc_request_calls; *ready = 1; return 2; } static void test_disc_request_activate(void) { psx_mod_set_disc_request_provider(test_disc_request); } /* Widescreen tag passthroughs mod_runtime forwards to the GPU; unused here. */ extern "C" void gpu_ws_tag_hud_primitive(uint32_t, int) {} extern "C" void gpu_ws_tag_world_primitive(uint32_t, int) {} extern "C" void gpu_ws_set_adaptive_backdrop_preload(int) {} extern "C" int gpu_ws_configured_x_reveal(void) { return 0; } extern "C" void gpu_ws_set_manual_view_anchor(int, int) {} extern "C" void gpu_ws_tag_hud_prim(uint32_t, int) {} extern "C" void gpu_ws_tag_screen_mask_quad(uint32_t) {} extern "C" void gpu_ws_tag_radial_screen_mask_quad(uint32_t, float) {} extern "C" void gpu_ws_tag_feedback_rect(uint32_t) {} static CPUState* interrupted_entry_cpu; static void test_vblank_plugin(void) { plugin_calls++; if (interrupted_entry_cpu && psx_mod_finish_function(interrupted_entry_cpu)) failures++; } /* Function-entry hooks: one owned by the plan's active plugin, one by a * plugin whose feature is disabled, one by an id no package selects. */ static int active_entry_hits; static int disabled_entry_hits; static int unselected_entry_hits; static uint32_t active_entry_last; static int entry_test_mode, nested_result; #if defined(_WIN32) && defined(__GNUC__) && !defined(__clang__) /* Model a guest scheduler jump which abandons native C++ scopes. Asking the * Windows CRT longjmp to unwind those scopes can itself fault in SEH, before * this fixture gets to verify the stranded callback-context guard. */ static intptr_t callback_escape[5]; #define callback_setjmp() __builtin_setjmp(callback_escape) #define callback_longjmp() __builtin_longjmp(callback_escape, 1) #else static std::jmp_buf callback_escape; #define callback_setjmp() setjmp(callback_escape) #define callback_longjmp() std::longjmp(callback_escape, 1) #endif static uint32_t nested_pc; static void test_active_entry(CPUState* cpu, uint32_t address) { active_entry_hits++; active_entry_last = address; if (entry_test_mode == 1) { CPUState unrelated{}; if (psx_mod_finish_function(&unrelated) || !psx_mod_finish_function(cpu)) failures++; cpu->gpr[2] = 0x12345678u; } else if (entry_test_mode == 2 || entry_test_mode == 3) { const int mode = entry_test_mode; if (mode == 3 && !psx_mod_finish_function(cpu)) failures++; CPUState nested{}; nested.gpr[31] = 0x80004000u; entry_test_mode = mode == 2 ? 1 : 0; nested_result = psx_mod_function_entry(&nested, address); nested_pc = nested.pc; entry_test_mode = mode; } else if (entry_test_mode == 4) { /* A native call made by a hook can cross a guest VBlank. Its * callbacks must neither inherit nor discard the interrupted hook. */ interrupted_entry_cpu = cpu; mod_runtime_on_vblank(); interrupted_entry_cpu = nullptr; if (!psx_mod_finish_function(cpu)) failures++; cpu->gpr[2] = 0x87654321u; } else if (entry_test_mode == 5) { /* Guest scheduling/RFE can abandon the callback's C++ scope. */ callback_longjmp(); } else if (entry_test_mode == 6) { if (!psx_mod_finish_function(cpu)) failures++; ModFunctionEntryContext interrupted{}; mod_runtime_function_entry_context_save(&interrupted); if (callback_setjmp() == 0) { CPUState nested{}; entry_test_mode = 5; psx_mod_function_entry(&nested, address); failures++; /* the nested callback must escape */ } mod_runtime_function_entry_context_restore(&interrupted); if (!psx_mod_function_entry_active() || !psx_mod_finish_function(cpu)) failures++; entry_test_mode = 6; } } static void test_disabled_entry(CPUState*, uint32_t) { disabled_entry_hits++; } static void test_unselected_entry(CPUState*, uint32_t) { unselected_entry_hits++; } static bool filter_handles; static bool filter_context_active; static unsigned active_filter_hits; static unsigned inactive_filter_hits; static uint32_t active_filter_last; static int test_active_filter(CPUState* cpu, uint32_t address) { ++active_filter_hits; active_filter_last = address; filter_context_active = psx_mod_function_entry_active() != 0; if (!filter_handles) return 0; cpu->gpr[2] = 42; return 1; } static int test_inactive_filter(CPUState*, uint32_t) { ++inactive_filter_hits; return 1; } static int guest_function_hits; static int instruction_hits; static int memory_hits; static uint32_t memory_delta = 0x1000u; static uint32_t test_memory_address(const CPUState*, uint32_t, uint32_t, uint32_t address) { ++memory_hits; return address + memory_delta; } static void test_instruction(CPUState* cpu, uint32_t) { instruction_hits++; cpu->gpr[5] = 0x80301008u; if (psx_mod_finish_function(cpu)) failures++; } static void test_guest_function(CPUState* cpu, uint32_t) { guest_function_hits++; const uint32_t ra = cpu->gpr[31]; CPUState nested{}; nested.gpr[31] = 0x80007000u; const int mode = entry_test_mode; entry_test_mode = 1; if (!psx_mod_function_entry(&nested, 0x80003000u) || nested.pc != nested.gpr[31]) failures++; entry_test_mode = mode; interrupted_entry_cpu = cpu; mod_runtime_on_vblank(); interrupted_entry_cpu = nullptr; if (!psx_mod_finish_function(cpu)) failures++; cpu->gpr[2] = cpu->gpr[4] + 7u; if (cpu->gpr[31] != ra) failures++; } static void test_activation_plugin(void) { activation_calls++; } static const uint8_t* media_snapshot; static uint64_t media_size; static int media_access, foreign_media_access; static uint32_t mounted_lba; static int extent_ok, invalid_extent, foreign_extent; static int audio_ok, audio_invalid, audio_foreign, audio_data_rejected; static uint32_t audio_lba, audio_lba2; static void test_disc_audio_plugin() { PSXModCDDATrack tracks[]={{nullptr,0,0,2},{nullptr,0,0,3}}; uint32_t starts[2]{},counts[2]{}; if(!psx_mod_append_cdda_tracks(tracks,2,starts,counts) || starts[0]!=150 || starts[1]!=153 || counts[0]!=3 || counts[1]!=2) failures++; } static int immediate_activation_choice, immediate_reset_accepted; static void test_immediate_reset(void) { immediate_reset_accepted = psx_mod_set_immediate_selector(1, 2); } static void test_media_plugin(void) { if (immediate_activation_choice) psx_mod_set_immediate_selector(1, immediate_activation_choice); media_access = psx_mod_current_resource_bytes("rom", &media_snapshot, &media_size); const uint8_t* foreign = nullptr; uint64_t size = 0; foreign_media_access = psx_mod_current_resource_bytes("private", &foreign, &size); extent_ok=psx_mod_append_disc_extent("xa",0,2,&mounted_lba); uint32_t bad=99; invalid_extent=psx_mod_append_disc_extent("xa",1,2,&bad); foreign_extent=psx_mod_append_disc_extent("private",0,1,&bad); PSXModCDDATrack sources[]={{"xa",0,1,0},{"xa",2320,1,0}}; uint32_t starts[2]{},counts[2]{}; audio_ok=psx_mod_append_cdda_tracks(sources,2,starts,counts); audio_lba=starts[0];audio_lba2=starts[1]; sources[1].byte_offset=2321; audio_invalid=psx_mod_append_cdda_tracks(sources,2,starts,counts); if(starts[0] || counts[0]) failures++; sources[1]={"private",0,1,0}; audio_foreign=psx_mod_append_cdda_tracks(sources,2,starts,counts); sources[1]={nullptr,0,0,1}; audio_data_rejected=!psx_mod_append_cdda_tracks(sources,2,starts,counts); } extern "C" { void* iso_open(const char*); void iso_close(void*); int iso_read_raw_sector(void*,uint32_t,uint8_t*,int); int iso_read_sector(void*,uint32_t,uint8_t*,int); int iso_read_subq(void*,uint32_t,uint8_t*,int,int*); uint32_t iso_sector_count(void*); int iso_track_count(void*); uint32_t iso_track_start_lba(void*,int); int iso_track_is_audio(void*,int); int iso_cdda_track_count(void*); uint32_t iso_cdda_track_start_lba(void*,int); uint32_t iso_cdda_sector_count(void*); int iso_cdda_track_is_audio(void*,int); int iso_read_cdda_sector(void*,uint32_t,uint8_t*,int); } static int big_ram_activations; static int option_changed_hits = 0; static std::string option_changed_value; static int option_changed_refuse = 0; /* 1 = the plugin answers "next start" */ static int test_option_changed(const char* option_id, const char* value) { char cur[32]; option_changed_hits++; /* Runs in the plugin's context: current_option_value answers the new value. */ if (std::string(option_id) == "size" && psx_mod_current_option_value("size", cur, sizeof(cur)) == 1) option_changed_value = cur; (void)value; return option_changed_refuse ? 0 : 1; } static void test_big_ram_activation(void) { big_ram_activations++; } static void test_restore_plugin(void) { restore_calls++; if (ram[0x1000] != 0xa1) failures++; /* main plan must be reapplied first */ } static void check(bool value, const char* message) { if (!value) { std::cerr << "FAIL: " << message << "\n"; failures++; } } static void write_text(const fs::path& path, const std::string& text) { fs::create_directories(path.parent_path()); std::ofstream out(path); out << text; } static void write_bytes(const fs::path& path, const std::vector& bytes) { fs::create_directories(path.parent_path()); std::ofstream out(path, std::ios::binary); out.write((const char*)bytes.data(), (std::streamsize)bytes.size()); } static std::string sha256_hex(const std::vector& bytes) { uint8_t digest[32]; psx_sha256_compute(bytes.data(), bytes.size(), digest); static const char hex[] = "0123456789abcdef"; std::string out(64, '0'); for (size_t i = 0; i < 32; ++i) { out[i * 2] = hex[digest[i] >> 4]; out[i * 2 + 1] = hex[digest[i] & 15]; } return out; } static uint64_t launch_timing_total() { uint64_t total = 0; host_launch_timing_snapshot(nullptr, 0, &total, nullptr); return total; } static void check_commit_timing(uint64_t before, bool ok, bool hashed) { HostLaunchTimingEvent events[HOST_LAUNCH_TIMING_CAPACITY]{}; const auto n = host_launch_timing_snapshot(events, HOST_LAUNCH_TIMING_CAPACITY, nullptr, nullptr); const HostLaunchTimingEvent* commit = nullptr; for (uint32_t i = 0; i < n; ++i) if (events[i].seq > before && events[i].stage == HOST_LAUNCH_COMMIT) commit = &events[i]; check(commit && commit->ok == unsigned(ok), "real commit outcome retained by C snapshot"); if (!commit) return; uint32_t stages = 0; for (uint32_t i = 0; i < n; ++i) { const auto& e = events[i]; if (e.seq <= before || e.parent_id != commit->scope_id) continue; stages |= 1u << e.stage; check(e.start_us >= commit->start_us && e.start_us + e.duration_us <= commit->start_us + commit->duration_us, "real commit child measurement nested inside total"); } check(bool(stages & (1u << HOST_LAUNCH_DISC_HASH)) == hashed, "disc hash timing follows actual digest reuse"); const uint32_t required = (1u << HOST_LAUNCH_PREPARE_RESOURCES) | (1u << HOST_LAUNCH_RESOLVE); check((stages & required) == required, "real preparation and resolution observed"); if (ok) { const uint32_t final = (1u << HOST_LAUNCH_OVERLAY_VERIFY) | (1u << HOST_LAUNCH_DERIVED_DISC) | (1u << HOST_LAUNCH_SAVE_STATE) | (1u << HOST_LAUNCH_BUILD_DISC_INDEX); check((stages & final) == final, "real successful commit final stages observed"); } else { check(!(stages & (1u << HOST_LAUNCH_BUILD_DISC_INDEX)), "rejected commit does not invent final-stage completion"); } } extern "C" int psx_mod_counters_snapshot(const char**, uint64_t*, uint64_t*, int, uint64_t*); static uint64_t startup_counter(const char* requested) { const char* names[128]{}; uint64_t counts[128]{}, frames[128]{}; const int count = psx_mod_counters_snapshot(names, counts, frames, 128, nullptr); for (int i = 0; i < count && i < 128; ++i) if (std::string(names[i]) == requested) return counts[i]; return 0; } static int test_command_calls; static int command_fill_reply(const char* args, char* reply, int cap) { ++test_command_calls; std::memset(reply, 'x', (size_t)cap); // deliberately fills the entire bound return std::strcmp(args, "test") == 0 ? 17 : 11; } static int command_replacement(const char*, char* reply, int) { reply[0] = 0; return 99; } static void test_commands(void) { char name[] = "qa.owned"; check(psx_mod_register_command(name, command_fill_reply) == 1, "register owned command name"); std::memset(name, '?', sizeof(name)-1); struct { char reply[8]; char guard; } buffer = {{0}, 'g'}; check(psx_mod_run_command("qa.owned", "test", buffer.reply, sizeof(buffer.reply)) == 17, "command owns name and forwards args/result"); check(buffer.reply[7] == 0 && std::strlen(buffer.reply) == 7 && buffer.guard == 'g', "full-capacity reply is bounded and terminated"); check(psx_mod_run_command("qa.owned", nullptr, buffer.reply, 1) == 11 && buffer.reply[0] == 0, "one-byte reply and null args are bounded"); const int before = test_command_calls; check(psx_mod_run_command("qa.owned", "", nullptr, 8) == -1 && psx_mod_run_command("qa.owned", "", buffer.reply, 0) == -1 && psx_mod_run_command("missing", "", buffer.reply, 8) == -1 && test_command_calls == before, "invalid or unknown command does not invoke callback"); check(!psx_mod_register_command(nullptr, command_fill_reply) && !psx_mod_register_command("", command_fill_reply) && !psx_mod_register_command("missing.callback", nullptr) && !psx_mod_register_command(std::string(64, 'a').c_str(), command_fill_reply), "invalid and unreachable names are refused"); check(psx_mod_register_command("qa.owned", command_replacement) && psx_mod_run_command("qa.owned", "", buffer.reply, 8) == 99, "duplicate registration explicitly replaces callback"); const auto maximum = std::string(63, 'b'); check(psx_mod_register_command(maximum.c_str(), command_replacement) == 1, "63-byte command name is reachable"); for (int i = 0; i < 14; ++i) check(psx_mod_register_command(("qa.extra." + std::to_string(i)).c_str(), command_replacement) == 1, "temporary names fill bounded registry"); check(!psx_mod_register_command("qa.overflow", command_replacement) && psx_mod_run_command("qa.extra.13", "", buffer.reply, 8) == 99 && psx_mod_run_command("qa.owned", "", buffer.reply, 8) == 99, "overflow preserves existing owned registrations"); } int main(int argc, char** argv) { test_commands(); if (argc > 1 && std::string(argv[1]) == "--commands-only") { std::cout << "mod command tests: " << failures << " failures\n"; return failures ? 1 : 0; } #if defined(PSX_LAUNCHER_MOD_COMMIT_WORKER_SAFE) const fs::path root = fs::temp_directory_path() / "psxrecomp-mod-runtime-cache-test"; const auto isolated_cache = root / "test-cache"; #if defined(_WIN32) check(_wputenv_s(L"LOCALAPPDATA", isolated_cache.c_str()) == 0, "isolate audited digest receipts from owner cache"); #else check(setenv("XDG_CACHE_HOME", isolated_cache.c_str(), 1) == 0, "isolate audited digest receipts from owner cache"); #endif #if defined(_WIN32) const char* configured_cache = std::getenv("LOCALAPPDATA"); #else const char* configured_cache = std::getenv("XDG_CACHE_HOME"); #endif check(configured_cache && fs::path(configured_cache) == isolated_cache, "CRT getenv observes the isolated audited cache"); #else const fs::path root = fs::temp_directory_path() / "psxrecomp-mod-runtime-test"; #endif std::error_code ec; fs::remove_all(root, ec); const std::vector stock(8 * 2352, 0); std::vector overlay(3000); for (size_t i = 0; i < overlay.size(); ++i) overlay[i] = (uint8_t)(i * 17u + 3u); const fs::path stock_path = root / "stock.bin"; write_bytes(stock_path, stock); write_bytes(root / "audio.bin", std::vector(2 * 2352, 0x5a)); const fs::path cue_path = root / "stock.cue"; write_text(cue_path, "FILE \"stock.bin\" BINARY\n" " TRACK 01 MODE2/2352\n" " INDEX 01 00:00:00\n" "FILE \"audio.bin\" BINARY\n" " TRACK 02 AUDIO\n" " INDEX 01 00:00:00\n"); write_bytes(root / "packages/runtime.test/1.0.0/assets/overlay.bin", overlay); write_text(root / "packages/runtime.test/1.0.0/manifest.toml", "format_version = 5\n" "id = \"runtime.test\"\n" "version = \"1.0.0\"\n" "name = \"Runtime Test\"\n" "[[target]]\n" "game_id = \"SLUS-RUNTIME\"\n" "disc_sha256 = \"" + sha256_hex(stock) + "\"\n" "[[feature]]\n" "id = \"main-code\"\n" "name = \"Main Code\"\n" "[[feature]]\n" "id = \"disc-byte\"\n" "name = \"Disc Byte\"\n" "[[feature]]\n" "id = \"asset-overlay\"\n" "name = \"Asset Overlay\"\n" "[[feature]]\n" "id = \"user-byte\"\n" "name = \"User Byte\"\n" "[[feature]]\n" "id = \"dynamic-main\"\n" "name = \"Dynamic Main\"\n" "[[feature]]\n" "id = \"sparse-main\"\n" "name = \"Sparse Main\"\n" "[[feature]]\n" "id = \"sparse-flag\"\n" "name = \"Sparse Flag\"\n" "[[feature]]\n" "id = \"sparse-disc\"\n" "name = \"Sparse Disc\"\n" "[[feature]]\n" "id = \"vblank-plugin\"\n" "name = \"VBlank Plugin\"\n" "[[option]]\nfeature = \"vblank-plugin\"\nid = \"cameras\"\n" "label = \"Cameras\"\ntype = \"choice\"\ndefault = \"unified\"\n" "[[option.choice]]\nvalue = \"unified\"\nlabel = \"Unified\"\n" "[[option.choice]]\nvalue = \"split\"\nlabel = \"Split\"\n" "[[option]]\n" "feature = \"dynamic-main\"\n" "id = \"count\"\n" "label = \"Count\"\n" "type = \"integer\"\n" "min = 0\n" "max = 254\n" "default = 42\n" "[[option]]\n" "feature = \"sparse-main\"\n" "id = \"frames\"\n" "label = \"Frames\"\n" "type = \"integer\"\n" "min = 0\n" "max = 99\n" "default = 2\n" "[[patch]]\n" "feature = \"main-code\"\n" "target = \"main_exe\"\n" "address = 2147487744\n" "expected = \"01020304\"\n" "replace = \"a1a2a3a4\"\n" "[[patch]]\n" "feature = \"disc-byte\"\n" "target = \"disc_raw\"\n" "offset = 4714\n" "expected = \"aa\"\n" "replace = \"bb\"\n" "[[patch]]\n" "feature = \"user-byte\"\n" "target = \"disc_user\"\n" "offset = 6154\n" "expected = \"cc\"\n" "replace = \"dd\"\n" "[[patch]]\n" "feature = \"dynamic-main\"\n" "target = \"main_exe\"\n" "address = 2147488000\n" "expected = \"0000\"\n" "replace_from = { option = \"count\", encoding = \"u16le\" }\n" "[[patch]]\n" "feature = \"dynamic-main\"\n" "target = \"main_exe\"\n" "address = 2147488002\n" "expected = \"0100\"\n" "replace_from = { option = \"count\", encoding = \"u16le\", addend = 1 }\n" "[[patch]]\n" "feature = \"sparse-main\"\n" "target = \"main_exe\"\n" "address = 2147488256\n" "expected = \"02000132\"\n" "fields = [{ offset = 0, option = \"frames\", encoding = \"u8\" }]\n" "when_integer = { option = \"frames\", op = \"gt\", value = 0 }\n" "[[patch]]\n" "feature = \"sparse-main\"\n" "target = \"main_exe\"\n" "address = 2147488256\n" "expected = \"02000132\"\n" "fields = [{ offset = 0, replace = \"01\" }, " "{ offset = 2, replace = \"00\" }]\n" "when_integer = { option = \"frames\", op = \"eq\", value = 0 }\n" "[[patch]]\n" "feature = \"sparse-flag\"\n" "target = \"main_exe\"\n" "address = 2147488256\n" "expected = \"02000132\"\n" "fields = [{ offset = 1, replace = \"42\" }]\n" "[[patch]]\n" "feature = \"sparse-disc\"\n" "target = \"disc_user\"\n" "offset = 6164\n" "expected = \"11223344\"\n" "fields = [{ offset = 0, replace = \"aa\" }, " "{ offset = 2, replace = \"bb\" }]\n" "[[overlay]]\n" "feature = \"asset-overlay\"\n" "target = \"disc_raw\"\n" "offset = 11408\n" "file = \"assets/overlay.bin\"\n" "sha256 = \"" + sha256_hex(overlay) + "\"\n" "expected_sha256 = \"" + sha256_hex(std::vector(overlay.size(), 0)) + "\"\n" "[[plugin]]\n" "feature = \"vblank-plugin\"\n" "id = \"runtime.test-vblank\"\n" "[[feature]]\n" "id = \"entry-disabled\"\n" "name = \"Disabled Entry\"\n" "[[plugin]]\n" "feature = \"entry-disabled\"\n" "id = \"runtime.test-disabled-entry\"\n"); write_text(root / "state.toml", "format_version = 2\n" "[[package]]\n" "id = \"runtime.test\"\n" "version = \"1.0.0\"\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"main-code\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"disc-byte\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"asset-overlay\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"user-byte\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"dynamic-main\"\n" "enabled = true\n" "[feature.values]\n" "count = 42\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"sparse-main\"\n" "enabled = true\n" "[feature.values]\n" "frames = 0\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"sparse-flag\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"sparse-disc\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"runtime.test\"\n" "id = \"vblank-plugin\"\n" "enabled = true\n"); std::string error; PSXRecompV4::mod_clear_plugins_for_tests(); check(PSXRecompV4::mod_register_activation_plugin( "runtime.test-vblank", test_activation_plugin), "runtime test activation hook must register"); check(PSXRecompV4::mod_register_vblank_plugin( "runtime.test-vblank", test_vblank_plugin), "runtime test plugin must register"); check(psx_mod_register_function_entry_plugin( "runtime.test-vblank", 0x80003000u, test_active_entry) == 1, "active plugin's function-entry hook must register"); check(psx_mod_register_function_entry_plugin( "runtime.test-disabled-entry", 0x80003000u, test_disabled_entry) == 1, "disabled feature's function-entry hook must register"); check(psx_mod_register_function_entry_plugin( "runtime.unselected-entry", 0x80003000u, test_unselected_entry) == 1, "unselected function-entry hook must register"); CPUState entry_cpu{}; check(psx_mod_register_instruction_plugin("runtime.test-vblank", 0x80003004u, 0x90A30014u, test_instruction), "register guarded instruction callback"); check(!psx_mod_register_instruction_plugin("runtime.test-vblank", 0xA0003004u, 0x90A30014u, test_instruction) && !psx_mod_register_instruction_plugin("runtime.other", 0x80003005u, 0x90A30014u, test_instruction), "reject aliased duplicate and unaligned sites"); psx_mod_write_word(0x80003004u, 0x90A30014u); check(psx_mod_register_memory_address_plugin("runtime.test-vblank", 0x80003008u, 0x8CA30014u, test_memory_address), "register effective address callback"); check(!psx_mod_register_memory_address_plugin("runtime.test-vblank", 0xA0003008u, 0x8CA30014u, test_memory_address) && !psx_mod_register_memory_address_plugin("runtime.test-vblank", 0x80003009u, 0x8CA30014u, test_memory_address) && !psx_mod_register_memory_address_plugin("runtime.test-vblank", 0x8000300cu, 0x24020001u, test_memory_address), "reject duplicate, unaligned and non-memory adapters"); check(psx_mod_register_memory_address_plugin("runtime.unselected-entry", 0x80003008u, 0x8CA30014u, test_memory_address), "register inactive adapter"); psx_mod_write_word(0x80003008u, 0x8CA30014u); check(psx_mod_register_guest_function_plugin( "runtime.test-vblank", 0x8FFF0000u, test_guest_function), "trusted callback registers outside the hardware map"); check(!psx_mod_register_guest_function_plugin( "runtime.other", 0xAFFF0000u, test_guest_function) && !psx_mod_register_guest_function_plugin("runtime.other", 0x80003000u, test_guest_function) && !psx_mod_register_guest_function_plugin("runtime.other", 0x8FFF0001u, test_guest_function) && !psx_mod_register_guest_function_plugin("runtime.other", 0xCFFF0000u, test_guest_function), "guest callbacks refuse alias collisions, game addresses, misalignment and KSEG2"); check(psx_mod_register_guest_function_plugin( "runtime.unselected-entry", 0x8FFF0004u, test_unselected_entry), "unselected callback implementation can register"); check(psx_mod_register_savestate_plugin("runtime.test-vblank", test_restore_plugin), "restore callback must register through the C API"); check(!psx_mod_register_savestate_plugin("runtime.test-vblank", test_restore_plugin), "duplicate restore callback must be rejected"); check(PSXRecompV4::mod_runtime_initialize( root, "SLUS-RUNTIME", 0x80002000, {}, &error), error.c_str()); const auto first_commit_timing = launch_timing_total(); check(PSXRecompV4::mod_runtime_commit(cue_path, &error), "CUE and its data-track BIN must have the same mod target identity"); check_commit_timing(first_commit_timing, true, true); /* Committed but not yet activated: no hook may run. */ psx_mod_instruction(&entry_cpu, 0x80003004u, 0x90A30014u); check(instruction_hits == 0 && !g_psx_mod_instruction_hooks, "instruction hooks await activation"); check(psx_mod_memory_address(&entry_cpu, 0x80003008u, 0x8CA30014u, 0x80004000u) == 0x80004000u && !memory_hits, "address adapters await activation"); check(!psx_mod_dispatch_guest_function(&entry_cpu, 0x8FFF0000u) && g_psx_mod_guest_functions == 0, "committed guest functions remain unavailable until activation"); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(g_psx_mod_function_entry_hooks == 0 && active_entry_hits == 0, "function-entry hooks must not run before plugin activation"); psx_host_set_hud_draw_cb(old_session_hud, nullptr); mod_runtime_activate_plugins(); psx_host_hud_draw(64, 64); check(hud_calls == 0, "activation clears the prior session HUD before resetting plugins"); check(activation_calls == 1, "resolved trusted plugin must activate before runtime startup"); entry_cpu.pc = 0x80004000u; const CPUState memory_before = entry_cpu; check(psx_mod_memory_address(&entry_cpu, 0x80003008u, 0x8CA30014u, 0x80004000u) == 0x80005000u && psx_mod_memory_address(&entry_cpu, 0xA0003008u, 0x8CA30014u, 0x80004003u) == 0x80005003u && memory_hits == 2 && !std::memcmp(&entry_cpu, &memory_before, sizeof entry_cpu), "active adapter handles aliases and preserves CPU and merge lanes"); check(psx_mod_memory_address(&entry_cpu, 0x80003008u, 0x8CA30015u, 0x80004000u) == 0x80004000u, "address adapter rejects wrong emitted word"); psx_mod_write_word(0x80003008u, 0x8CA30015u); check(psx_mod_memory_address(&entry_cpu, 0x80003008u, 0x8CA30014u, 0x80004000u) == 0x80004000u && memory_hits == 2, "address adapter rejects changed overlay word"); psx_mod_write_word(0x80003008u, 0x8CA30014u); memory_delta = 1; check(psx_mod_memory_address(&entry_cpu, 0x80003008u, 0x8CA30014u, 0x80004000u) == 0x80004000u, "misaligned adapter result is ignored"); memory_delta = 0x1000u; psx_mod_instruction(&entry_cpu, 0x80003004u, 0x90A30014u); psx_mod_instruction(&entry_cpu, 0xA0003004u, 0x90A30014u); check(instruction_hits == 2 && entry_cpu.gpr[5] == 0x80301008u && entry_cpu.pc == 0x80004000u, "instruction aliases preserve control flow and edit registers"); psx_mod_instruction(&entry_cpu, 0x80003004u, 0x90A30015u); psx_mod_write_word(0x80003004u, 0x90A30015u); psx_mod_instruction(&entry_cpu, 0x80003004u, 0x90A30014u); check(instruction_hits == 2, "fetched and live instruction guards reject changed code"); psx_mod_write_word(0x80003004u, 0x90A30014u); mod_runtime_on_vblank(); check(plugin_calls == 1, "resolved trusted plugin must run on guest VBlank"); check(g_psx_mod_function_entry_hooks == 1, "activation must flatten exactly the active plan's entry hooks"); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(active_entry_hits == 1 && active_entry_last == 0x80003000u, "active plan's function-entry hook must run at its address"); psx_mod_function_entry(&entry_cpu, 0x00003000u); psx_mod_function_entry(&entry_cpu, 0xA0003000u); check(active_entry_hits == 3 && active_entry_last == 0xA0003000u, "function-entry hooks must match every segment alias of the code address"); psx_mod_function_entry(&entry_cpu, 0x80003004u); psx_mod_function_entry(&entry_cpu, 0x80203000u); check(active_entry_hits == 3, "function-entry hooks must not run for other addresses or RAM mirrors"); check(disabled_entry_hits == 0 && unselected_entry_hits == 0, "hooks of plugins the plan does not activate must never run"); /* A replaced plan drops every hook until its own activation. */ check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), error.c_str()); psx_mod_instruction(&entry_cpu, 0x80003004u, 0x90A30014u); check(instruction_hits == 2 && !g_psx_mod_instruction_hooks, "clearing removes instruction hooks"); check(psx_mod_memory_address(&entry_cpu, 0x80003008u, 0x8CA30014u, 0x80004000u) == 0x80004000u, "clearing removes address adapters"); check(!psx_mod_dispatch_guest_function(&entry_cpu, 0x8FFF0000u) && g_psx_mod_guest_functions == 0, "clearing the plan drops guest callback availability"); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(g_psx_mod_function_entry_hooks == 0 && active_entry_hits == 3, "clearing the plan must drop its function-entry hooks"); check(PSXRecompV4::mod_runtime_commit(cue_path, &error), error.c_str()); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(active_entry_hits == 3, "a re-committed plan must not run hooks before activation"); mod_runtime_activate_plugins(); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(active_entry_hits == 4 && disabled_entry_hits == 0 && unselected_entry_hits == 0, "re-activation restores exactly the active plan's hooks"); /* The lobby rematch, in the order main.cpp's start_mod_session() drives * it: a netplay match (plan cleared, then activated) and an offline * rematch that commits and activates exactly like a first boot. */ check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), error.c_str()); mod_runtime_activate_plugins(); mod_runtime_on_vblank(); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(activation_calls == 2 && plugin_calls == 1 && g_psx_mod_function_entry_hooks == 0 && active_entry_hits == 4, "a netplay session must stay vanilla: no activation, VBlank or hook"); check(PSXRecompV4::mod_runtime_commit(cue_path, &error), error.c_str()); mod_runtime_activate_plugins(); check(activation_calls == 3, "an offline rematch after netplay must run activation callbacks"); mod_runtime_on_vblank(); check(plugin_calls == 2, "an offline rematch after netplay must run VBlank plugins"); psx_mod_function_entry(&entry_cpu, 0x80003000u); check(g_psx_mod_function_entry_hooks == 1 && active_entry_hits == 5 && disabled_entry_hits == 0 && unselected_entry_hits == 0, "an offline rematch after netplay must arm exactly its plan's hooks"); check(psx_mod_register_function_filter_plugin( "runtime.test-vblank", 0x80003008u, test_active_filter) == 1, "active plan's return filter must register"); check(psx_mod_register_function_filter_plugin( "runtime.test-vblank", 0xA0003008u, test_active_filter) == 0, "filter aliases must not register duplicate hooks"); check(psx_mod_register_function_filter_plugin( "runtime.test-disabled-entry", 0x80003008u, test_inactive_filter) == 1 && psx_mod_register_function_filter_plugin( "runtime.unselected-entry", 0x80003008u, test_inactive_filter) == 1, "inactive filter implementations must register without running"); entry_cpu.pc = 0x80003008u; entry_cpu.gpr[31] = 0x80004000u; entry_cpu.gpr[2] = 99; check(!psx_mod_function_entry(&entry_cpu, entry_cpu.pc) && active_filter_hits == 0, "new filters must wait for active-plan table rebuild"); mod_runtime_activate_plugins(); check(g_psx_mod_function_entry_hooks == 2 && !psx_mod_function_entry(&entry_cpu, entry_cpu.pc) && active_filter_hits == 1 && filter_context_active && entry_cpu.pc == 0x80003008u && entry_cpu.gpr[2] == 99, "unhandled active filter must preserve native fallthrough"); filter_handles = true; entry_cpu.pc = 0xA0003008u; check(psx_mod_function_entry(&entry_cpu, entry_cpu.pc) == 1 && active_filter_hits == 2 && active_filter_last == 0xA0003008u && entry_cpu.pc == entry_cpu.gpr[31] && entry_cpu.gpr[2] == 42 && inactive_filter_hits == 0 && !psx_mod_function_entry_active(), "handled alias filter must publish return registers and pc=$ra"); check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), error.c_str()); check(!psx_mod_function_entry(&entry_cpu, 0x80003008u) && active_filter_hits == 2 && g_psx_mod_function_entry_hooks == 0, "clearing the active plan must also disarm return filters"); check(PSXRecompV4::mod_runtime_commit(cue_path, &error), error.c_str()); mod_runtime_activate_plugins(); entry_cpu.gpr[31] = 0x80005000u; check(!psx_mod_finish_function(&entry_cpu) && !psx_mod_function_entry(nullptr, 0x80003000u), "completion outside an entry callback or with no CPU is refused"); entry_test_mode = 1; check(psx_mod_function_entry(&entry_cpu, 0x80003000u) == 1 && entry_cpu.pc == 0x80005000u && entry_cpu.gpr[2] == 0x12345678u, "explicit completion returns handled and publishes guest return PC/results"); entry_test_mode = 2; entry_cpu.pc = 0; check(!psx_mod_function_entry(&entry_cpu, 0x80003000u) && entry_cpu.pc == 0 && nested_result == 1 && nested_pc == 0x80004000u, "inner completion cannot finish an ordinary outer callback"); entry_test_mode = 3; check(psx_mod_function_entry(&entry_cpu, 0x80003000u) == 1 && entry_cpu.pc == 0x80005000u && nested_result == 0, "outer completion survives a nested ordinary callback"); entry_test_mode = 4; check(psx_mod_function_entry(&entry_cpu, 0x80003000u) == 1 && entry_cpu.pc == 0x80005000u && entry_cpu.gpr[2] == 0x87654321u, "entry completion survives VBlank callbacks during a native call"); entry_test_mode = 0; entry_test_mode = 5; if (callback_setjmp() == 0) { psx_mod_function_entry(&entry_cpu, 0x80003000u); check(false, "callback must escape without its native cleanup"); } check(psx_mod_function_entry_active(), "escape reproduces the stranded capture gate"); const ModFunctionEntryContext no_callback{}; mod_runtime_function_entry_context_restore(&no_callback); check(!psx_mod_function_entry_active() && !psx_mod_finish_function(&entry_cpu), "scheduler landing releases the gate and clears abandoned completion ownership"); entry_test_mode = 6; check(psx_mod_function_entry(&entry_cpu, 0x80003000u) == 1 && entry_cpu.pc == entry_cpu.gpr[31] && !psx_mod_function_entry_active(), "nested exception landing preserves the live outer callback and completion"); entry_test_mode = 0; entry_cpu.gpr[4] = 35u; const uint32_t guest_sp = entry_cpu.gpr[29]; check(g_psx_mod_guest_functions == 1 && psx_mod_dispatch_guest_function(&entry_cpu, 0x8FFF0000u) && entry_cpu.gpr[2] == 42u && entry_cpu.pc == 0x80005000u && entry_cpu.gpr[29] == guest_sp, "active callback returns its result through normal guest PC/stack state"); check(psx_mod_dispatch_guest_function(&entry_cpu, 0x0FFF0000u) && psx_mod_dispatch_guest_function(&entry_cpu, 0xAFFF0000u) && guest_function_hits == 3, "guest callback aliases retain nested entry and VBlank scope"); check(!psx_mod_dispatch_guest_function(&entry_cpu, 0x8FFF0004u) && !psx_mod_dispatch_guest_function(&entry_cpu, 0x8FFF0001u) && !psx_mod_dispatch_guest_function(&entry_cpu, 0xCFFF0000u) && !psx_mod_dispatch_guest_function(nullptr, 0x8FFF0000u) && unselected_entry_hits == 0, "unselected, unknown, misaligned and invalid callback requests fall through"); ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4; ram[0x1100] = 0; ram[0x1101] = 0; ram[0x1102] = 1; ram[0x1103] = 0; ram[0x1200] = 2; ram[0x1201] = 0; ram[0x1202] = 1; ram[0x1203] = 0x32; mod_runtime_on_dispatch(0x80001000); check(ram[0x1000] == 1, "patch must wait for the configured entry point"); mod_runtime_on_dispatch(0x80002000); check(ram[0x1000] == 0xa1 && ram[0x1003] == 0xa4, "main-EXE patch must apply before entry execution"); check(ram[0x1100] == 42 && ram[0x1101] == 0 && ram[0x1102] == 43 && ram[0x1103] == 0, "dynamic main-EXE patches must encode all sites before entry"); check(ram[0x1200] == 1 && ram[0x1201] == 0x42 && ram[0x1202] == 0 && ram[0x1203] == 0x32, "adjacent sparse fields must compose while preserving guard-only " "bytes"); /* A full-machine savestate replaces main RAM after the entry-point plan * has already applied. Loading a stock checkpoint must not silently turn * the currently enabled main-EXE features back off. */ ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4; ram[0x1100] = 0; ram[0x1101] = 0; ram[0x1102] = 1; ram[0x1103] = 0; ram[0x1200] = 2; ram[0x1201] = 0; ram[0x1202] = 1; ram[0x1203] = 0x32; const int activation_before_restore = activation_calls; const int vblank_before_restore = plugin_calls; mod_runtime_on_savestate_loaded(); check(restore_calls == 1 && activation_calls == activation_before_restore && plugin_calls == vblank_before_restore, "restore must rebind hooks without activation or a gameplay VBlank"); check(ram[0x1000] == 0xa1 && ram[0x1003] == 0xa4 && ram[0x1100] == 42 && ram[0x1102] == 43 && ram[0x1200] == 1 && ram[0x1201] == 0x42 && ram[0x1202] == 0 && ram[0x1203] == 0x32, "savestate restore must reapply the complete enabled main plan"); std::array sector{}; sector[10] = 0xaa; mod_runtime_patch_disc_sector(2, 1, sector.data(), (uint32_t)sector.size()); check(sector[10] == 0xaa, "disc overlay must stay off during reference reads"); mod_runtime_enable_disc_patches(); mod_runtime_patch_disc_sector(2, 1, sector.data(), (uint32_t)sector.size()); check(sector[10] == 0xbb, "raw disc overlay must patch matching sectors"); std::array overlay_sector{}; mod_runtime_patch_disc_sector( 4, 1, overlay_sector.data(), (uint32_t)overlay_sector.size()); check(overlay_sector[1999] == 0 && overlay_sector[2000] == overlay[0] && overlay_sector[2351] == overlay[351], "file overlay must patch the tail of its first sector"); overlay_sector.fill(0); mod_runtime_patch_disc_sector( 5, 1, overlay_sector.data(), (uint32_t)overlay_sector.size()); check(overlay_sector.front() == overlay[352] && overlay_sector.back() == overlay[2703], "file overlay must patch complete middle sectors"); overlay_sector.fill(0); mod_runtime_patch_disc_sector( 6, 1, overlay_sector.data(), (uint32_t)overlay_sector.size()); check(overlay_sector[0] == overlay[2704] && overlay_sector[295] == overlay[2999] && overlay_sector[296] == 0, "file overlay must patch the head of its final sector"); std::array mode2_sector{}; mode2_sector[15] = 2; mode2_sector[18] = 0; mode2_sector[24 + 10] = 0xcc; mode2_sector[24 + 20] = 0x11; mode2_sector[24 + 21] = 0x22; mode2_sector[24 + 22] = 0x33; mode2_sector[24 + 23] = 0x44; mod_runtime_patch_disc_sector( 3, 1, mode2_sector.data(), (uint32_t)mode2_sector.size()); check(mode2_sector[24 + 10] == 0xdd, "disc_user operations must apply to raw Mode2 Form1 user data"); check(mode2_sector[24 + 20] == 0xaa && mode2_sector[24 + 21] == 0x22 && mode2_sector[24 + 22] == 0xbb && mode2_sector[24 + 23] == 0x44, "sparse disc writes must validate a complete guard and modify only " "owned fields"); std::array audio_sector{}; audio_sector[24 + 10] = 0xcc; mod_runtime_patch_disc_sector( 3, 1, audio_sector.data(), (uint32_t)audio_sector.size()); check(audio_sector[24 + 10] == 0xcc, "disc_user operations must not modify CDDA/non-data sectors"); check(PSXRecompV4::mod_runtime_initialize( root, "SLUS-RUNTIME", 0x80002000, {}, &error), error.c_str()); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), error.c_str()); ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4; ram[0x1100] = 0; ram[0x1101] = 0; ram[0x1102] = 2; ram[0x1103] = 0; /* second dynamic guard is wrong */ mod_runtime_on_dispatch(0x80002000); check(ram[0x1000] == 1 && ram[0x1003] == 4 && ram[0x1100] == 0 && ram[0x1101] == 0, "one failed generated guard must leave the complete main plan untouched"); check(PSXRecompV4::mod_runtime_initialize( root, "SLUS-RUNTIME", 0x80002000, {}, &error), error.c_str()); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), error.c_str()); ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4; ram[0x1100] = 0; ram[0x1101] = 0; ram[0x1102] = 1; ram[0x1103] = 0; ram[0x1200] = 2; ram[0x1201] = 0; ram[0x1202] = 1; ram[0x1203] = 0x33; /* guard-only byte is wrong */ mod_runtime_on_dispatch(0x80002000); check(ram[0x1000] == 1 && ram[0x1003] == 4 && ram[0x1200] == 2 && ram[0x1201] == 0 && ram[0x1202] == 1 && ram[0x1203] == 0x33, "a failed sparse guard-only byte must leave the complete main plan " "untouched"); /* Loading can be requested while the boot executable is still running, * before the configured game entry point has dispatched. The restored * checkpoint itself supplies the bytes used to validate and apply the * plan in that case. */ check(PSXRecompV4::mod_runtime_initialize( root, "SLUS-RUNTIME", 0x80002000, {}, &error), error.c_str()); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), error.c_str()); ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4; ram[0x1100] = 0; ram[0x1101] = 0; ram[0x1102] = 1; ram[0x1103] = 0; ram[0x1200] = 2; ram[0x1201] = 0; ram[0x1202] = 1; ram[0x1203] = 0x32; mod_runtime_on_savestate_loaded(); check(ram[0x1000] == 0xa1 && ram[0x1003] == 0xa4 && ram[0x1100] == 42 && ram[0x1102] == 43 && ram[0x1200] == 1 && ram[0x1201] == 0x42 && ram[0x1202] == 0 && ram[0x1203] == 0x32, "pre-entry savestate restore must validate and apply the main plan"); mod_runtime_enable_disc_patches(); std::array bad_sparse_disc{}; bad_sparse_disc[15] = 2; bad_sparse_disc[18] = 0; bad_sparse_disc[24 + 10] = 0xcc; bad_sparse_disc[24 + 20] = 0x11; bad_sparse_disc[24 + 21] = 0x99; /* guard-only byte is wrong */ bad_sparse_disc[24 + 22] = 0x33; bad_sparse_disc[24 + 23] = 0x44; mod_runtime_patch_disc_sector( 3, 1, bad_sparse_disc.data(), (uint32_t)bad_sparse_disc.size()); check(bad_sparse_disc[24 + 10] == 0xcc && bad_sparse_disc[24 + 20] == 0x11 && bad_sparse_disc[24 + 22] == 0x33, "a failed sparse disc guard must leave every write in the sector " "untouched"); /* Implicit requirement through the real commit/activation path: the * hidden required feature's activation plugin runs, the state.toml commit * writes never gains the derived activation (it is not a choice), * and a requirement on an absent package makes the launch commit fail * rather than boot the requiring build without it. */ { const fs::path req_root = root / "requirement-runtime"; write_text(req_root / "bundled/runtime.ram/1.0.0/manifest.toml", "format_version = 5\n" "id = \"runtime.ram\"\n" "version = \"1.0.0\"\n" "name = \"Runtime RAM\"\n" "[[target]]\n" "game_id = \"*\"\n" "[[feature]]\n" "id = \"big-ram\"\n" "name = \"Big RAM\"\n" "hidden = true\n" "[[option]]\n" "feature = \"big-ram\"\n" "id = \"size\"\n" "label = \"Size\"\n" "type = \"choice\"\n" "default = \"eight\"\n" "[[option.choice]]\nvalue = \"eight\"\nlabel = \"8 MB\"\n" "[[plugin]]\n" "feature = \"big-ram\"\n" "id = \"runtime.big-ram\"\n"); write_text(req_root / "bundled/runtime.mode/1.0.0/manifest.toml", "format_version = 7\n" "id = \"runtime.mode\"\n" "version = \"1.0.0\"\n" "name = \"Runtime Mode\"\n" "[[target]]\n" "game_id = \"SLUS-REQ\"\n" "[[feature]]\n" "id = \"mode\"\n" "name = \"Mode\"\n" "[[option]]\n" "feature = \"mode\"\n" "id = \"extras\"\n" "label = \"Extras\"\n" "type = \"choice\"\n" "default = \"none\"\n" "[[option.choice]]\nvalue = \"none\"\nlabel = \"None\"\n" "[[option.choice]]\nvalue = \"full\"\nlabel = \"Full\"\n" "[[requirement]]\n" "feature = \"mode\"\n" "package = \"runtime.ram\"\n" "requires_feature = \"big-ram\"\n" "when = { extras = \"full\" }\n"); const std::string state_text = "format_version = 2\n" "\n[[feature]]\npackage_id = \"runtime.mode\"\nid = \"mode\"\n" "enabled = true\n" "[feature.values]\nextras = \"full\"\n"; write_text(req_root / "state.toml", state_text); const auto read_state = [&]() { std::ifstream in(req_root / "state.toml", std::ios::binary); return std::string((std::istreambuf_iterator(in)), std::istreambuf_iterator()); }; big_ram_activations = 0; check(PSXRecompV4::mod_register_activation_plugin( "runtime.big-ram", test_big_ram_activation), "big-ram activation hook must register"); check(PSXRecompV4::mod_runtime_initialize( req_root, "SLUS-REQ", 0x80002000, {}, &error), error.c_str()); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), "a satisfied implicit requirement must commit"); mod_runtime_activate_plugins(); check(big_ram_activations == 1, "the implicitly required feature's plugin must activate"); char value[32]; check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && std::string(value) == "eight", "an implicit feature's plugin must read its option values"); /* In-game overlay P5: a live option change reaches the plugin and * every later read, and is refused while netplay owns the plan. */ check(psx_mod_set_option_live("runtime.ram", "big-ram", "size", "four") == 0, "without an option-changed callback a live set waits for restart"); check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && std::string(value) == "eight", "a restart-only change must not reach the running plugin"); check(!psx_mod_option_live_capable("runtime.ram", "big-ram"), "no callback yet: not live-capable"); check(psx_mod_register_option_changed_plugin("runtime.big-ram", test_option_changed), "option-changed hook must register"); check(psx_mod_option_live_capable("runtime.ram", "big-ram"), "a registered callback makes the feature live-capable"); check(psx_mod_set_option_live("runtime.ram", "big-ram", "size", "four") == 1 && option_changed_hits == 1 && option_changed_value == "four", "the plugin must see the change in its own context"); check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && std::string(value) == "four", "a live override must answer later reads"); /* A callback that refuses ("nothing changes until next start") must * leave the value the session runs with untouched. */ option_changed_refuse = 1; check(psx_mod_set_option_live("runtime.ram", "big-ram", "size", "two") == 0 && option_changed_hits == 2, "a refused live set reports refused after asking the plugin"); check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && std::string(value) == "four", "a refused live set must not change the value later reads see"); option_changed_refuse = 0; /* A new session (initialise + commit) answers its own committed * plan, not the previous session's live override. */ check(PSXRecompV4::mod_runtime_initialize( req_root, "SLUS-REQ", 0x80002000, {}, &error), error.c_str()); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), "the second session must commit"); mod_runtime_activate_plugins(); check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && std::string(value) == "eight", "a live override must not outlive its session's plan"); check(psx_mod_set_option_live("runtime.ram", "no-such-feature", "size", "x") == 0, "features outside the plan are not live-settable"); check(psx_mod_save_selection() == 1, "saving the selection mid-session must succeed"); test_netplay_active = 1; check(psx_mod_set_option_live("runtime.ram", "big-ram", "size", "four") == -1, "netplay must refuse live option changes"); test_netplay_active = 0; /* commit rewrites state.toml in canonical form; what matters is that * the player's choice survives and the derived one is never added. */ const std::string after = read_state(); check(after.find("runtime.ram") == std::string::npos && after.find("extras = \"full\"") != std::string::npos, "committing must not write the derived activation to state.toml"); /* Condition false: the required plugin stays off. */ write_text(req_root / "state.toml", "format_version = 2\n" "\n[[feature]]\npackage_id = \"runtime.mode\"\nid = \"mode\"\n" "enabled = true\n" "[feature.values]\nextras = \"none\"\n"); big_ram_activations = 0; check(PSXRecompV4::mod_runtime_initialize( req_root, "SLUS-REQ", 0x80002000, {}, &error), error.c_str()); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), error.c_str()); mod_runtime_activate_plugins(); check(big_ram_activations == 0, "a requirement whose condition is false must not activate"); /* Required package absent (e.g. the title excluded the builtin). */ write_text(req_root / "state.toml", state_text); fs::remove_all(req_root / "bundled/runtime.ram", ec); check(PSXRecompV4::mod_runtime_initialize( req_root, "SLUS-REQ", 0x80002000, {}, &error), error.c_str()); std::string commit_error; check(!PSXRecompV4::mod_runtime_commit(stock_path, &commit_error) && commit_error.find("runtime.mode/mode requires " "runtime.ram/big-ram") != std::string::npos, "an unmet requirement must fail the launch commit loudly"); } /* * Display geometry passthrough. Ape Escape scans out 384 while its mode * width is 368, which is precisely why a plugin cannot derive this from * GPUSTAT: the horizontal range that makes the difference is write-only. */ g_test_display.width = 384; g_test_display.height = 240; check(psx_mod_display_width() == 384u, "psx_mod_display_width must report the presenter's visible width, " "not the coarse mode width"); check(psx_mod_display_height() == 240u, "psx_mod_display_height must report the presenter's visible height"); g_test_display.width = 0; g_test_display.height = 0; check(psx_mod_display_width() == 0u && psx_mod_display_height() == 0u, "unestablished display geometry must report zero so callers skip " "drawing instead of guessing"); /* Source-owned ISO: nested file spanning two sectors, without a derived * disc. Host reads must choose the original mount and leave sizes honest. */ std::vector iso(24*2048); auto le32 = [&](size_t at,uint32_t n) { for(unsigned i=0;i<4;++i) iso[at+i]=(uint8_t)(n>>(i*8)); }; auto record = [&](size_t at,uint32_t lba,uint32_t bytes,bool directory,const std::string& name) { iso[at]=(uint8_t)((33+name.size()+1)&~size_t(1)); le32(at+2,lba); le32(at+10,bytes); iso[at+25]=directory?2:0; iso[at+28]=1;iso[at+31]=1;iso[at+32]=(uint8_t)name.size(); std::copy(name.begin(),name.end(),iso.begin()+at+33); }; iso[16*2048]=1;std::copy_n("CD001",5,iso.begin()+16*2048+1);iso[16*2048+6]=1; record(16*2048+156,20,2048,true,std::string(1,'\0')); record(20*2048,21,2048,true,"S0"); record(21*2048,22,3000,false,"LEVEL.NSF;1"); for(unsigned i=0;i<3000;++i)iso[22*2048+i]=(uint8_t)(i*7); const auto disc_root=root/"host-reader"; const auto iso_path=disc_root/"original.iso"; write_bytes(iso_path,iso); check(PSXRecompV4::mod_runtime_initialize(disc_root,"READER",0,{},&error),"reader initialize"); check(PSXRecompV4::mod_runtime_commit(iso_path,&error),"reader mount original ISO"); uint32_t bytes=0; static unsigned host_read_progress = 0; mod_runtime_set_progress_hook([] { ++host_read_progress; }); check(psx_mod_read_disc_file("S0/LEVEL.NSF",nullptr,0,&bytes) && bytes==3000,"query original nested file size"); std::vector result(3000); check(!psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),2999,&bytes) && bytes==0,"undersized destination rejected"); check(host_read_progress == 0, "size query and rejected destination do not report reading"); check(psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),(uint32_t)result.size(),&bytes) && bytes==3000 && std::equal(result.begin(),result.end(),iso.begin()+22*2048),"complete original file bytes"); check(host_read_progress >= 2, "whole-file plugin reads report progress and completion"); mod_runtime_set_progress_hook(nullptr); check(!psx_mod_read_disc_file("S0/MISSING.NSF",nullptr,0,&bytes) && bytes==0,"missing file explicit"); check(!psx_mod_read_disc_file("S0",nullptr,0,&bytes),"directory rejected"); { int ready = 99; psx_mod_set_disc_request_provider(test_disc_request); check(mod_runtime_disc_request(&ready) == 0 && ready == 0, "disc provider registration outside activation is ignored"); const auto request_root = disc_root / "request-provider"; write_text(request_root / "bundled/disc.request/1.0.0/manifest.toml", "format_version = 5\nid = \"disc.request\"\nversion = \"1.0.0\"\nname = \"Disc requests\"\n" "[[target]]\ngame_id = \"READER\"\n" "[[feature]]\nid = \"auto\"\nname = \"Auto\"\ndefault_enabled = true\n" "[[plugin]]\nfeature = \"auto\"\nid = \"test.disc-request\"\n"); check(psx_mod_register_activation_plugin("test.disc-request", test_disc_request_activate), "register disc request fixture"); check(PSXRecompV4::mod_runtime_initialize(request_root, "READER", 0, {}, &error) && PSXRecompV4::mod_runtime_commit(iso_path, &error), "commit disc request plan"); mod_runtime_activate_plugins(); test_game_started = 0; check(mod_runtime_disc_request(&ready) == 0 && ready == 0 && disc_request_calls == 0, "boot does not invoke automatic disc request callbacks"); test_game_started = 1; test_netplay_active = 1; check(mod_runtime_disc_request(&ready) == 0 && ready == 0 && disc_request_calls == 0, "online mode does not invoke automatic disc request callbacks"); test_netplay_active = 0; check(mod_runtime_disc_request(&ready) == 2 && ready == 1 && disc_request_calls == 1, "active offline plugin supplies a disc request"); check(PSXRecompV4::mod_runtime_commit(iso_path, &error) && mod_runtime_disc_request(&ready) == 0 && ready == 0, "a new plan clears the previous activation's disc provider"); check(PSXRecompV4::mod_runtime_initialize(disc_root, "READER", 0, {}, &error) && PSXRecompV4::mod_runtime_commit(iso_path, &error), "restore reader plan"); } { // Swapping media must change both host lookup and bytes. An empty // drive must fail instead of quietly reading the boot image. const auto other_path = disc_root / "other.iso"; auto other_iso = iso; other_iso.resize(25 * 2048); other_iso[21 * 2048 + 2] = 23; std::fill_n(other_iso.begin() + 23 * 2048, 3000, uint8_t(0x65)); write_bytes(other_path, other_iso); static std::string mounted_path; static int mounted_index = 1; mounted_path = iso_path.string(); mod_runtime_set_mounted_disc_resolver( [] { return mounted_path.c_str(); }, [] { return mounted_index; }); check(psx_mod_mounted_disc() == 1, "mounted query follows host roster"); mounted_path = other_path.string(); mounted_index = 2; uint32_t mounted_lba = 0; check(psx_mod_mounted_disc() == 2 && psx_mod_disc_file_extent("S0/LEVEL.NSF", &mounted_lba, &bytes) && mounted_lba == 23 && bytes == 3000, "host extent follows changed disc"); check(psx_mod_read_disc_file("S0/LEVEL.NSF", result.data(), 3000, &bytes) && std::all_of(result.begin(), result.end(), [](auto b) { return b == 0x65; }), "host file bytes follow changed disc"); std::vector padded; check(PSXRecompV4::mod_runtime_read_disc_file_sectors("S0/LEVEL.NSF", 4000, padded, mounted_lba, bytes, &error) && mounted_lba == 23 && padded.size() == 4096 && padded[2999] == 0x65, "resident sector reads follow changed disc"); mounted_path.clear(); mounted_index = 0; check(!psx_mod_read_disc_file("S0/LEVEL.NSF", nullptr, 0, &bytes) && bytes == 0, "empty drive refuses boot-disc fallback"); mod_runtime_set_mounted_disc_resolver(nullptr, nullptr); check(psx_mod_mounted_disc() == 0 && psx_mod_read_disc_file("S0/LEVEL.NSF", result.data(), 3000, &bytes) && std::equal(result.begin(), result.end(), iso.begin() + 22 * 2048), "cleared resolver restores single-disc host reads"); } std::vector raw_iso(24*2352); for(unsigned i=0;i<24;++i) { /* A raw sector starts with the CD sync pattern; geometry is read from * it, never from the file length. */ raw_iso[i*2352]=0; std::fill_n(raw_iso.begin()+i*2352+1,10,(uint8_t)0xFF); raw_iso[i*2352+11]=0; raw_iso[i*2352+15]=2; std::copy_n(iso.begin()+i*2048,2048,raw_iso.begin()+i*2352+24); } const auto raw_path=disc_root/"original.bin"; write_bytes(raw_path,raw_iso); check(PSXRecompV4::mod_runtime_commit(raw_path,&error),"reader mount raw disc"); check(psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),(uint32_t)result.size(),&bytes) && std::equal(result.begin(),result.end(),iso.begin()+22*2048),"raw and ISO reads identical"); { const auto folder=root/"prepared-overlay"; const auto pack_path=folder/"local.pack"; std::vector pack(3016,0x42),payload(3000); for(size_t i=0;i expected(raw_iso.begin()+offset,raw_iso.begin()+offset+payload.size()); check(PSXRecompV4::mod_register_media_preparer("test.overlay.prepare", [&](const PSXRecompV4::ModPrepareContext& context,std::map& outputs,std::string&) { if(context.progress && !context.progress(0.5f,"Building local pack"))return false; write_bytes(pack_path,pack);outputs["pack"]=pack_path;return true; }),"register prepared overlay fixture"); write_text(folder/"bundled/prepared.overlay/1.0.0/manifest.toml", "format_version = 10\nid = \"prepared.overlay\"\nversion = \"1.0.0\"\nname = \"Prepared overlay\"\nprepare = \"test.overlay.prepare\"\nsave_compatibility = \"isolated\"\n" "[[target]]\ngame_id = \"READER\"\ndisc_sha256 = \""+sha256_hex(raw_iso)+"\"\n" "[[feature]]\nid = \"audio\"\nname = \"Audio\"\ndefault_enabled = true\n" "[[resource]]\nfeature = \"audio\"\nid = \"pack\"\nlabel = \"Pack\"\nformat = \"file\"\nhidden = true\nrequired = true\nsize = 3016\nsha256 = \""+sha256_hex(pack)+"\"\n" "[[overlay]]\nfeature = \"audio\"\ntarget = \"disc_raw\"\noffset = "+std::to_string(offset)+"\n" "resource = \"pack\"\nresource_offset = 16\nsize = 3000\nsha256 = \""+sha256_hex(payload)+"\"\nexpected_sha256 = \""+sha256_hex(expected)+"\"\n"); check(PSXRecompV4::mod_runtime_initialize(folder,"READER",0,{},&error),"initialize prepared overlay"); int reports=0; check(!PSXRecompV4::mod_runtime_prepare_for_launcher(raw_path,&error, [&](float fraction,const std::string&){++reports;return fraction<1.0f;}),"cancel before installing prepared plan"); check(reports>1 && error=="Preparation canceled.","runtime propagates cooperative cancellation"); check(PSXRecompV4::mod_runtime_initialize(folder,"READER",0,{},&error),"cold offer overlay initialize"); std::string save_scope; check(!PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope.empty(), "uncommitted isolated plan cannot fall back to shared save states"); check(PSXRecompV4::mod_runtime_prepare_resources(raw_path,&error),"cold offer prepares exact-disc resources without commit"); check(!PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error), "resource preparation does not install a save-state scope"); const auto offer_fp=PSXRecompV4::mod_runtime_plan_fingerprint_portable(); check(offer_fp.size()==64 && PSXRecompV4::mod_runtime_fingerprint().empty(),"cold offer qualifies disc without installing a plan"); check(PSXRecompV4::mod_runtime_initialize(folder,"READER",0,{},&error),"cold LAN overlay initialize"); test_match_caps={}; check(PSXRecompV4::mod_runtime_commit_for_direct_netplay(raw_path,&error),"cold LAN accepts exact-disc resource overlay"); check(PSXRecompV4::mod_runtime_session_plan_fp()==offer_fp && PSXRecompV4::mod_runtime_plan_fingerprint_portable()==offer_fp && PSXRecompV4::mod_runtime_fingerprint()==offer_fp,"offer and committed exact-disc identities agree"); check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope==offer_fp, "direct netplay scopes isolated save states by its installed verified plan"); PSXRecompV4::mod_runtime_end_netplay(); check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope.empty(), "intentional netplay clear retires isolated save scope"); const auto changed_disc=folder/"offered.bin"; write_bytes(changed_disc,raw_iso); check(PSXRecompV4::mod_runtime_initialize(folder,"READER",0,{},&error),"changed offer overlay initialize"); check(PSXRecompV4::mod_runtime_prepare_resources(changed_disc,&error),"prepare source before same-path mutation"); auto changed_bytes=raw_iso;changed_bytes[10]^=1; // Outside every stock overlay guard range. write_bytes(changed_disc,changed_bytes); check(!PSXRecompV4::mod_runtime_commit_for_direct_netplay(changed_disc,&error),"final LAN commit rejects same-path changed source identity"); check(PSXRecompV4::mod_runtime_session_plan_fp().empty(),"changed offer cannot install session identity"); check(!PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error), "failed required plan cannot select shared save-state slots"); check(PSXRecompV4::mod_runtime_initialize(folder,"READER",0,{},&error),"restore original overlay selection"); check(PSXRecompV4::mod_runtime_prepare_for_launcher(raw_path,&error),"prepare verified resource-backed overlay"); check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope==offer_fp, "offline isolated plan restores the same verified save-state scope"); mod_runtime_enable_disc_patches(); fs::remove(pack_path,ec); // Committed bytes must remain immutable and resident. check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope==offer_fp, "isolated scope uses committed immutable content after source removal"); for(unsigned lba=21;lba<=23;++lba) { std::array sector; std::copy_n(raw_iso.begin()+lba*2352,2352,sector.begin()); auto wanted=sector; const uint64_t start=std::max(offset,lba*2352); const uint64_t end=std::min(offset+payload.size(),(lba+1)*2352); std::copy(payload.begin()+start-offset,payload.begin()+end-offset,wanted.begin()+start-lba*2352); mod_runtime_patch_disc_sector(lba,1,sector.data(),sector.size()); check(sector==wanted,"resource overlay crosses sectors with exact untouched surroundings"); } } /* Verified bytes are scoped to the committed feature, and do not depend * on reopening an owner file after launch. No launcher is needed here. */ const auto media_root = root / "verified-media"; std::vector rom(64, 0); rom[0] = 0x80; rom[1] = 0x37; rom[2] = 0x12; rom[3] = 0x40; rom[32] = 0x5a; const auto rom_path = media_root / "owner.z64"; write_bytes(rom_path, rom); std::vector xa(2336*2); xa[2]=xa[6]=0x64;xa[8]=0x52; xa[2336+2]=xa[2336+6]=0xE4;xa[2336+8]=0x63; const auto xa_path=media_root/"owner.xa";write_bytes(xa_path,xa); write_text(media_root / "packages/media.test/1.0.0/manifest.toml", "format_version = 8\nid = \"media.test\"\nversion = \"1.0.0\"\nname = \"Media Test\"\n" "[[target]]\ngame_id = \"READER\"\n" "[[feature]]\nid = \"active\"\nname = \"Active\"\ndefault_enabled = true\n" "[[feature]]\nid = \"private\"\nname = \"Private\"\n" "[[resource]]\nfeature = \"active\"\nid = \"rom\"\nlabel = \"ROM\"\n" "format = \"n64-rom\"\nrequired = true\nsize = 64\nsha256 = \"" + sha256_hex(rom) + "\"\n" "[[resource]]\nfeature = \"active\"\nid = \"xa\"\nlabel = \"XA\"\n" "format = \"file\"\nrequired = true\nsize = 4672\nsha256 = \"" + sha256_hex(xa) + "\"\n" "[[resource]]\nfeature = \"private\"\nid = \"private\"\nlabel = \"Private\"\n" "format = \"n64-rom\"\nrequired = true\nsize = 64\nsha256 = \"" + sha256_hex(rom) + "\"\n" "[[plugin]]\nfeature = \"active\"\nid = \"media.test.plugin\"\n"); write_text(media_root / "state.toml", "format_version = 2\n[[feature]]\npackage_id = \"media.test\"\nid = \"active\"\nenabled = true\n" "[feature.resources]\nrom = \"" + rom_path.generic_string() + "\"\nxa = \"" + xa_path.generic_string() + "\"\n"); check(psx_mod_register_activation_plugin("media.test.plugin", test_media_plugin), "register media consumer"); check(PSXRecompV4::mod_runtime_initialize(media_root, "READER", 0, {}, &error), "media initialize"); check(PSXRecompV4::mod_runtime_commit(iso_path, &error), "verify and commit owner media"); { std::string save_scope; check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope.empty(), "shared packages retain historical save-state paths"); const PSXModImmediateSite site = {0x80020000u, 0x28420020u, 1u, 2u, {64, 128}}; psx_mod_immediate_set_sites(&site, 1); check(psx_mod_register_session_reset_plugin("immediate.test.reset",test_immediate_reset), "register reset-boundary probe"); immediate_activation_choice = 1; psx_mod_immediate_begin_session(0); mod_runtime_activate_plugins(); check(!immediate_reset_accepted && psx_mod_immediate_select(1)==1, "cleanup cannot select; active plugin selects64"); std::string scope64, scope128; check(PSXRecompV4::mod_runtime_savestate_scope(scope64,&error) && scope64.size()==64, "non-stock selectors isolate even shared-package savestates"); check(!psx_mod_set_immediate_selector(1, 2), "live selection refused"); check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope==scope64, "failed live selection preserves current state compatibility"); immediate_activation_choice = 2; psx_mod_immediate_begin_session(0); mod_runtime_activate_plugins(); check(psx_mod_immediate_select(1)==2, "next boot selects128"); check(PSXRecompV4::mod_runtime_savestate_scope(scope128,&error) && scope128.size()==64 && scope128!=scope64, "128-slot session cannot read64-slot state namespace"); psx_mod_immediate_begin_session(1); mod_runtime_activate_plugins(); check(!psx_mod_immediate_session_valid() && psx_mod_immediate_select(1)==0, "online activation cannot enable selectors even if plugin ignores refusal"); immediate_activation_choice = 0; psx_mod_immediate_begin_session(0); mod_runtime_activate_plugins(); check(PSXRecompV4::mod_runtime_savestate_scope(save_scope,&error) && save_scope.empty(), "stock selectors restore historical paths"); psx_mod_immediate_reset(); psx_mod_immediate_set_sites(nullptr, 0); } const std::string host_media_fp = PSXRecompV4::mod_runtime_plan_fingerprint_portable(); check(host_media_fp.size() == 64, "verified external media has a portable netplay identity"); test_match_caps = {}; check(PSXRecompV4::mod_runtime_commit_for_direct_netplay(iso_path, &error), "LAN retains verified selected content"); check(PSXRecompV4::mod_runtime_session_plan_fp() == host_media_fp, "LAN announces full portable plan identity"); PSXRecompV4::mod_runtime_end_netplay(); check(PSXRecompV4::mod_runtime_session_plan_fp().empty(), "LAN return retires session identity"); const auto guest_rom_path = media_root / "different-machine" / "guest.z64"; write_bytes(guest_rom_path, rom); const std::string guest_state = "format_version = 2\n[[feature]]\npackage_id = \"media.test\"\nid = \"active\"\nenabled = false\n" "[feature.resources]\nrom = \"" + guest_rom_path.generic_string() + "\"\nxa = \"" + xa_path.generic_string() + "\"\n"; write_text(media_root / "state.toml", guest_state); auto read_guest_state = [&]() { std::ifstream in(media_root / "state.toml", std::ios::binary); return std::string(std::istreambuf_iterator(in), {}); }; const std::string guest_state_bytes = read_guest_state(); check(PSXRecompV4::mod_runtime_initialize(media_root, "READER", 0, {}, &error), "guest media initialize"); test_match_caps.valid = 1; test_match_caps.mod_count = 1; std::snprintf(test_match_caps.mods[0].id, sizeof(test_match_caps.mods[0].id), "media.test"); std::snprintf(test_match_caps.mods[0].ver, sizeof(test_match_caps.mods[0].ver), "1.0.0"); std::snprintf(test_match_caps.mods[0].feats, sizeof(test_match_caps.mods[0].feats), "active"); std::snprintf(test_match_caps.mod_plan_fp, sizeof(test_match_caps.mod_plan_fp), "%s", host_media_fp.c_str()); check(PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "host plan enables guest media with different local paths"); check(PSXRecompV4::mod_runtime_plan_fingerprint_portable() == host_media_fp, "guest content identity matches host"); check(read_guest_state() == guest_state_bytes, "online plan preserves offline selection and resource paths byte for byte"); const std::string committed_fp = PSXRecompV4::mod_runtime_fingerprint(); test_match_caps.mod_plan_fp[0] = host_media_fp[0] == 'a' ? 'b' : 'a'; check(!PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "mismatched host content fingerprint refused"); check(PSXRecompV4::mod_runtime_fingerprint() == committed_fp, "mismatch leaves previous committed plan intact"); test_match_caps.mod_plan_fp[0] = '\0'; check(!PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "missing host fingerprint refused"); std::snprintf(test_match_caps.mod_plan_fp, sizeof(test_match_caps.mod_plan_fp), "%s", host_media_fp.c_str()); auto bad_rom = rom; bad_rom[33] ^= 1; write_bytes(guest_rom_path, bad_rom); check(!PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "changed donor media refused before online launch"); check(read_guest_state() == guest_state_bytes, "failed online launch leaves offline state untouched"); write_bytes(guest_rom_path, rom); check(PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "valid donor media recovers after refused launch"); const uint8_t* unavailable = nullptr; uint64_t unavailable_size = 0; check(!psx_mod_current_resource_bytes("rom", &unavailable, &unavailable_size), "bytes unavailable outside plugin callback"); mod_runtime_activate_plugins(); check(media_access && media_size == rom.size() && std::equal(rom.begin(), rom.end(), media_snapshot), "plugin receives exact verified bytes"); check(!foreign_media_access, "inactive feature cannot supply bytes to another feature"); check(extent_ok && mounted_lba==24 && !invalid_extent && !foreign_extent,"activation mounts only owned bounded resource extents"); check(audio_ok && audio_lba==150 && audio_lba2==151 && !audio_invalid && !audio_foreign && audio_data_rejected,"audio playlist validates bounds, ownership and audio source kind"); uint32_t bad_lba=1; check(!psx_mod_append_disc_extent("xa",0,1,&bad_lba)&&!bad_lba,"late mounting refused"); void* mounted=iso_open(iso_path.string().c_str()); check(mounted&&iso_sector_count(mounted)==24,"BIOS sees original disc before activation is enabled"); check(iso_cdda_track_count(mounted)==1,"audio override is invisible during BIOS boot"); mod_runtime_enable_disc_patches(); check(iso_cdda_track_count(mounted)==3 && iso_cdda_track_start_lba(mounted,2)==150 && iso_cdda_track_start_lba(mounted,3)==151 && iso_cdda_sector_count(mounted)==152 && !iso_cdda_track_is_audio(mounted,1) && iso_cdda_track_is_audio(mounted,3) && !iso_cdda_track_is_audio(mounted,4),"audio-only TOC and lead-out stay separate from XA extents"); check(iso_read_cdda_sector(mounted,150,sector.data(),sector.size()) && std::equal(sector.begin(),sector.begin()+2352,xa.begin()),"lossless external CD audio sector"); check(iso_read_cdda_sector(mounted,151,sector.data(),sector.size()) && std::equal(sector.begin(),sector.begin()+2352,xa.begin()+2320),"audio track boundary resolves next resource slice"); check(!iso_read_cdda_sector(mounted,152,sector.data(),sector.size()) && !iso_read_cdda_sector(mounted,149,sector.data(),sector.size()) && !iso_read_cdda_sector(mounted,150,sector.data(),2351),"audio end, data placeholder and short buffer rejected"); std::array subq{};int valid=0; check(iso_sector_count(mounted)==26&&iso_track_count(mounted)==2&& iso_track_start_lba(mounted,2)==24&&!iso_track_is_audio(mounted,2),"appended extents expose one data track and leadout"); check(iso_read_raw_sector(mounted,24,sector.data(),sector.size())&§or[12]==0&§or[13]==2&§or[14]==0x24&§or[15]==2&& std::equal(xa.begin(),xa.begin()+2336,sector.begin()+16),"native raw sector header and exact XA subheader/audio bytes"); check(iso_read_sector(mounted,25,sector.data(),sector.size())&§or[0]==0x63,"cooked access resolves the same extent"); check(!iso_read_sector(mounted,26,sector.data(),sector.size())&& !iso_read_raw_sector(mounted,24,sector.data(),2351),"extent end and short raw buffers rejected"); check(iso_read_subq(mounted,24,subq.data(),subq.size(),&valid)&&valid&&subq[0]==0x41&&subq[1]==2&&subq[5]==0,"appended data track has native subchannel position"); check(iso_read_sector(mounted,22,sector.data(),sector.size())&§or[1]==7,"base sectors unchanged"); xa[8]=0xff;write_bytes(xa_path,xa); check(iso_read_raw_sector(mounted,24,sector.data(),sector.size())&§or[24]==0x52,"streaming uses verified snapshot after external file changes"); mod_runtime_activate_plugins(); check(mounted_lba==24&&iso_sector_count(mounted)==26,"reactivation has stable LBAs and no duplicate extents"); check(iso_cdda_track_count(mounted)==3 && iso_cdda_sector_count(mounted)==152 && iso_read_cdda_sector(mounted,150,sector.data(),sector.size()) && sector[8]==0x52, "reactivation retains audio identity and immutable snapshot"); rom[32] ^= 1; write_bytes(guest_rom_path, rom); check(media_snapshot && media_snapshot[32] == 0x5a, "committed bytes survive owner file changes"); check(!PSXRecompV4::mod_runtime_commit({}, &error), "new commit rejects modified media"); check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), "netplay clears donor plan"); check(iso_sector_count(mounted)==24&&iso_track_count(mounted)==1&& !iso_read_raw_sector(mounted,24,sector.data(),sector.size()),"clearing plan removes all donor sectors and restores TOC"); check(iso_cdda_track_count(mounted)==1 && iso_cdda_sector_count(mounted)==24, "clearing plan also restores original audio TOC"); iso_close(mounted); PSXRecompV4::mod_runtime_end_netplay(); check(PSXRecompV4::mod_runtime_session_plan_fp().empty(), "leaving online clears host fingerprint"); check(PSXRecompV4::mod_runtime_commit(iso_path, &error), "offline commit after netplay uses original disabled media selection"); mod_runtime_activate_plugins(); const uint8_t* ended_bytes = nullptr; uint64_t ended_size = 0; check(!psx_mod_current_resource_bytes("rom", &ended_bytes, &ended_size), "online media is retired after session"); { PSXRecompV4::ModPackageManager offline; offline.set_root(media_root); check(offline.scan(&error) && offline.load_state(&error) && !offline.feature_enabled("media.test", "active"), "later offline save keeps original disabled feature rather than host selection"); } /* Path-only files are still usable offline, but never portable online. */ const auto loose_root = root / "unverified-media"; write_text(loose_root / "packages/loose.test/1.0.0/manifest.toml", "format_version = 8\nid = \"loose.test\"\nversion = \"1.0.0\"\nname = \"Loose\"\n" "[[target]]\ngame_id = \"READER\"\n[[feature]]\nid = \"active\"\nname = \"Active\"\ndefault_enabled = true\n" "[[resource]]\nfeature = \"active\"\nid = \"file\"\nlabel = \"File\"\nformat = \"file\"\nrequired = true\n"); write_text(loose_root / "state.toml", "format_version = 2\n[[feature]]\npackage_id = \"loose.test\"\nid = \"active\"\nenabled = true\n" "[feature.resources]\nfile = \"" + rom_path.generic_string() + "\"\n"); check(PSXRecompV4::mod_runtime_initialize(loose_root, "READER", 0, {}, &error), "loose resource initialize"); check(PSXRecompV4::mod_runtime_commit(iso_path, &error), "unverified file remains usable offline"); check(PSXRecompV4::mod_runtime_plan_fingerprint_portable().empty(), "unverified resource has no online fingerprint"); const auto rejected_commit_timing = launch_timing_total(); check(!PSXRecompV4::mod_runtime_commit(iso_path, &error, false), "online commit rejects unverified resource"); // Netplay remeasures the source in both worker and legacy configurations. check_commit_timing(rejected_commit_timing, false, true); check(!PSXRecompV4::mod_runtime_commit_for_direct_netplay(iso_path, &error), "LAN also refuses unverified resources"); test_match_caps = {}; test_match_caps.valid = 1; check(PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "explicit empty host plan remains vanilla"); // A guest may own the source without ever enabling/preparing the host's // feature offline. Preparation must precede the portable hash comparison. { const auto prepared_root = root / "prepared-netplay"; const auto prepared_asset = prepared_root / "generated" / "rom.z64"; int prepare_calls = 0; check(PSXRecompV4::mod_register_media_preparer("test.netplay.prepare", [&](const PSXRecompV4::ModPrepareContext&, std::map& output, std::string&) { ++prepare_calls; write_bytes(prepared_asset, rom); output["rom"] = prepared_asset; return true; }), "register netplay preparation fixture"); write_text(prepared_root / "packages/prepared.test/1.0.0/manifest.toml", "format_version = 9\nid = \"prepared.test\"\nversion = \"1.0.0\"\nname = \"Prepared\"\nprepare = \"test.netplay.prepare\"\n" "[[target]]\ngame_id = \"READER\"\n[[feature]]\nid = \"active\"\nname = \"Active\"\ndefault_enabled = true\n" "[[resource]]\nfeature = \"active\"\nid = \"rom\"\nlabel = \"ROM\"\nformat = \"n64-rom\"\nhidden = true\nrequired = true\nsize = 64\nsha256 = \"" + sha256_hex(rom) + "\"\n"); PSXRecompV4::mod_runtime_end_netplay(); test_match_caps = {}; check(PSXRecompV4::mod_runtime_initialize(prepared_root, "READER", 0, {}, &error) && PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "direct launch prepares its selected resources"); const std::string prepared_fp = PSXRecompV4::mod_runtime_session_plan_fp(); PSXRecompV4::mod_runtime_end_netplay(); const std::string disabled = "format_version = 2\n[[feature]]\npackage_id = \"prepared.test\"\nid = \"active\"\nenabled = false\n"; write_text(prepared_root / "state.toml", disabled); check(PSXRecompV4::mod_runtime_initialize(prepared_root, "READER", 0, {}, &error), "unprepared guest initialize"); test_match_caps.valid = 1; test_match_caps.mod_count = 1; std::snprintf(test_match_caps.mod_plan_fp, sizeof(test_match_caps.mod_plan_fp), "%s", prepared_fp.c_str()); std::snprintf(test_match_caps.mods[0].id, sizeof(test_match_caps.mods[0].id), "prepared.test"); std::snprintf(test_match_caps.mods[0].ver, sizeof(test_match_caps.mods[0].ver), "1.0.0"); std::snprintf(test_match_caps.mods[0].feats, sizeof(test_match_caps.mods[0].feats), "active"); check(PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "host enables and prepares guest feature before verification"); check(prepare_calls == 2 && PSXRecompV4::mod_runtime_plan_fingerprint_portable() == prepared_fp, "prepared guest resolves identical verified bytes"); std::ifstream saved(prepared_root / "state.toml"); check(std::string(std::istreambuf_iterator(saved), {}) == disabled, "prepared online launch preserves disabled offline choice"); PSXRecompV4::mod_runtime_end_netplay(); test_match_caps = {}; } // Host-only features ([[plugin]] netplay = "host_only": the CRT Filter, // Image Filtering, Enhanced Audio, Rewind) stay the player's own in every // netplay mode and are no part of the plan fingerprint. { static int look_runs = 0, plain_runs = 0; check(psx_mod_register_activation_plugin("host.only.look", +[]() { ++look_runs; }), "register host-only fixture"); check(psx_mod_register_activation_plugin("host.only.plain", +[]() { ++plain_runs; }), "register ordinary fixture"); const auto host_root = root / "host-only-netplay"; write_text(host_root / "packages/look.test/1.0.0/manifest.toml", "format_version = 11\nid = \"look.test\"\nversion = \"1.0.0\"\nname = \"Look\"\n" "[[target]]\ngame_id = \"READER\"\n[[feature]]\nid = \"look\"\nname = \"Look\"\n" "[[plugin]]\nfeature = \"look\"\nid = \"host.only.look\"\nnetplay = \"host_only\"\n"); write_text(host_root / "packages/plain.test/1.0.0/manifest.toml", "format_version = 5\nid = \"plain.test\"\nversion = \"1.0.0\"\nname = \"Plain\"\n" "[[target]]\ngame_id = \"READER\"\n[[feature]]\nid = \"active\"\nname = \"Active\"\n" "[[plugin]]\nfeature = \"active\"\nid = \"host.only.plain\"\n"); const std::string both = "format_version = 2\n[[feature]]\npackage_id = \"look.test\"\nid = \"look\"\nenabled = true\n" "[[feature]]\npackage_id = \"plain.test\"\nid = \"active\"\nenabled = true\n"; const std::string look_only = "format_version = 2\n[[feature]]\npackage_id = \"look.test\"\nid = \"look\"\nenabled = true\n"; PSXRecompV4::mod_runtime_end_netplay(); test_match_caps = {}; write_text(host_root / "state.toml", both); check(PSXRecompV4::mod_runtime_initialize(host_root, "READER", 0, {}, &error), "host-only initialize"); check(PSXRecompV4::mod_runtime_feature_host_only("look.test", "look") && !PSXRecompV4::mod_runtime_feature_host_only("plain.test", "active"), "host-only features are recognised"); // LAN/direct: the local selection; its fingerprint leaves the look out. check(PSXRecompV4::mod_runtime_commit_for_direct_netplay(iso_path, &error), "direct launch with a host-only feature"); const std::string plain_fp = PSXRecompV4::mod_runtime_session_plan_fp(); check(plain_fp.size() == 64, "direct launch publishes a fingerprint"); PSXRecompV4::mod_runtime_end_netplay(); // Vanilla netplay: the player's host-only feature stays, the rest goes. look_runs = plain_runs = 0; check(PSXRecompV4::mod_runtime_commit_netplay_view(iso_path, &error), "vanilla netplay commit"); mod_runtime_activate_plugins(); check(look_runs == 1 && plain_runs == 0, "vanilla netplay keeps only the player's host-only feature"); PSXRecompV4::mod_runtime_end_netplay(); // Content negotiation, explicit empty host plan: vanilla shared game, // the player's look kept. test_match_caps = {}; test_match_caps.valid = 1; look_runs = plain_runs = 0; check(PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "empty host plan commit"); mod_runtime_activate_plugins(); check(look_runs == 1 && plain_runs == 0, "an empty host plan keeps the player's host-only feature"); PSXRecompV4::mod_runtime_end_netplay(); test_match_caps = {}; // Content negotiation, the host's plan (plain only, which a host // never publishes the look into): a guest with plain off offline and // the look on gets plain from the host, keeps its look, and the // fingerprints agree. write_text(host_root / "state.toml", look_only); check(PSXRecompV4::mod_runtime_initialize(host_root, "READER", 0, {}, &error), "guest initialize"); test_match_caps = {}; test_match_caps.valid = 1; test_match_caps.mod_count = 1; std::snprintf(test_match_caps.mod_plan_fp, sizeof(test_match_caps.mod_plan_fp), "%s", plain_fp.c_str()); std::snprintf(test_match_caps.mods[0].id, sizeof(test_match_caps.mods[0].id), "plain.test"); std::snprintf(test_match_caps.mods[0].ver, sizeof(test_match_caps.mods[0].ver), "1.0.0"); std::snprintf(test_match_caps.mods[0].feats, sizeof(test_match_caps.mods[0].feats), "active"); look_runs = plain_runs = 0; check(PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error), "the host's plan verifies whatever the guest's host-only choice"); mod_runtime_activate_plugins(); check(look_runs == 1 && plain_runs == 1, "the host's plan runs beside the guest's own host-only feature"); PSXRecompV4::mod_runtime_end_netplay(); test_match_caps = {}; } // The audio timeline skips INDEX00 pregaps and maps into the unchanged // mounted BIN, including a second audio track sharing the same file. const auto audio_root=root/"disc-audio"; std::vector pcm(8*2352); for(unsigned i=0;i grown_iso = iso; grown_iso[22 * 2048 + 3500] = 0x77; write_bytes(patched_iso, grown_iso); write_text(patched_root / "packages/reader.patch/1.0.0/manifest.toml", "format_version = 5\n" "id = \"reader.patch\"\n" "version = \"1.0.0\"\n" "name = \"Reader Patch\"\n" "[[target]]\n" "game_id = \"READER\"\n" "disc_sha256 = \"" + sha256_hex(grown_iso) + "\"\n" "[[feature]]\n" "id = \"asset\"\n" "name = \"Asset\"\n" "[[feature]]\n" "id = \"grow\"\n" "name = \"Grow\"\n" "[[patch]]\n" "feature = \"grow\"\n" "target = \"disc_user\"\n" "offset = " + std::to_string(21 * 2048 + 10) + "\n" "expected = \"b80b0000\"\n" "replace = \"a00f0000\"\n" "[[feature]]\n" "id = \"huge\"\n" "name = \"Huge\"\n" "[[patch]]\n" "feature = \"huge\"\n" "target = \"disc_user\"\n" "offset = " + std::to_string(21 * 2048 + 10) + "\n" "expected = \"b80b0000\"\n" "replace = \"00f82204\"\n" "[[patch]]\n" "feature = \"asset\"\n" "target = \"disc_user\"\n" "offset = " + std::to_string(22 * 2048 + 5) + "\n" "expected = \"23\"\n" "replace = \"5a\"\n"); write_text(patched_root / "state.toml", "format_version = 2\n" "[[feature]]\n" "package_id = \"reader.patch\"\n" "id = \"asset\"\n" "enabled = true\n" "[[feature]]\n" "package_id = \"reader.patch\"\n" "id = \"grow\"\n" "enabled = true\n"); check(PSXRecompV4::mod_runtime_initialize(patched_root, "READER", 0, {}, &error), "patched reader initialize"); check(PSXRecompV4::mod_runtime_commit(patched_iso, &error), "patched reader commit"); check(psx_mod_read_disc_file("S0/LEVEL.NSF", nullptr, 0, &bytes) && bytes == 4000, "host disc reads resolve paths through patched directory records"); std::vector effective(4000); check(psx_mod_read_disc_file("S0/LEVEL.NSF", effective.data(), (uint32_t)effective.size(), &bytes) && bytes == 4000 && effective[5] == 0x5a && effective[3500] == 0x77 && std::equal(effective.begin() + 6, effective.begin() + 3000, iso.begin() + 22 * 2048 + 6), "host disc reads apply the committed plan before the drive is enabled"); check(!psx_mod_read_disc_file("S0", nullptr, 0, &bytes), "effective lookup still rejects directories"); std::array drive_sector{}; std::copy_n(iso.begin() + 22 * 2048, 2048, drive_sector.begin()); mod_runtime_patch_disc_sector(22, 0, drive_sector.data(), (uint32_t)drive_sector.size()); check(drive_sector[5] == 0x23, "the emulated drive path stays original until disc patches are enabled"); mod_runtime_enable_disc_patches(); mod_runtime_patch_disc_sector(22, 0, drive_sector.data(), (uint32_t)drive_sector.size()); check(drive_sector[5] == 0x5a, "enabled drive path applies the same plan"); /* Grown archives may exceed 64 MiB (an extended MMX6 ROCK_X6.DAT is * 69,400,576 bytes); the bound is CD capacity, not a fixed cap. */ write_text(patched_root / "state.toml", "format_version = 2\n" "[[feature]]\n" "package_id = \"reader.patch\"\n" "id = \"huge\"\n" "enabled = true\n"); check(PSXRecompV4::mod_runtime_initialize(patched_root, "READER", 0, {}, &error) && PSXRecompV4::mod_runtime_commit(patched_iso, &error), "huge plan commit"); check(psx_mod_read_disc_file("S0/LEVEL.NSF", nullptr, 0, &bytes) && bytes == 69400576u, "files grown past 64 MiB keep their effective size"); } { const fs::path hd_mods = root / "hd-mods"; write_text(hd_mods / "bundled" / "runtime.hd" / "1.0.0" / "manifest.toml", "format_version = 5\nid = \"runtime.hd\"\nversion = \"1.0.0\"\n" "name = \"HD\"\nresolver = \"declarative\"\nsave_compatibility = \"shared\"\n" "[[target]]\ngame_id = \"SCUS-94236\"\n" "[[feature]]\nid = \"textures\"\nname = \"HD\"\ndefault_enabled = false\n" "[[resource]]\nfeature = \"textures\"\nid = \"pack\"\nlabel = \"Pack\"\n" "format = \"directory\"\nrequired = true\n" "[[option]]\nfeature = \"textures\"\nid = \"replacements\"\n" "label = \"Load\"\ntype = \"boolean\"\ndefault = \"true\"\n" "[[option]]\nfeature = \"textures\"\nid = \"dump\"\n" "label = \"Dump\"\ntype = \"boolean\"\ndefault = \"false\"\n" "[[plugin]]\nfeature = \"textures\"\nid = \"psx.hd-textures\"\n"); write_text(hd_mods / "state.toml", "format_version = 2\n[[feature]]\npackage_id = \"runtime.hd\"\n" "id = \"textures\"\nenabled = true\n[feature.values]\ndump = \"true\"\n"); check(psx_mod_register_activation_plugin("psx.hd-textures", test_hd_activation), "HD activation plugin registers"); check(PSXRecompV4::mod_runtime_initialize(hd_mods, "SCUS-94236", 0, {}, &error), "HD mods initialize"); const fs::path expected = fs::absolute(hd_mods / "texture-packs" / "SCUS-94236"); check(fs::is_directory(expected / "dumps") && fs::is_directory(expected / "replacements"), "default HD directories exist before first launch, even without dumping"); check(PSXRecompV4::mod_runtime_commit(stock_path, &error), "default pack resource commits without manual folder selection"); const int shutdown_before = hd_shutdown_calls; mod_runtime_activate_plugins(); check(hd_shutdown_calls == shutdown_before + 1 && hd_configure_calls == 1 && hd_root == expected.string() && hd_replacements_value == 1 && hd_dump_value == 1, "HD activation clears previous session and reads owning committed options"); char value[16] = "old"; check(!psx_mod_current_option_value("dump", value, sizeof(value)) && value[0] == '\0', "owning option context does not escape the trusted callback"); check(!psx_mod_set_hd_texture_pack("pack", 1, 1), "pack configuration requires the trusted resource callback context"); hd_replacements_value = 0; check(psx_mod_set_hd_texture_dump(0) && hd_dump_value == 0, "live dump control forwards the requested switch"); check(!gpu_hd_textures_active() && psx_mod_set_hd_texture_dump(1) && hd_dump_value == 1, "dump-only session can turn capture back on after turning it off"); check(psx_mod_reload_hd_texture_pack() && hd_reload_calls == 1, "pack reload forwards to renderer"); check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), "HD plan clears"); mod_runtime_activate_plugins(); check(hd_shutdown_calls == shutdown_before + 2 && hd_configure_calls == 1, "disabled HD feature leaves no previous session pack active"); check(!psx_mod_set_hd_texture_dump(1), "live dump control refuses an unconfigured session"); const fs::path custom = hd_mods / fs::u8path(u8"chosen pack # % \u65e5\u672c"); fs::create_directories(custom); write_text(hd_mods / "state.toml", "format_version = 2\n[[feature]]\npackage_id = \"runtime.hd\"\n" "id = \"textures\"\nenabled = true\n[feature.resources]\npack = \"" + custom.generic_u8string() + "\"\n"); check(PSXRecompV4::mod_runtime_initialize(hd_mods, "SCUS-94236", 0, {}, &error) && PSXRecompV4::mod_runtime_commit(stock_path, &error), "chosen Unicode pack path resolves and persists through commit"); mod_runtime_activate_plugins(); check(hd_root == custom.generic_u8string(), "renderer receives the chosen folder as UTF-8"); check(PSXRecompV4::mod_runtime_initialize(hd_mods, "SCUS-94236", 0, {}, &error) && PSXRecompV4::mod_runtime_commit(stock_path, &error), "committed Unicode pack path reloads without corruption"); mod_runtime_activate_plugins(); check(hd_root == custom.generic_u8string(), "Unicode pack root survives the complete session cycle"); } /* A compiled game hook survives mod-plan clearing, but runs only online. */ check(psx_game_register_netplay_function_entry( 0x80004000u, test_game_netplay_entry) == 1, "trusted game netplay entry must register"); check(psx_game_register_netplay_function_entry( 0x80004000u, test_game_netplay_entry) == 1, "duplicate game netplay registration must be idempotent"); check(psx_game_register_netplay_function_filter( 0x80005000u, test_game_netplay_filter) == 1, "trusted game netplay filter must register"); CPUState game_cpu{}; psx_mod_function_entry(&game_cpu, 0x80004000u); game_cpu.gpr[31] = 0x80006000u; check(!psx_mod_function_entry(&game_cpu, 0x80005000u), "game netplay filter must not consume offline function"); check(game_netplay_entry_hits == 0, "game netplay hook must not run offline"); check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), error.c_str()); test_netplay_active = 1; psx_mod_function_entry(&game_cpu, 0xA0004000u); check(game_netplay_entry_hits == 1 && g_psx_mod_function_entry_hooks == 2, "game hook must survive netplay mod clear and match code aliases"); check(psx_mod_function_entry(&game_cpu, 0xA0005000u) == 1 && game_cpu.pc == 0x80006000u && game_cpu.gpr[2] == 0x1234u && game_netplay_filter_hits == 1, "game netplay filter must consume call and resume at return PC"); psx_mod_function_entry(&game_cpu, 0x80004004u); check(game_netplay_entry_hits == 1, "game hook must ignore other addresses"); test_netplay_active = 0; psx_mod_function_entry(&game_cpu, 0x80004000u); check(game_netplay_entry_hits == 1, "game hook must disarm when netplay stops"); check(!psx_mod_function_entry(&game_cpu, 0x80005000u) && game_netplay_filter_hits == 1, "game filter must disarm when netplay stops"); // Reliable identity detects byte replacement even when size/mtime are restored. { const auto identity_path = root / "identity.bin"; write_bytes(identity_path, {1, 2, 3, 4}); const auto original_time = fs::last_write_time(identity_path); std::string before, after; const bool reliable = PSXRecompV4::host_file_identity(identity_path, before); write_bytes(identity_path, {4, 3, 2, 1}); fs::last_write_time(identity_path, original_time); check(!reliable || (PSXRecompV4::host_file_identity(identity_path, after) && before != after), "file identity rejects same-size restored-mtime replacement"); check(!PSXRecompV4::host_file_identity(root, after), "directories are never file cache identities"); check(!PSXRecompV4::host_file_identity(root / "absent.bin", after), "missing identity is a cache miss"); } #if defined(PSX_LAUNCHER_MOD_COMMIT_WORKER_SAFE) { const auto cached_root = root / "audited-cache"; const auto output_path = root / "audited-output.z64"; const auto input_path = root / "audited-input.bin"; const auto disc = root / "audited-disc.iso"; fs::copy_file(iso_path, disc, fs::copy_options::overwrite_existing); write_bytes(input_path, {1,2,3,4}); std::string input_receipt, output_receipt; int prepare_calls = 0; bool throw_probe = false, mutate_on_resolve = false, throw_resolver = false; const std::string manifest = "format_version = 9\nid = \"cache.test\"\nversion = \"1.0.0\"\nname = \"Cache\"\nprepare = \"test.cache.prepare\"\nresolver = \"builtin:test.cache.resolve\"\n" "[[target]]\ngame_id = \"READER\"\n" "[[feature]]\nid = \"active\"\nname = \"Active\"\ndefault_enabled = true\n" "[[resource]]\nfeature = \"active\"\nid = \"source\"\nlabel = \"Source\"\ninput_only = true\nshared_source = \"cache.original\"\nrequired = true\n" "[[resource]]\nfeature = \"active\"\nid = \"rom\"\nlabel = \"ROM\"\nformat = \"n64-rom\"\nhidden = true\nrequired = true\nsize = 64\nsha256 = \"" + sha256_hex(rom) + "\"\n" "[[feature]]\nid = \"second\"\nname = \"Second\"\ndefault_enabled = false\n" "[[resource]]\nfeature = \"second\"\nid = \"rom\"\nlabel = \"ROM\"\nformat = \"n64-rom\"\nhidden = true\nrequired = true\nsize = 64\nsha256 = \"" + sha256_hex(rom) + "\"\n"; check(PSXRecompV4::mod_register_builtin_resolver("test.cache.resolve", [&](const auto&, const auto&, const auto&, auto&, auto&) { if (throw_resolver) throw std::runtime_error("resolver fixture exception"); if (mutate_on_resolve) write_bytes(input_path, {7,7,7,7}); return true; }), "register resolver for mutation and unwind regression"); const auto manifest_path = cached_root / "packages/cache.test/1.0.0/manifest.toml"; write_text(manifest_path, manifest); check(PSXRecompV4::mod_register_media_preparer("test.cache.prepare", [&](const PSXRecompV4::ModPrepareContext&, std::map& outputs, std::string&) { ++prepare_calls; write_bytes(output_path, rom); PSXRecompV4::host_file_identity(input_path, input_receipt); PSXRecompV4::host_file_identity(output_path, output_receipt); outputs["rom"] = output_path; return true; }, [&](const PSXRecompV4::ModPrepareContext& context, std::map& outputs, std::string&) { if (throw_probe) throw std::runtime_error("probe fixture exception"); std::string input, output; if (context.feature_id == "second" || !PSXRecompV4::host_file_identity(input_path, input) || input != input_receipt || !PSXRecompV4::host_file_identity(output_path, output) || output != output_receipt) return false; outputs["rom"] = output_path; return true; }), "register audited preparation and cheap identity probe"); PSXRecompV4::ModPackageManager source_selection(cached_root); check(source_selection.scan(&error) && source_selection.set_feature_resource_path("cache.test", "active", "source", input_path, &error) && source_selection.save_state(&error), "persist shared input selection for audited fixture"); PSXRecompV4::mod_runtime_end_netplay(); test_match_caps = {}; check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error), "audited initialize"); check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc) && prepare_calls == 0, "cold cached-only launch does not prepare missing receipt"); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && prepare_calls == 1, "cold launcher creates verified preparation"); std::string input_stamp, output_stamp, disc_stamp, manifest_stamp; const bool qualified_stamps = PSXRecompV4::host_file_identity(input_path,input_stamp) && PSXRecompV4::host_file_identity(output_path,output_stamp) && PSXRecompV4::host_file_identity(disc,disc_stamp) && PSXRecompV4::host_file_identity(manifest_path,manifest_stamp); if (qualified_stamps) { const auto reused = startup_counter("startup.plan_reuse"); const auto hashed = startup_counter("startup.disc_hash"); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && PSXRecompV4::mod_runtime_commit(disc, &error) && prepare_calls == 1 && startup_counter("startup.plan_reuse") == reused + 2 && startup_counter("startup.disc_hash") == hashed, "duplicate provider and main commit reuse a prepared plan without converter or hash"); check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error) && PSXRecompV4::mod_runtime_try_prepare_cached(disc) && prepare_calls == 1 && startup_counter("startup.disc_hash") == hashed, "warm initialize preloads receipts with no preparer or raw hash"); mod_runtime_activate_plugins(); check(PSXRecompV4::mod_runtime_commit(disc, &error) && prepare_calls == 2, "activation invalidates the preboot ticket"); check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error) && PSXRecompV4::mod_runtime_try_prepare_cached(disc), "restore warm plan for source invalidation"); const auto input_time = fs::last_write_time(input_path); write_bytes(input_path, {4,3,2,1}); fs::last_write_time(input_path, input_time); check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc), "external shared input replacement invalidates title receipt"); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && prepare_calls == 3, "changed source goes through full preparation"); auto corrupt_rom = rom; corrupt_rom[32] ^= 1; write_bytes(output_path, corrupt_rom); // Even if the probe claims the file is current, the first warm resolve // must verify output bytes against the declared SHA. PSXRecompV4::host_file_identity(output_path, output_receipt); check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error) && !PSXRecompV4::mod_runtime_try_prepare_cached(disc) && prepare_calls == 3, "warm output corruption fails immutable-byte verification"); fs::remove(output_path); check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc) && prepare_calls == 3, "missing output fails without invoking converter"); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && prepare_calls == 4, "full preparation repairs missing output"); } else { const auto hashed = startup_counter("startup.disc_hash"); const auto reused = startup_counter("startup.plan_reuse"); const auto before = prepare_calls; check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc,&error) && PSXRecompV4::mod_runtime_commit(disc,&error) && prepare_calls==before+2 && startup_counter("startup.disc_hash")==hashed+2 && startup_counter("startup.plan_reuse")==reused, "unqualified identities retain full preparation and hashing without tickets"); check(PSXRecompV4::mod_runtime_initialize(cached_root,"READER",0,{},&error) && !PSXRecompV4::mod_runtime_try_prepare_cached(disc) && prepare_calls==before+2, "unqualified warm startup never invokes the converter or claims readiness"); const auto input_time=fs::last_write_time(input_path); write_bytes(input_path,{4,3,2,1});fs::last_write_time(input_path,input_time); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc,&error) && prepare_calls==before+3, "restored-mtime source still goes through full preparation"); auto corrupt=rom;corrupt[32]^=1;write_bytes(output_path,corrupt); check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc) && prepare_calls==before+3, "unqualified corrupt output never obtains cached readiness"); fs::remove(output_path); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc,&error) && prepare_calls==before+4, "unqualified full preparation repairs missing content"); } throw_probe = true; check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc), "throwing cached probe fails safely"); throw_probe = false; check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error), "cached flag restored after failure"); // Probe results are transactional across multiple enabled features. PSXRecompV4::ModPackageManager transactional(cached_root); check(transactional.scan(&error) && transactional.load_state(&error) && transactional.set_feature_enabled("cache.test", "second", true, &error) && transactional.set_feature_resource_path("cache.test", "active", "rom", rom_path, &error), "enable second probe fixture"); const auto original_output = transactional.feature_resource_path("cache.test", "active", "rom"); check(!transactional.prepare_resources("READER", disc, root / "cache", &error, true) && transactional.feature_resource_path("cache.test", "active", "rom") == original_output, "later cache miss discards earlier pending output selections"); // A throw inside resolve must restore cached-only/provider modes. check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error), "invalidate ticket for resolver exception fixture"); throw_resolver = true; check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc), "best-effort preload contains resolver exceptions before launcher UI"); throw_resolver = false; fs::remove(output_path); const auto before_unwind = prepare_calls; check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && prepare_calls == before_unwind + 1, "cached-only mode restored after resolve exception"); // A dependency changed during resolution must never publish readiness. check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error), "invalidate ticket for resolver mutation fixture"); const auto before_mutation = prepare_calls; mutate_on_resolve = true; check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error), "full resolution can finish with concurrent input mutation"); mutate_on_resolve = false; check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && prepare_calls == before_mutation + 2, "unstable resolve never publishes a reusable ticket"); if (qualified_stamps) { // Altering a syntactically valid digest must fail its receipt checksum. std::filesystem::path cache_dir; #if defined(_WIN32) if (const char* local = std::getenv("LOCALAPPDATA")) cache_dir = local; #else if (const char* cache = std::getenv("XDG_CACHE_HOME")) cache_dir = cache; else if (const char* home_dir = std::getenv("HOME")) cache_dir = fs::path(home_dir) / ".cache"; #endif if (cache_dir.empty()) cache_dir = fs::temp_directory_path(); cache_dir /= "psxrecomp/imports/disc-digests-v2"; bool corrupted_receipt = false; for (const auto& entry : fs::directory_iterator(cache_dir)) { std::ifstream saved(entry.path(), std::ios::binary); std::string receipt(std::istreambuf_iterator(saved), {}); if (receipt.find(disc.generic_string()) == std::string::npos || receipt.size() < 130) continue; const size_t digest_offset = receipt.size() - 130; receipt[digest_offset] = receipt[digest_offset] == '0' ? '1' : '0'; write_text(entry.path(), receipt); corrupted_receipt = true; } check(corrupted_receipt && PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error) && !PSXRecompV4::mod_runtime_try_prepare_cached(disc), "corrupt well-formed digest receipt misses warm startup"); const auto before_corrupt_hash = startup_counter("startup.disc_hash"); check(PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && startup_counter("startup.disc_hash") == before_corrupt_hash + 1, "full launch repairs corrupted digest with authoritative source hash"); } // Catalog changes cannot certify stale in-memory parsed manifests. write_text(manifest_path, manifest + "\n# external catalog mutation\n"); check(!PSXRecompV4::mod_runtime_prepare_for_launcher(disc, &error) && error.find("catalog changed") != std::string::npos, "changed manifest requires rescan instead of resealing stale package"); check(PSXRecompV4::mod_runtime_initialize(cached_root, "READER", 0, {}, &error), "explicit initialize rescans changed catalog"); const int before_netplay = prepare_calls; const auto net_hash = startup_counter("startup.disc_hash"); check(PSXRecompV4::mod_runtime_commit_for_direct_netplay(disc, &error) && prepare_calls == before_netplay + 1 && startup_counter("startup.disc_hash") > net_hash, "netplay ignores preboot ticket and hashes the source fully"); check(!PSXRecompV4::mod_runtime_try_prepare_cached(disc), "active netplay never preloads an offline ticket"); PSXRecompV4::mod_runtime_end_netplay(); // Legacy preparers have no cheap receipt and must never claim warm readiness. const auto legacy_root = root / "legacy-cache"; int legacy_calls = 0; check(PSXRecompV4::mod_register_media_preparer("test.legacy.prepare", [&](const PSXRecompV4::ModPrepareContext&, std::map& outputs, std::string&) { ++legacy_calls; outputs["rom"] = output_path; return true; }), "register legacy preparer without probe"); std::string legacy_manifest = manifest; legacy_manifest.replace(legacy_manifest.find("test.cache.prepare"), std::string("test.cache.prepare").size(), "test.legacy.prepare"); write_text(legacy_root / "packages/cache.test/1.0.0/manifest.toml", legacy_manifest); check(PSXRecompV4::mod_runtime_initialize(legacy_root, "READER", 0, {}, &error) && !PSXRecompV4::mod_runtime_try_prepare_cached(disc) && legacy_calls == 0, "legacy preparer always misses cached-only startup"); } #else { const auto defaults = root / "default-hashing"; fs::create_directories(defaults); check(PSXRecompV4::mod_runtime_initialize(defaults, "READER", 0, {}, &error), "default hashing fixture initialize"); const auto hash_count = startup_counter("startup.disc_hash"); check(PSXRecompV4::mod_runtime_commit(iso_path, &error) && PSXRecompV4::mod_runtime_commit(iso_path, &error) && startup_counter("startup.disc_hash") == hash_count + 1, "default runtime preserves hash-only-on-selected-path-change behavior"); check(!PSXRecompV4::mod_runtime_try_prepare_cached(iso_path), "default runtime does not opt into cached startup"); } #endif /* The executable-owned profile forces its trusted plugin, including hooks, * without applying/saving any player's offline patches or selections. */ test_match_caps = {}; const bool missing_profile = argc > 1 && std::string(argv[1]) == "--missing-profile"; const bool missing_renderer = argc > 1 && std::string(argv[1]) == "--missing-renderer"; const bool option_renderer_missing = argc > 1 && std::string(argv[1]) == "--option-renderer-missing"; const bool profile_options = option_renderer_missing || (argc > 1 && std::string(argv[1]) == "--profile-options"); static const PSXModNetplayProfile profile = { missing_profile ? "runtime.missing-simulation" : "runtime.test-vblank", "runtime-coop-delay-v1", 0, 0, 1, 3u, profile_options ? "runtime.option-renderer" : "runtime.missing-renderer", nullptr, 0, profile_options ? "runtime.test" : nullptr, profile_options ? "vblank-plugin" : nullptr, profile_options ? "cameras" : nullptr, profile_options ? "split" : nullptr }; if (profile_options && !option_renderer_missing) check(psx_mod_register_activation_plugin("runtime.option-renderer", option_renderer_activate), "profile option renderer registration"); check(psx_mod_register_netplay_profile(&profile), "trusted profile registration"); check(PSXRecompV4::mod_runtime_initialize(root, "SLUS-RUNTIME", 0x80002000, {}, &error), "profile reinitialize offline catalog"); if (missing_profile || missing_renderer || option_renderer_missing) { if (missing_renderer) check(psx_mod_netplay_set_aspect(1), "select profile renderer"); if (option_renderer_missing) { test_match_caps.valid = 1; test_match_caps.mod_count = 1; std::strcpy(test_match_caps.mods[0].id, "runtime.test"); std::strcpy(test_match_caps.mods[0].ver, "1.0.0"); std::strcpy(test_match_caps.mods[0].feats, "vblank-plugin=cameras~split"); std::string fingerprint; check(PSXRecompV4::mod_runtime_netplay_settings_fingerprint(test_match_caps, fingerprint, &error), "missing renderer offer still has a valid settings identity"); std::strcpy(test_match_caps.mod_plan_fp, fingerprint.c_str()); } check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error) && !psx_mod_netplay_is_active() && !error.empty(), "missing trusted plugin refuses profile activation with a reason"); check(psx_mod_netplay_savestates_supported(), "failed profile cannot latch snapshot policy"); const auto before_failed_activation = activation_calls; mod_runtime_activate_plugins(); check(activation_calls == before_failed_activation, "failed profile cannot activate stale offline plugin"); fs::remove_all(root, ec); return failures ? 1 : 0; } const auto selection_path = root / "state.toml"; auto read_selection = [&]() { std::ifstream file(selection_path, std::ios::binary); return std::string(std::istreambuf_iterator(file), {}); }; std::string offline_selection = read_selection(); check(PSXRecompV4::mod_runtime_commit_for_direct_netplay(iso_path, &error), error.c_str()); check(psx_mod_netplay_is_active() && !psx_mod_netplay_savestates_supported(), "profile activates host-state snapshot policy"); const int activations_before = activation_calls; mod_runtime_activate_plugins(); check(activation_calls == activations_before + 1, "profile forces native plugin activation"); ram[0x1000] = 0x5a; mod_runtime_on_dispatch(0x80002000); check(ram[0x1000] == 0x5a, "profile excludes offline executable writes"); check(psx_mod_read_disc_file("S0/LEVEL.NSF", result.data(), 3000, &bytes) && bytes == 3000, "profile retains original-disc reads"); check(read_selection() == offline_selection, "profile does not save temporary selections"); check(PSXRecompV4::mod_runtime_commit_netplay_view(iso_path, &error), "profile rematch commit"); mod_runtime_activate_plugins(); check(activation_calls == activations_before + 2, "profile rematch activates exactly once"); CPUState profile_cpu{}; test_netplay_active = 1; filter_handles = true; check(psx_mod_function_entry(&profile_cpu, 0x80003008) && profile_cpu.gpr[2] == 42u, "profile retains modern trusted function filters"); test_netplay_active = 0; test_match_caps.valid = 1; test_match_caps.mod_count = 1; check(!PSXRecompV4::mod_runtime_commit_for_netplay(iso_path, &error) && !psx_mod_netplay_is_active(), "profile rejects imported package plans"); test_match_caps = {}; check(PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "profile restores after rejected plan"); if (profile_options) { char value[32], unified_identity[65], split_identity[65]; check(psx_mod_option_value("runtime.test", "vblank-plugin", "cameras", value, sizeof(value)) && std::string(value) == "unified", "profile includes default camera option"); check(psx_mod_netplay_content_identity("", unified_identity), "default profile content identity"); test_match_caps.valid = 1; test_match_caps.mod_count = 1; std::strcpy(test_match_caps.mods[0].id, "runtime.test"); std::strcpy(test_match_caps.mods[0].ver, "1.0.0"); std::strcpy(test_match_caps.mods[0].feats, "vblank-plugin=cameras~split"); std::string fingerprint; check(PSXRecompV4::mod_runtime_netplay_settings_fingerprint(test_match_caps, fingerprint, &error) && fingerprint == "24a3aeaf6034d1a59df3ac27334227343237550d4938b7b00c6ae77dfec21367", "published settings fingerprint matches the canonical codec fixture"); char preview_identity[65]; check(psx_mod_netplay_content_identity("", preview_identity) && std::string(preview_identity) == unified_identity && read_selection() == offline_selection, "offer preview does not mutate the active profile or preferences"); check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "missing settings fingerprint rejected"); std::strcpy(test_match_caps.mod_plan_fp, fingerprint.c_str()); test_match_caps.mod_plan_fp[0] = '0'; check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "tampered settings fingerprint rejected"); std::strcpy(test_match_caps.mod_plan_fp, fingerprint.c_str()); check(PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), error.c_str()); mod_runtime_activate_plugins(); check(psx_mod_option_value("runtime.test", "vblank-plugin", "cameras", value, sizeof(value)) && std::string(value) == "split", "profile adopts host split camera option"); check(option_renderer_activations == 1, "split activates the declared renderer at native aspect"); check(psx_mod_netplay_content_identity("", split_identity) && std::string(split_identity) != unified_identity, "different camera policies cannot handshake"); check(read_selection() == offline_selection, "host options do not persist over offline choices"); std::strcpy(test_match_caps.mods[0].feats, "vblank-plugin=cameras~unified"); check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "changed options with stale fingerprint rejected"); check(PSXRecompV4::mod_runtime_netplay_settings_fingerprint(test_match_caps, fingerprint, &error), "unified offer fingerprint"); std::strcpy(test_match_caps.mod_plan_fp, fingerprint.c_str()); check(PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), error.c_str()); mod_runtime_activate_plugins(); check(option_renderer_activations == 1, "unified does not activate the option renderer"); const char* rejected[] = {"dynamic-main", "vblank-plugin,dynamic-main", "vblank-plugin=cameras~invalid", "vblank-plugin=unknown~split"}; for (const char* token : rejected) { std::strcpy(test_match_caps.mods[0].feats, token); check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error) && !psx_mod_netplay_is_active(), "profile rejects undeclared host policy"); } std::strcpy(test_match_caps.mods[0].feats, "vblank-plugin=cameras~split"); test_match_caps.mod_count = 2; check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "profile rejects extra packages"); test_match_caps.mod_count = 1; std::strcpy(test_match_caps.mods[0].ver, "different-version"); check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "profile rejects different package versions"); std::strcpy(test_match_caps.mods[0].ver, "1.0.0"); std::strcpy(test_match_caps.mods[0].id, "different-package"); check(!PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "profile rejects unrelated settings packages"); test_match_caps = {}; check(PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), "profile recovers after rejected options"); check(read_selection() == offline_selection, "all settings commits preserve offline state"); PSXRecompV4::mod_runtime_end_netplay(); PSXRecompV4::ModPackageManager preferences(root); check(preferences.scan(&error) && preferences.load_state(&error) && preferences.set_feature_option("runtime.test", "vblank-plugin", "cameras", "split", &error) && preferences.save_state(&error), "explicit local split preference"); offline_selection = read_selection(); check(PSXRecompV4::mod_runtime_initialize(root, "SLUS-RUNTIME", 0x80002000, {}, &error), "profile reloads the explicit local preference"); check(PSXRecompV4::mod_runtime_commit_for_direct_netplay(iso_path, &error), error.c_str()); check(psx_mod_option_value("runtime.test", "vblank-plugin", "cameras", value, sizeof(value)) && std::string(value) == "split", "direct sessions use the local agreed preference"); check(psx_mod_netplay_content_identity("", split_identity) && std::string(split_identity) != unified_identity, "direct option identity covers camera policy"); test_match_caps.valid = 1; test_match_caps.mod_count = 1; std::strcpy(test_match_caps.mods[0].id, "runtime.test"); std::strcpy(test_match_caps.mods[0].ver, "1.0.0"); std::strcpy(test_match_caps.mods[0].feats, "vblank-plugin"); check(PSXRecompV4::mod_runtime_netplay_settings_fingerprint(test_match_caps, fingerprint, &error), "default host offer fingerprint"); std::strcpy(test_match_caps.mod_plan_fp, fingerprint.c_str()); check(PSXRecompV4::mod_runtime_commit_netplay(iso_path, &error), error.c_str()); check(psx_mod_option_value("runtime.test", "vblank-plugin", "cameras", value, sizeof(value)) && std::string(value) == "unified", "omitted host options use defaults rather than guest preferences"); check(read_selection() == offline_selection, "host defaults preserve the guest's split preference"); } PSXRecompV4::mod_runtime_end_netplay(); check(!psx_mod_netplay_is_active() && psx_mod_netplay_savestates_supported() && PSXRecompV4::mod_runtime_fingerprint().empty(), "profile end clears hooks and snapshot policy"); check(read_selection() == offline_selection, "profile end preserves offline selection file"); { const auto a = root / "portable-default-a"; const auto b = root / "portable-default-b"; const auto manifest = a / "bundled/portable.hd/1.0.0/manifest.toml"; write_text(manifest, "format_version = 5\nid = \"portable.hd\"\nversion = \"1.0.0\"\nname = \"Portable HD fixture\"\nresolver = \"declarative\"\nsave_compatibility = \"shared\"\n" "[[target]]\ngame_id = \"SLUS-PORTABLE\"\n" "[[feature]]\nid = \"hd\"\nname = \"HD\"\ndefault_enabled = false\n" "[[resource]]\nfeature = \"hd\"\nid = \"pack\"\nlabel = \"Pack\"\nformat = \"directory\"\nrequired = true\n" "[[option]]\nfeature = \"hd\"\nid = \"replacements\"\nlabel = \"Load\"\ntype = \"boolean\"\ndefault = \"true\"\n" "[[option]]\nfeature = \"hd\"\nid = \"dump\"\nlabel = \"Dump\"\ntype = \"boolean\"\ndefault = \"false\"\n" "[[plugin]]\nfeature = \"hd\"\nid = \"psx.hd-textures\"\n"); check(PSXRecompV4::mod_runtime_initialize(a, "SLUS-PORTABLE", 0x80002000, {}, &error), error.c_str()); check(fs::is_directory(a / "texture-packs/SLUS-PORTABLE/replacements"), "disabled default was actually initialized"); check(PSXRecompV4::mod_runtime_commit({}, &error), error.c_str()); fs::copy(a, b, fs::copy_options::recursive, ec); check(!ec, "copy default-disabled fixture to relocated root"); fs::rename(a, root / "unavailable-default-a", ec); check(!ec && !fs::exists(a), "old install root is unavailable"); check(PSXRecompV4::mod_runtime_initialize(b, "SLUS-PORTABLE", 0x80002000, {}, &error), error.c_str()); #if defined(RECOMP_LAUNCHER) const auto* portable_provider = PSXRecompV4::mod_runtime_launcher_provider(); RecompLauncherCModResource portable_resource{}; check(portable_provider && portable_provider->feature_resource_get(portable_provider->ctx, "portable.hd", "hd", 0, &portable_resource) && fs::path(portable_resource.path) == fs::absolute(b / "texture-packs/SLUS-PORTABLE"), "live launcher getter exposes exact relocated B default while disabled"); #endif check(PSXRecompV4::mod_runtime_commit({}, &error), error.c_str()); PSXRecompV4::ModPackageManager readback(b); check(readback.scan(&error) && readback.load_state(&error), error.c_str()); check(readback.selected_package("portable.hd") != nullptr, "portable fixture package remains selected"); const auto retained = readback.feature_resource_path("portable.hd", "hd", "pack"); check(retained.empty() || retained.lexically_relative(a).empty() || *retained.lexically_relative(a).begin() == "..", "disabled generated default must not retain unavailable install root"); check(!fs::exists(a), "relocation does not recreate unavailable old root"); check(readback.set_feature_enabled("portable.hd", "hd", true, &error) && readback.save_state(&error), error.c_str()); check(PSXRecompV4::mod_runtime_initialize(b, "SLUS-PORTABLE", 0x80002000, {}, &error), error.c_str()); const auto before_cwd = fs::current_path(); fs::current_path(root); check(PSXRecompV4::mod_runtime_commit({}, &error), error.c_str()); mod_runtime_activate_plugins(); check(fs::path(hd_root) == fs::absolute(b / "texture-packs/SLUS-PORTABLE"), "enabled relocated plugin receives B default despite changed cwd"); fs::current_path(before_cwd); PSXRecompV4::ModPackageManager generated(b); check(generated.scan(&error) && generated.load_state(&error), error.c_str()); check(generated.set_generated_feature_resource_default("portable.hd", "hd", "pack", "texture-packs/SLUS-PORTABLE", &error), error.c_str()); const auto expected = fs::absolute(b / "texture-packs/SLUS-PORTABLE"); check(generated.feature_resource_path("portable.hd", "hd", "pack") == expected, "manager getter returns exact relocated default"); const auto resolved = generated.resolve("SLUS-PORTABLE"); check(resolved.ok && resolved.resources.size() == 1 && resolved.resources[0].path == expected, "committed resolver and getter agree on B"); for (const auto& invalid : {fs::path("../escape"), fs::path("./escape"), expected}) check(!generated.set_generated_feature_resource_default("portable.hd", "hd", "pack", invalid, &error), "generated defaults reject rooted or traversal paths"); const auto external = root / "explicit-external-pack"; fs::create_directories(external); check(generated.set_feature_resource_path("portable.hd", "hd", "pack", external, &error) && generated.save_state(&error), error.c_str()); fs::rename(external, root / "unavailable-external-pack", ec); check(!ec, "explicit external fixture made unavailable"); PSXRecompV4::ModPackageManager legacy(b); check(legacy.scan(&error) && legacy.load_state(&error) && legacy.set_generated_feature_resource_default("portable.hd", "hd", "pack", "texture-packs/SLUS-PORTABLE", &error), error.c_str()); check(legacy.feature_resource_path("portable.hd", "hd", "pack") == external, "missing legacy absolute user selection remains unchanged"); const auto missing_plan = legacy.resolve("SLUS-PORTABLE"); check(missing_plan.resources.size() == 1 && missing_plan.resources[0].path == external, "resolver preserves missing selection for existing downstream validation"); } { const auto base = root/"function-overrides"; write_text(base/"bundled/override.test/1.0.0/manifest.toml", R"toml( format_version = 5 id = "override.test" version = "1.0.0" name = "Overrides" resolver = "declarative" save_compatibility = "shared" [[target]] game_id = "SLUS-OVERRIDE" [[feature]] id = "replace" name = "Replace" default_enabled = true [[option]] feature = "replace" id = "scope" label = "Scope" type = "choice" default = "owned" [[option.choice]] value = "owned" label = "Owned" [[option.choice]] value = "other" label = "Other" [[plugin]] feature = "replace" id = "override.test" )toml"); check(PSXRecompV4::mod_register_activation_plugin("override.test",override_activate),"register override activation"); check(psx_mod_register_function_override("override.test:first",0x80006004,override_body,nullptr,0,0),"queue first override"); check(psx_mod_register_function_override("override.test:second",0x80006000,override_body,nullptr,0,0),"queue second override"); check(!psx_mod_register_function_override("override.test:bad",0x80006001,override_body,nullptr,0,0),"reject misaligned override"); check(PSXRecompV4::mod_runtime_initialize(base,"SLUS-OVERRIDE",0x80002000,{},&error),error.c_str()); check(PSXRecompV4::mod_runtime_commit({},&error),error.c_str()); mod_runtime_activate_plugins(); check(!mod_runtime_activation_error() && func_override_count()==2 && override_activation_calls==1,"selected overrides arm together"); CPUState override_cpu{}; check(g_psx_func_override_hook && g_psx_func_override_hook(&override_cpu,0x6004),"invoke package override"); check(override_scope_checks==2 && !psx_mod_function_entry_active(), "override owns its resource context across VBlank and blocks mid-stack snapshots"); char no_scope[16]{}; check(!psx_mod_current_option_value("scope",no_scope,sizeof no_scope),"override restores its caller's context"); mod_runtime_activate_plugins(); check(!mod_runtime_activation_error() && func_override_count()==2 && override_activation_calls==2,"session reactivation replaces entries without duplicates"); check(PSXRecompV4::mod_runtime_clear_for_netplay(&error),error.c_str()); check(func_override_count()==0,"cleared plan disarms all package entries"); override_activation_calls=0; check(func_override_add("direct",0x80006000,override_body,0)==FO_OK,"register colliding direct override"); check(PSXRecompV4::mod_runtime_initialize(base,"SLUS-OVERRIDE",0x80002000,{},&error),error.c_str()); check(PSXRecompV4::mod_runtime_commit({},&error),error.c_str()); mod_runtime_activate_plugins(); check(mod_runtime_activation_error()!=nullptr && override_activation_calls==0, "late collision rejects launch before callbacks"); check(func_override_count()==1,"collision rolls back all package entries and preserves direct entry"); check(PSXRecompV4::mod_runtime_clear_for_netplay(&error),error.c_str()); mod_runtime_activate_plugins(); check(!mod_runtime_activation_error() && func_override_count()==1,"vanilla session clears failure and retains direct tier"); } fs::remove_all(root, ec); if (failures) return 1; std::cout << "mod runtime tests passed\n"; return 0; } /* render_pass.c: no sandboxed local view runs in this test. */ extern "C" int psx_mod_local_view_scope(void) { return 0; }