#include "mod_runtime.h" #include "psx_sha256.h" #include #include #include #include #include #include #include namespace fs = std::filesystem; static std::array ram; static int failures; 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" void dirty_ram_mark_executable_range(uint32_t, uint32_t) {} 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; } int main() { const fs::path root = fs::temp_directory_path() / "psxrecomp-mod-runtime-test"; 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 / "packages/runtime.test/1.0.0/assets/overlay.bin", overlay); write_text(root / "packages/runtime.test/1.0.0/manifest.toml", "format_version = 2\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" "[[option]]\n" "feature = \"dynamic-main\"\n" "id = \"count\"\n" "label = \"Count\"\n" "type = \"integer\"\n" "min = 0\n" "max = 254\n" "default = 42\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" "[[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"); 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"); std::string error; 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; 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"); 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; 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"); 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"); fs::remove_all(root, ec); if (failures) return 1; std::cout << "mod runtime tests passed\n"; return 0; }