diff --git a/docs/regional/low-input-latency.md b/docs/regional/low-input-latency.md new file mode 100644 index 0000000..1436770 --- /dev/null +++ b/docs/regional/low-input-latency.md @@ -0,0 +1,43 @@ +# Low Input Latency + +Optional, default-off fresher resolved digital P1 input. The original pad reader, +button-edge logic, dialogue locks, actions and rumble remain in control; no extra +simulation or rendering is introduced. US/Japan retain 60 Hz and Europe 50 Hz. + +Verified original-byte profiles cover USA, all five EU1 languages, both EU2 +revisions and their five languages, and Japan. European routines differ only in +verified data relocations. Japan retains its original packed P1/P2 BIOS producer; +the checked cached-P1 store is observed in both original loop callers. Its +legacy ReadPad button bytes are converted to the canonical host layout before +priming and back to the original layout before the guest store. P2 stays original. + +Requires the companion psxrecomp digital-pad snapshot API. Rebuild the runtime +and overlays with the added mod_instruction_sites. Never rename old native DLLs +into a new configuration namespace. Whole-function contracts and device checks +fall back to original input when a profile or controller mode is unsupported. + +Short controller-only LOAD checks pass for USA and English EU2 original revision: +walking, direction changes, jump/landing, and observed input edges. English EU1 +also has an owner-confirmed responsiveness improvement. The other European +languages and EU2 Rev 1 have original-byte contract coverage, not new gameplay +acceptance. Japan's original menu lists the save, but loading into controllable +gameplay remains unconfirmed after the bounded follow-up, including the byte- +layout correction. These checks are not campaign certification. + +Coverage is offline single-player digital P1. Analog/Hybrid, registered controller +transforms or sources, configured multiple pads, multitap, netplay, replay and +input guards retain original input. Unknown guest-only multiplayer patches are +not validated by this API. +Short observed taps are latched; discontinuities re-prime without replaying old +edges. No host API can recover input that the operating system never delivered. + +The engine regression is digital_pad_snapshot_test. The optional title fixture +eu/tests/test_low_input_latency.c accepts owner-extracted regional executables +and checks original-byte contracts, preserved registers and fallback paths. +Retail bytes are never distributed. Internal timings are not controller-to-TV +measurements. Frame interpolation is independent and is not part of this change. + +The validated US/EU production paths are unchanged; Japan only received the +necessary legacy-button byte-layout correction. Re-polling SDL inside the guest hook +or changing presentation timing needs separate correctness work; neither is an +additional proven safe latency reduction in this change. diff --git a/eu/CMakeLists.txt b/eu/CMakeLists.txt index 1db7517..1a5d0b5 100644 --- a/eu/CMakeLists.txt +++ b/eu/CMakeLists.txt @@ -96,6 +96,7 @@ if(TOMBI_PC_FEATURES) endif() # Address-free host policy; guest widescreen hooks use checked EU adapters. target_sources(${tombi_target} PRIVATE + "${TOMBA_ROOT}/src/mods/tomba_low_input_latency_plugin.c" "${TOMBA_ROOT}/src/mods/tomba_hybrid_controller_plugin.c" "${TOMBA_ROOT}/src/mods/tomba_skip_fmv_plugin.c") if(TARGET ${tombi_target}-pgxp) @@ -105,6 +106,25 @@ if(TOMBI_PC_FEATURES) endif() include(CTest) if(BUILD_TESTING) + add_executable(tombi-low-input-tests tests/test_low_input_latency.c) + target_include_directories(tombi-low-input-tests PRIVATE + "${PSXRECOMP_ROOT}/runtime/include") + target_compile_options(tombi-low-input-tests PRIVATE + $<$:/UNDEBUG> + $<$>:-UNDEBUG>) + set(TOMBI_INPUT_TEST_MAIN_DIR "" CACHE PATH + "Optional owner-extracted EU1 MAIN0..4 directory") + if(TOMBI_INPUT_TEST_MAIN_DIR) + add_test(NAME tombi_low_input COMMAND tombi-low-input-tests + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN0.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN1.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN2.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN3.EXE" + "${TOMBI_INPUT_TEST_MAIN_DIR}/MAIN4.EXE") + else() + add_test(NAME tombi_low_input COMMAND tombi-low-input-tests) + endif() + set_tests_properties(tombi_low_input PROPERTIES SKIP_RETURN_CODE 77) add_executable(tombi-widescreen-identity-tests tests/test_widescreen_identity.c "${tombi_ws}/eu_widescreen.c") target_include_directories(tombi-widescreen-identity-tests PRIVATE diff --git a/eu/low_input_latency.c b/eu/low_input_latency.c new file mode 100644 index 0000000..9b75d18 --- /dev/null +++ b/eu/low_input_latency.c @@ -0,0 +1,2 @@ +/* Regional input implementation is shared; original readers still execute. */ +#include "../src/mods/tomba_low_input_latency_plugin.c" diff --git a/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml new file mode 100644 index 0000000..7bf3620 --- /dev/null +++ b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.0/manifest.toml @@ -0,0 +1,22 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.0" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCES-01330" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces the original digital controller packet delay. Currently verified for English EU1 Rev 1, offline single-player D-pad. Other languages and controller modes retain original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false + +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/eu/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/eu/pc.toml b/eu/pc.toml index 90df356..e1d218d 100644 --- a/eu/pc.toml +++ b/eu/pc.toml @@ -20,6 +20,7 @@ known_md5 = ["5029ec8fee56ec6bb6316bd16ee77d54"] known_sha1 = ["2505d958b4439d04844cadd3f968fc7432a48e00"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../psxrecomp/bios/SCPH1001.toml" # ABI/address metadata only; runtime uses bundled OpenBIOS. seeds = "seeds/boot.txt" out_dir = "../build-eu-prototype/generated" diff --git a/eu/release/game.toml b/eu/release/game.toml index 55c2c05..0ac28fd 100644 --- a/eu/release/game.toml +++ b/eu/release/game.toml @@ -16,6 +16,7 @@ known_md5 = ["5029ec8fee56ec6bb6316bd16ee77d54"] known_sha1 = ["2505d958b4439d04844cadd3f968fc7432a48e00"] [recompiler] +mod_instruction_sites = ["0x800263B4"] seeds = "seeds/boot.txt" out_dir = "cache/generated" strict = true diff --git a/eu/seamless/game.toml b/eu/seamless/game.toml index c6b2451..cf654b8 100644 --- a/eu/seamless/game.toml +++ b/eu/seamless/game.toml @@ -20,6 +20,7 @@ known_md5 = ["5029ec8fee56ec6bb6316bd16ee77d54"] known_sha1 = ["2505d958b4439d04844cadd3f968fc7432a48e00"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../../psxrecomp/bios/SCPH1001.toml" # ABI/address metadata only; runtime uses bundled OpenBIOS. seeds = "../seeds/boot.txt" out_dir = "../../build-eu-seamless/generated/boot" diff --git a/eu/tests/test_low_input_latency.c b/eu/tests/test_low_input_latency.c new file mode 100644 index 0000000..d48a592 --- /dev/null +++ b/eu/tests/test_low_input_latency.c @@ -0,0 +1,79 @@ +/* Optional original-disc contract test: pass owner-extracted executables. + * No retail bytes, cards, rendering or network are needed in this fixture. */ +#include +#include +#include +#include +#include "../low_input_latency.c" +static unsigned char image[0x200000]; +static uint32_t sample,last_legacy; +static int allowed,reads; +uint8_t psx_mod_read_byte(uint32_t pc) {return (pc&0x1fffffffu)>(8*i));} +int psx_mod_read_digital_pad(uint32_t player,PSXModDigitalPadReader *r,uint32_t old,uint32_t *out) { + (void)r;last_legacy=old;assert(player==0);++reads;*out=sample;return allowed; +} +int psx_mod_register_session_reset_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} +int psx_mod_register_activation_plugin(const char *id,PSXModActivationCallback cb) {(void)id;(void)cb;return 1;} +int psx_mod_register_savestate_plugin(const char *id,PSXModVBlankCallback cb) {(void)id;(void)cb;return 1;} +int psx_mod_register_instruction_plugin(const char *id,uint32_t pc,uint32_t word,PSXModFunctionEntryCallback cb) { + (void)id;(void)cb; + for(unsigned i=0;i0x20000);fclose(f); + const TombaInputProfile *p=0; + for(unsigned i=0;istart,q->size)==q->hash && body_hash(q->extra,q->extra_size)==q->extra_hash) {p=q;break;} + } + assert(p);assert(psx_mod_read_word(p->hook)==p->instruction); + if(p->kind)put(p->field1,1); + else {image[p->field0&0x1fffffu]=0;put(p->field1,4);} + CPUState before;memset(&before,0x5a,sizeof before);before.gpr[31]=p->caller; + if(p->kind)before.gpr[4]=p->field0; + allowed=1;reads=0; + for(unsigned bit=0;bit<18;++bit) { + sample=bit<16?1u<kind?((sample>>8)&0xffu)|((sample&0xffu)<<8):(uint32_t)(int32_t)(int16_t)(uint16_t)sample; + read_return(&cpu,p->hook);assert(!memcmp(&cpu,&expected,sizeof cpu)); + } + assert(reads==18); + /* Both verified Japanese callers reach the same original P1 store. + * Exercise each, not just the first matching regional profile. */ + if(p->kind) { + for(unsigned i=0;ikind!=p->kind || q->hash!=p->hash || q->start!=p->start)continue; + CPUState call=before,expected=before; + call.gpr[31]=expected.gpr[31]=q->caller; + sample=0xa55au;expected.gpr[2]=0x5aa5u; + int count=reads;read_return(&call,q->hook); + assert(reads==count+1 && !memcmp(&call,&expected,sizeof call)); + } + } + /* Priming/fallback receives canonical buttons while preserving the + * original guest result when the host reader declines. */ + CPUState raw=before;raw.gpr[2]=0x1234u;CPUState original=raw; + allowed=0;read_return(&raw,p->hook); + assert(last_legacy==(p->kind?0x3412u:0x1234u)); + assert(!memcmp(&raw,&original,sizeof raw)); + CPUState cpu=before;allowed=0;read_return(&cpu,p->hook);assert(!memcmp(&cpu,&before,sizeof cpu)); + allowed=1;cpu=before;cpu.gpr[31]=0;CPUState bad=cpu;reads=0; + read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&bad,sizeof cpu)); + if(p->kind)put(p->field1,0);else put(p->field1,7); + cpu=before;reads=0;read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + if(p->kind)put(p->field1,1);else put(p->field1,4); + image[p->start&0x1fffffu]^=1;cpu=before;reads=0; + read_return(&cpu,p->hook);assert(!reads && !memcmp(&cpu,&before,sizeof cpu)); + printf("PASS original input ABI: %s\n",argv[input]); + } +} diff --git a/eu2/CMakeLists.txt b/eu2/CMakeLists.txt index 55a9bcf..dec2eb6 100644 --- a/eu2/CMakeLists.txt +++ b/eu2/CMakeLists.txt @@ -12,7 +12,10 @@ option(TOMBI_EU2_GEOMETRY_VALIDATE "Private original-vs-HLE differential checks" if(TOMBI_EU2_PLAYER_RELEASE AND PSX_DEBUG_TOOLS) message(FATAL_ERROR "Player releases must use PSX_DEBUG_TOOLS=OFF") endif() -set(eu2_mods NONE) +set(input_mods "${CMAKE_CURRENT_BINARY_DIR}/input-preloaded") +file(COPY "${TOMBA_ROOT}/mods/preloaded/packages/tombi.enhancement.low-input-latency" + DESTINATION "${input_mods}/packages") +set(eu2_mods "${input_mods}") if(TOMBI_EU2_SEAMLESS) find_package(Python3 REQUIRED COMPONENTS Interpreter) execute_process(COMMAND "${Python3_EXECUTABLE}" -I -B @@ -112,3 +115,5 @@ if(TOMBI_EU2_SEAMLESS) endif() endif() endif() + +target_sources(tombi-eu2 PRIVATE "${TOMBA_ROOT}/src/mods/tomba_low_input_latency_plugin.c") diff --git a/eu2/game.toml b/eu2/game.toml index e2296ee..a5424ef 100644 --- a/eu2/game.toml +++ b/eu2/game.toml @@ -20,6 +20,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2/generated" diff --git a/eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/eu2/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/eu2/pc.toml b/eu2/pc.toml index 607e6af..ac88004 100644 --- a/eu2/pc.toml +++ b/eu2/pc.toml @@ -20,6 +20,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2-seamless/generated/boot" diff --git a/eu2/release/game.toml b/eu2/release/game.toml index ac5d116..7da2068 100644 --- a/eu2/release/game.toml +++ b/eu2/release/game.toml @@ -16,6 +16,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] seeds = "seeds/boot.txt" out_dir = "cache/generated" strict = true diff --git a/eu2/rev1/game.toml b/eu2/rev1/game.toml index 23b2525..131c8eb 100644 --- a/eu2/rev1/game.toml +++ b/eu2/rev1/game.toml @@ -20,6 +20,7 @@ known_md5 = ["2456298c5fc4c7686e739f02ee165363"] known_sha1 = ["ec50074b6fd98f611bae11e3de68d6e76ffdd6d4"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2/generated" diff --git a/eu2/rev1/pc.toml b/eu2/rev1/pc.toml index ca06825..42c7f66 100644 --- a/eu2/rev1/pc.toml +++ b/eu2/rev1/pc.toml @@ -20,6 +20,7 @@ known_md5 = ["2456298c5fc4c7686e739f02ee165363"] known_sha1 = ["ec50074b6fd98f611bae11e3de68d6e76ffdd6d4"] [recompiler] +mod_instruction_sites = ["0x800263B4"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2-seamless/generated/boot" diff --git a/eu2/rev1/release/game.toml b/eu2/rev1/release/game.toml index 592dbf0..e71fe7b 100644 --- a/eu2/rev1/release/game.toml +++ b/eu2/rev1/release/game.toml @@ -16,6 +16,7 @@ known_md5 = ["2456298c5fc4c7686e739f02ee165363"] known_sha1 = ["ec50074b6fd98f611bae11e3de68d6e76ffdd6d4"] [recompiler] +mod_instruction_sites = ["0x800263B4"] seeds = "seeds/boot.txt" out_dir = "cache/generated" strict = true diff --git a/eu2/seamless.toml b/eu2/seamless.toml index 5ab20d3..2eaf70e 100644 --- a/eu2/seamless.toml +++ b/eu2/seamless.toml @@ -20,6 +20,7 @@ known_md5 = ["5cbbd8800409d992ce4b6d0df4bd7fab"] known_sha1 = ["684dbf0ad03f872511046d40d3cc85b17d839dbf"] [recompiler] +mod_instruction_sites = ["0x80026368"] bios_config = "../psxrecomp/bios/SCPH1001.toml" seeds = "seeds/boot.txt" out_dir = "../build-eu2-seamless/generated/boot" diff --git a/game.toml b/game.toml index 0912e58..119d757 100644 --- a/game.toml +++ b/game.toml @@ -27,6 +27,7 @@ known_sha1 = [ ] [recompiler] +mod_instruction_sites = ["0x80028EEC"] seeds = "seeds/ghidra_funcs.txt" out_dir = "generated" strict = true diff --git a/jp/CMakeLists.txt b/jp/CMakeLists.txt index 71899ea..47acc5f 100644 --- a/jp/CMakeLists.txt +++ b/jp/CMakeLists.txt @@ -11,7 +11,10 @@ include("${PSXRECOMP_ROOT}/runtime/runtime.cmake") # adapter, jp/widescreen_profile.py). option(TOMBA_JP_ENHANCEMENTS "Seamless Loading, FMV Skip and Widescreen" OFF) set(TOMBA_JP_GENERATED_DIR "${TOMBA_ROOT}/build-jp/generated" CACHE PATH "Japanese boot code directory") -set(jp_mods NONE) +set(input_mods "${CMAKE_CURRENT_BINARY_DIR}/input-preloaded") +file(COPY "${TOMBA_ROOT}/mods/preloaded/packages/tombi.enhancement.low-input-latency" + DESTINATION "${input_mods}/packages") +set(jp_mods "${input_mods}") if(TOMBA_JP_ENHANCEMENTS) find_package(Python3 REQUIRED COMPONENTS Interpreter) get_filename_component(jp_generated "${TOMBA_JP_GENERATED_DIR}" DIRECTORY) @@ -57,3 +60,5 @@ endif() # The shared launcher already embeds the UTF-8 application manifest # (recomp-ui windows/recomp_utf8.manifest); a second manifest resource would # collide with it at link time. + +target_sources(tomba-jp PRIVATE "${TOMBA_ROOT}/src/mods/tomba_low_input_latency_plugin.c") diff --git a/jp/game.toml b/jp/game.toml index 4fc1bf1..cb1c24b 100644 --- a/jp/game.toml +++ b/jp/game.toml @@ -17,6 +17,7 @@ known_md5 = ["788922f281da3337e03c7113f9bd9bf5"] known_sha1 = ["c86baf702303e7e0963b99aff307b1c40e40478a"] [recompiler] +mod_instruction_sites = ["0x800207C8"] seeds = "seeds/boot.txt" out_dir = "../build-jp/generated" strict = true diff --git a/jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/jp/mods/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/jp/pc.toml b/jp/pc.toml index c43edd3..72c3932 100644 --- a/jp/pc.toml +++ b/jp/pc.toml @@ -17,6 +17,7 @@ known_md5 = ["788922f281da3337e03c7113f9bd9bf5"] known_sha1 = ["c86baf702303e7e0963b99aff307b1c40e40478a"] [recompiler] +mod_instruction_sites = ["0x800207C8"] seeds = "seeds/boot.txt" out_dir = "../build-jp/rc2/generated/boot" strict = true diff --git a/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml b/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml new file mode 100644 index 0000000..dcaaade --- /dev/null +++ b/mods/preloaded/packages/tombi.enhancement.low-input-latency/0.1.1/manifest.toml @@ -0,0 +1,30 @@ +format_version = 5 +id = "tombi.enhancement.low-input-latency" +version = "0.1.1" +name = "Low Input Latency" +author = "TombiRecomp contributors" +description = "Uses fresher digital input without changing the original simulation rate." +resolver = "declarative" +save_compatibility = "shared" + +[[target]] +game_id = "SCUS-94236" + +[[target]] +game_id = "SCES-01330" + +[[target]] +game_id = "SCES-01331" + +[[target]] +game_id = "SLPS-01144" + +[[feature]] +id = "low-input-latency" +name = "Low Input Latency" +description = "Reduces digital input packet delay. Verified bindings for USA, both European language sets and Japan. Offline single-player D-pad; other controller modes keep original input. Applies at the next game start." +group = "Gameplay" +default_enabled = false +[[plugin]] +feature = "low-input-latency" +id = "tombi.low-input-latency" diff --git a/src/mods/tomba_input_profiles.inc b/src/mods/tomba_input_profiles.inc new file mode 100644 index 0000000..acfa9d8 --- /dev/null +++ b/src/mods/tomba_input_profiles.inc @@ -0,0 +1,39 @@ +/* Original-disc instruction/body contracts; no retail code. */ +static const TombaInputProfile input_profiles[] = { + /* us/SCUS_942.36 */ + {0x80028eecu,0x3e00008u,0x8002241cu,0x80028d70u,0x184u,0xa81e6469u,0x800223e0u,0x94u,0xfd11d463u,0x8009eb58u,0x8009c978u,0x0u}, + /* eu1/EXE/MAIN0.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xafd11216u,0x8001f850u,0x94u,0x4ed36982u,0x8009f7f0u,0x8009d610u,0x0u}, + /* eu1/EXE/MAIN1.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xad1d2e07u,0x8001f850u,0x94u,0xb8d62702u,0x8009f810u,0x8009d630u,0x0u}, + /* eu1/EXE/MAIN2.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xc09ccae7u,0x8001f850u,0x94u,0xbee40a82u,0x8009f830u,0x8009d650u,0x0u}, + /* eu1/EXE/MAIN3.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x7f0a61cfu,0x8001f850u,0x94u,0xc4712b12u,0x8009f828u,0x8009d648u,0x0u}, + /* eu1/EXE/MAIN4.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xc09ccae7u,0x8001f850u,0x94u,0xbee40a82u,0x8009f830u,0x8009d650u,0x0u}, + /* eu2/EXE/MAIN0.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0xccf4bcd5u,0x8001f850u,0x94u,0xbfb96c65u,0x8009f2e0u,0x8009d100u,0x0u}, + /* eu2/EXE/MAIN5.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x8155e9eau,0x8001f850u,0x94u,0xe320cdc5u,0x8009f308u,0x8009d128u,0x0u}, + /* eu2/EXE/MAIN6.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x9bc8bb6u,0x8001f850u,0x94u,0xe320cdc5u,0x8009f308u,0x8009d128u,0x0u}, + /* eu2/EXE/MAIN7.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x1bb6f19du,0x8001f850u,0x94u,0x712c5305u,0x8009f2e8u,0x8009d108u,0x0u}, + /* eu2/EXE/MAIN8.EXE */ + {0x80026368u,0x3e00008u,0x8001f88cu,0x800261f0u,0x180u,0x9bc8bb6u,0x8001f850u,0x94u,0xe320cdc5u,0x8009f308u,0x8009d128u,0x0u}, + /* eu2r1/EXE/MAIN0.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0xafd11216u,0x8001f850u,0x94u,0x4ed36982u,0x8009f7f0u,0x8009d610u,0x0u}, + /* eu2r1/EXE/MAIN5.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x51e0290fu,0x8001f850u,0x94u,0x6f08c132u,0x8009f818u,0x8009d638u,0x0u}, + /* eu2r1/EXE/MAIN6.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x8fc5b777u,0x8001f850u,0x94u,0x17f62302u,0x8009f820u,0x8009d640u,0x0u}, + /* eu2r1/EXE/MAIN7.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x75eb7997u,0x8001f850u,0x94u,0xdd502482u,0x8009f800u,0x8009d620u,0x0u}, + /* eu2r1/EXE/MAIN8.EXE */ + {0x800263b4u,0x3e00008u,0x8001f88cu,0x8002623cu,0x180u,0x8fc5b777u,0x8001f850u,0x94u,0x17f62302u,0x8009f820u,0x8009d640u,0x0u}, + /* jp/SLPS_011.44 */ + {0x800207c8u,0xa4820000u,0x80018ac8u,0x80020794u,0xc8u,0xde4a36eau,0x80016960u,0xc0u,0xaf653575u,0x8009f200u,0x8009bfe4u,0x1u}, + /* jp/SLPS_011.44/alternate-game-loop */ + {0x800207c8u,0xa4820000u,0x800196b4u,0x80020794u,0xc8u,0xde4a36eau,0x80016960u,0xc0u,0xaf653575u,0x8009f200u,0x8009bfe4u,0x1u}, +}; diff --git a/src/mods/tomba_low_input_latency_plugin.c b/src/mods/tomba_low_input_latency_plugin.c new file mode 100644 index 0000000..f42b2fa --- /dev/null +++ b/src/mods/tomba_low_input_latency_plugin.c @@ -0,0 +1,68 @@ +/* Execute the original input path in full. US/EU substitute only the + * digital reader return for their audited P1 caller. Japan substitutes the + * cached P1 halfword at the game's original store, after the load delay; + * its BIOS poll, packed P2 word and initialization remain original. + * The original outer routine still owns edges, locks, actions and rumble. */ +#include "mod_plugins.h" +#include "cpu_state.h" +#include +#define PLUGIN "tombi.low-input-latency" +typedef struct TombaInputProfile { + uint32_t hook,instruction,caller,start,size,hash; + uint32_t extra,extra_size,extra_hash,field0,field1,kind; +} TombaInputProfile; +#include "tomba_input_profiles.inc" +static PSXModDigitalPadReader reader={sizeof reader}; +static const TombaInputProfile *current; +static uint32_t body_hash(uint32_t address,unsigned bytes) { + uint32_t h=2166136261u; + for(unsigned i=0;ihook!=pc || p->caller!=cpu->gpr[31])continue; + if(base!=p->start || size!=p->size) { + base=p->start;size=p->size;hash=body_hash(base,size); + } + if(hash!=p->hash)continue; + if(extra!=p->extra || extra_size!=p->extra_size) { + extra=p->extra;extra_size=p->extra_size;extra_hash=body_hash(extra,extra_size); + } + if(extra_hash!=p->extra_hash)continue; + if(p->kind) { + if(cpu->gpr[4]!=p->field0 || psx_mod_read_word(p->field1)!=1 || + psx_mod_read_word(p->caller-8)!=0x0c0081e5u || psx_mod_read_word(p->caller-4)!=0)continue; + } else if(psx_mod_read_byte(p->field0)!=0 || psx_mod_read_half(p->field1)!=4)continue; + match=p;break; + } + if(!match) {reset();return;} + if(current!=match) { + if(!current || current->start!=match->start || current->hash!=match->hash)reset(); + current=match; + } + /* Japan's legacy BIOS ReadPad layout swaps the two button bytes. + * Keep the shared host reader canonical in both directions; only convert + * the checked guest boundary. Original P2 and SIO data stay untouched. */ + uint32_t legacy=cpu->gpr[2],held; + if(match->kind)legacy=((legacy>>8)&0xffu)|((legacy&0xffu)<<8); + if(psx_mod_read_digital_pad(0,&reader,legacy,&held)) + cpu->gpr[2]=match->kind?((held>>8)&0xffu)|((held&0xffu)<<8): + (uint32_t)(int32_t)(int16_t)(uint16_t)held; +} +PSX_MOD_CONSTRUCTOR(tombi_register_low_input_latency) { + (void)psx_mod_register_session_reset_plugin(PLUGIN,reset); + (void)psx_mod_register_activation_plugin(PLUGIN,reset); + (void)psx_mod_register_savestate_plugin(PLUGIN,reset); + for(unsigned i=0;ihook)duplicate=1; + if(!duplicate)(void)psx_mod_register_instruction_plugin(PLUGIN,p->hook,p->instruction,read_return); + } +}