From b7e43c9e388787b2a6c17f81539d938240385d13 Mon Sep 17 00:00:00 2001 From: Matt Stanley <5354790+mstan@users.noreply.github.com> Date: Sat, 15 Aug 2026 22:36:10 -0700 Subject: [PATCH 1/2] Expose launcher gamepad bindings --- recomp-ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recomp-ui b/recomp-ui index 653a642..7badd17 160000 --- a/recomp-ui +++ b/recomp-ui @@ -1 +1 @@ -Subproject commit 653a642c6246d0abfc9e76e17f0dd23ad58a2f6f +Subproject commit 7badd17339d7663e21bf6c78569691978d2f8f8b From 8ff798313189a3e139aa216a9a10173afa0fae3f Mon Sep 17 00:00:00 2001 From: Matt Stanley <5354790+mstan@users.noreply.github.com> Date: Sun, 16 Aug 2026 00:32:20 -0700 Subject: [PATCH 2/2] Remove MMX password saves mod from release --- CMakeLists.txt | 3 +- .../1.0.0/manifest.toml | 24 --- src/main.c | 53 +++-- src/mods/mmx_sram_plugin.c | 194 ------------------ tools/build-linux.sh | 2 +- tools/test_appimage_layout.sh | 2 - 6 files changed, 34 insertions(+), 244 deletions(-) delete mode 100644 mods/preloaded/packages/megaman-x.enhancement.sram/1.0.0/manifest.toml delete mode 100644 src/mods/mmx_sram_plugin.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 09ac406..c047646 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,6 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR) src/mmx_spc_player.c src/mods/mmx_widescreen_plugin.c src/mods/mmx_msu1_plugin.c - src/mods/mmx_sram_plugin.c # OpenGL function loader third_party/gl_core/gl_core_3_1.c @@ -170,6 +169,8 @@ add_mmx_variant(MegaManXSNESRecomp set(MMX_PRELOADED_MODS "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded") if(TARGET MegaManXSNESRecomp AND EXISTS "${MMX_PRELOADED_MODS}/packages") add_custom_command(TARGET MegaManXSNESRecomp POST_BUILD + COMMAND ${CMAKE_COMMAND} -E remove_directory + "$/mods" COMMAND ${CMAKE_COMMAND} -E copy_directory "${MMX_PRELOADED_MODS}" "$/mods" diff --git a/mods/preloaded/packages/megaman-x.enhancement.sram/1.0.0/manifest.toml b/mods/preloaded/packages/megaman-x.enhancement.sram/1.0.0/manifest.toml deleted file mode 100644 index d6cfcf7..0000000 --- a/mods/preloaded/packages/megaman-x.enhancement.sram/1.0.0/manifest.toml +++ /dev/null @@ -1,24 +0,0 @@ -format_version = 1 -id = "megaman-x.enhancement.sram" -version = "1.0.0" -name = "Mega Man X SRAM" -author = "mstan" -description = "Adds mod-gated battery-backed SRAM storage for Mega Man X password-save enhancements while leaving the stock password game unchanged when disabled." -license = "MIT" -resolver = "declarative" -save_compatibility = "shared" - -[[target]] -game_id = "megaman-x-us" -rom_sha256 = "b8f70a6e7fb93819f79693578887e2c11e196bdf1ac6ddc7cb924b1ad0be2d32" - -[[feature]] -id = "sram" -name = "SRAM Saves" -description = "Expose a small battery-backed SRAM area for password-save data. Disable this for stock no-SRAM behavior." -group = "Saves" -default_enabled = true - -[[plugin]] -feature = "sram" -id = "megaman-x.sram" diff --git a/src/main.c b/src/main.c index addb3ae..6c03514 100644 --- a/src/main.c +++ b/src/main.c @@ -912,17 +912,33 @@ static const struct RendererFuncs kSdlRendererFuncs = { }; -void MkDir(const char *s) { -#if defined(_WIN32) - _mkdir(s); -#else - mkdir(s, 0755); -#endif -} - -#include -#include "cpu_state.h" -#include "cpu_trace.h" +void MkDir(const char *s) { +#if defined(_WIN32) + _mkdir(s); +#else + mkdir(s, 0755); +#endif +} + +static void RmDirIfEmpty(const char *s) { +#if defined(_WIN32) + _rmdir(s); +#else + rmdir(s); +#endif +} + +static void RetireMmxSramModPackage(void) { +#if SNESRECOMP_ENABLE_MODS && !MMX_VARIANT_JP + remove("mods/packages/megaman-x.enhancement.sram/1.0.0/manifest.toml"); + RmDirIfEmpty("mods/packages/megaman-x.enhancement.sram/1.0.0"); + RmDirIfEmpty("mods/packages/megaman-x.enhancement.sram"); +#endif +} + +#include +#include "cpu_state.h" +#include "cpu_trace.h" #include "post_mortem.h" extern uint8_t g_ram[0x20000]; static void dump_sprite_state(void) { @@ -1190,7 +1206,7 @@ int main(int argc, char** argv) { * so headered and unheadered dumps both verify against the same hash. */ static char rom_path_buf[512]; int mods_ready = 0; - { + { /* 1.5 MiB LoROM, Rev 1 (v1.1). SHA-256 over the unheadered payload (the * launcher strips a 512-byte SMC copier header before hashing). * USA "Mega Man X (USA) (Rev 1)" crc32 DED53C64 @@ -1212,6 +1228,7 @@ int main(int argc, char** argv) { }; #endif #if SNESRECOMP_ENABLE_MODS && !MMX_VARIANT_JP + RetireMmxSramModPackage(); mods_ready = snes_mod_runtime_initialize_c( "mods", "megaman-x-us", "b8f70a6e7fb93819f79693578887e2c11e196bdf1ac6ddc7cb924b1ad0be2d32"); @@ -1308,16 +1325,8 @@ int main(int argc, char** argv) { SnesLauncherCGameInfo gi; memset(&gi, 0, sizeof(gi)); #endif - gi.name = MMX_GAME_NAME; - gi.region = MMX_GAME_REGION; - gi.sram_path = "saves/save.srm"; /* The SRAM Saves mod allocates this - for password-save enhancements. - With the mod disabled, stock MMX - still has no guest-visible SRAM. */ - gi.password_sram_path = "saves/save.srm"; - gi.password_sram_label = "Last Password"; - gi.password_sram_size = 2048; - gi.password_sram_offset = 0x100; + gi.name = MMX_GAME_NAME; + gi.region = MMX_GAME_REGION; gi.expected_crc = MMX_ROM_CRC32; gi.has_expected_crc = 1; gi.known_sha256 = &kMmxRomSha256; /* single accepted digest */ diff --git a/src/mods/mmx_sram_plugin.c b/src/mods/mmx_sram_plugin.c deleted file mode 100644 index 969bc11..0000000 --- a/src/mods/mmx_sram_plugin.c +++ /dev/null @@ -1,194 +0,0 @@ -#include "mod_runtime.h" - -#include -#include -#include - -#define MMX_SRAM_PLUGIN "megaman-x.sram" -#define MMX_SYNTHETIC_SRAM_BYTES 2048u -#define MMX_PASSWORD_DIGITS 12u -#define MMX_PASSWORD_WRAM 0x1e60u -#define MMX_PASSWORD_SRAM_OFFSET 0x100u -#define MMX_PASSWORD_PREFILL_GRACE_FRAMES 90 - -extern uint8_t g_ram[0x20000]; -extern uint8_t *g_sram; -extern int g_sram_size; -void RtlWriteSram(void); - -typedef struct MmxSramPassword { - char magic[8]; - uint8_t version; - uint8_t digits[MMX_PASSWORD_DIGITS]; - uint8_t checksum; -} MmxSramPassword; - -static const char kMmxSramMagic[8] = {'M', 'M', 'X', 'P', 'A', 'S', 'S', 0}; - -static int g_mmx_sram_active; -static int g_mmx_password_prefill_frames; - -static void mmx_sram_frame(void); - -int mmx_sram_mod_active(void) { - return g_mmx_sram_active; -} - -static void mmx_sram_reset(void) { - g_mmx_sram_active = 0; - g_mmx_password_prefill_frames = 0; -} - -static void mmx_sram_activate(void) { - if (!snes_mod_request_synthetic_sram_c(MMX_SYNTHETIC_SRAM_BYTES)) { - fprintf(stderr, "MMX SRAM mod: failed to request %u bytes\n", - (unsigned)MMX_SYNTHETIC_SRAM_BYTES); - return; - } - g_mmx_sram_active = 1; - g_mmx_password_prefill_frames = 0; - (void)snes_mod_register_frame_callback(mmx_sram_frame); -} - -static uint8_t mmx_password_checksum(const uint8_t digits[MMX_PASSWORD_DIGITS]) { - uint8_t checksum = 0x5a; - for (uint32_t i = 0; i < MMX_PASSWORD_DIGITS; i++) - checksum = (uint8_t)((checksum * 33u) ^ digits[i]); - return checksum; -} - -static int mmx_sram_password_valid(const MmxSramPassword *slot) { - if (!slot || memcmp(slot->magic, kMmxSramMagic, sizeof(slot->magic)) != 0 || - slot->version != 1) - return 0; - for (uint32_t i = 0; i < MMX_PASSWORD_DIGITS; i++) { - if (slot->digits[i] < 1 || slot->digits[i] > 8) - return 0; - } - return slot->checksum == mmx_password_checksum(slot->digits); -} - -static MmxSramPassword *mmx_sram_password_slot(void) { - if (!g_sram || - g_sram_size < (int)(MMX_PASSWORD_SRAM_OFFSET + sizeof(MmxSramPassword))) - return NULL; - return (MmxSramPassword *)(g_sram + MMX_PASSWORD_SRAM_OFFSET); -} - -static int mmx_password_digits_equal(const uint8_t *a, const uint8_t *b) { - return memcmp(a, b, MMX_PASSWORD_DIGITS) == 0; -} - -static int mmx_live_password_valid(uint8_t digits[MMX_PASSWORD_DIGITS], - int *is_default) { - int any_non_default = 0; - for (uint32_t i = 0; i < MMX_PASSWORD_DIGITS; i++) { - uint8_t zero_based = g_ram[MMX_PASSWORD_WRAM + i]; - if (zero_based > 7) - return 0; - digits[i] = (uint8_t)(zero_based + 1); - if (zero_based != 0) - any_non_default = 1; - } - if (is_default) - *is_default = !any_non_default; - return 1; -} - -static int mmx_password_screen_id_ok(uint8_t screen_id) { - return screen_id == 3 || screen_id == 0x23 || screen_id == 0x24; -} - -static int mmx_password_prefill_context_visible(void) { - uint8_t screen_id = g_ram[0x1f7a]; - return g_ram[0x003b] == 0 && g_ram[0x003c] <= 1 && - mmx_password_screen_id_ok(screen_id); -} - -static int mmx_password_capture_screen_visible(void) { - if (g_ram[0x003b] != 0 || g_ram[0x003c] != 1 || - !mmx_password_screen_id_ok(g_ram[0x1f7a])) - return 0; - - uint8_t digits[MMX_PASSWORD_DIGITS]; - if (!mmx_live_password_valid(digits, NULL)) - return 0; - - uint8_t cursor_x = g_ram[0x1e4c]; - uint8_t cursor_y = g_ram[0x1e4f]; - if (cursor_x > 3 || cursor_y > 3) - return 0; - - uint8_t pixel_x = g_ram[0x0e6d]; - return pixel_x == 0x30 || pixel_x == 0x38 || pixel_x == 0x68 || - pixel_x == 0x98 || pixel_x == 0xc8 || pixel_x == 0xd0; -} - -static void mmx_sram_store_password(const uint8_t digits[MMX_PASSWORD_DIGITS]) { - MmxSramPassword *slot = mmx_sram_password_slot(); - if (!slot) - return; - if (mmx_sram_password_valid(slot) && - mmx_password_digits_equal(slot->digits, digits)) - return; - memcpy(slot->magic, kMmxSramMagic, sizeof(slot->magic)); - slot->version = 1; - memcpy(slot->digits, digits, MMX_PASSWORD_DIGITS); - slot->checksum = mmx_password_checksum(slot->digits); - RtlWriteSram(); -} - -static void mmx_sram_prefill_password(const MmxSramPassword *slot) { - for (uint32_t i = 0; i < MMX_PASSWORD_DIGITS; i++) - g_ram[MMX_PASSWORD_WRAM + i] = (uint8_t)(slot->digits[i] - 1); -} - -static void mmx_sram_frame(void) { - if (!g_mmx_sram_active) - return; - const MmxSramPassword *slot = mmx_sram_password_slot(); - if (!slot) - return; - - int slot_valid = mmx_sram_password_valid(slot); - int prefill_context = mmx_password_prefill_context_visible(); - - uint8_t live_digits[MMX_PASSWORD_DIGITS]; - int is_default = 0; - if (!mmx_live_password_valid(live_digits, &is_default)) { - if (slot_valid && prefill_context && g_mmx_password_prefill_frames > 0) { - mmx_sram_prefill_password(slot); - g_mmx_password_prefill_frames--; - } - return; - } - - if (slot_valid && prefill_context) { - int live_is_saved = mmx_password_digits_equal(live_digits, slot->digits); - if (is_default) - g_mmx_password_prefill_frames = MMX_PASSWORD_PREFILL_GRACE_FRAMES; - if (g_mmx_password_prefill_frames > 0 && - (is_default || live_is_saved)) { - mmx_sram_prefill_password(slot); - g_mmx_password_prefill_frames--; - return; - } - if (!is_default && !live_is_saved) - g_mmx_password_prefill_frames = 0; - } else { - g_mmx_password_prefill_frames = 0; - } - - int visible = mmx_password_capture_screen_visible(); - if (!visible) - return; - - if (!is_default) - mmx_sram_store_password(live_digits); -} - -SNES_MOD_CONSTRUCTOR(mmx_register_sram_plugin) { - (void)snes_mod_register_reset_callback(mmx_sram_reset); - (void)snes_mod_register_activation_plugin(MMX_SRAM_PLUGIN, - mmx_sram_activate); -} diff --git a/tools/build-linux.sh b/tools/build-linux.sh index 635f162..c1fb84d 100644 --- a/tools/build-linux.sh +++ b/tools/build-linux.sh @@ -59,7 +59,6 @@ BOXART="recomp/launcher/boxart.tga" # AppImage icon source (optional) REQUIRED_MOD_MANIFESTS=( "packages/megaman-x.enhancement.widescreen/1.0.0/manifest.toml" "packages/megaman-x.enhancement.msu1/1.0.0/manifest.toml" - "packages/megaman-x.enhancement.sram/1.0.0/manifest.toml" ) PROD_CMAKE_FLAGS=( -DSNESRECOMP_ENABLE_TRACE=OFF ) DEBUG_CMAKE_FLAGS=( -DSNESRECOMP_ENABLE_TRACE=ON ) @@ -302,6 +301,7 @@ ROMDIR="\$(dirname "\$(readlink -f "\$SELF")")" # never a dir where a file belongs); user files are left alone entirely. if [ -d "\$HERE/usr/bin/mods" ] && [ -w "\$ROMDIR" ]; then mkdir -p "\$ROMDIR/mods" 2>/dev/null || true + rm -rf "\$ROMDIR/mods/packages/megaman-x.enhancement.sram" 2>/dev/null || true cp -a "\$HERE/usr/bin/mods/." "\$ROMDIR/mods/" 2>/dev/null || true chmod -R u+rwX "\$ROMDIR/mods" 2>/dev/null || true fi diff --git a/tools/test_appimage_layout.sh b/tools/test_appimage_layout.sh index e29c750..f305fe7 100644 --- a/tools/test_appimage_layout.sh +++ b/tools/test_appimage_layout.sh @@ -52,8 +52,6 @@ test -f "$state1/mods/packages/megaman-x.enhancement.widescreen/1.0.0/manifest.t echo "FAIL: release mod catalog not seeded beside the AppImage" >&2; exit 1; } test -f "$state1/mods/packages/megaman-x.enhancement.msu1/1.0.0/manifest.toml" || { echo "FAIL: MSU-1 mod package not seeded beside the AppImage" >&2; exit 1; } -test -f "$state1/mods/packages/megaman-x.enhancement.sram/1.0.0/manifest.toml" || { - echo "FAIL: SRAM mod package not seeded beside the AppImage" >&2; exit 1; } # 2. User state survives a relaunch: an edited config line and a # user-installed third-party mod package.