Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
format_version = 1
id = "megaman-x.enhancement.sram"
version = "1.0.0"
name = "Mega Man X SRAM"
name = "Mega Man X Password Saves"
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."
description = "Adds mod-gated sidecar persistence for Mega Man X password-save data while leaving the stock no-SRAM cartridge map unchanged."
license = "MIT"
resolver = "declarative"
save_compatibility = "shared"
Expand All @@ -14,8 +14,8 @@ 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."
name = "Password Saves"
description = "Persist the latest password in a sidecar save file. Disable this for stock password-only behavior."
group = "Saves"
default_enabled = true

Expand Down
8 changes: 4 additions & 4 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1310,10 +1310,10 @@ int main(int argc, char** argv) {
#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.sram_path = "saves/save.srm"; /* Password Saves owns this sidecar
file for launcher presentation only;
the stock MMX cartridge still has no
guest-visible SRAM. */
gi.password_sram_path = "saves/save.srm";
gi.password_sram_label = "Last Password";
gi.password_sram_size = 2048;
Expand Down
49 changes: 36 additions & 13 deletions src/mods/mmx_sram_plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
#include <string.h>

#define MMX_SRAM_PLUGIN "megaman-x.sram"
#define MMX_SYNTHETIC_SRAM_BYTES 2048u
#define MMX_PASSWORD_SAVE_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);
void RtlEnsureSaveDir(void);
void RtlSramFilePath(char *buf, size_t buflen);

typedef struct MmxSramPassword {
char magic[8];
Expand All @@ -27,6 +26,7 @@ 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 uint8_t g_mmx_password_save[MMX_PASSWORD_SAVE_BYTES];

static void mmx_sram_frame(void);

Expand All @@ -35,16 +35,39 @@ int mmx_sram_mod_active(void) {
}

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);
static void mmx_sram_read_file(void) {
char path[128];
RtlSramFilePath(path, sizeof(path));
FILE *f = fopen(path, "rb");
if (!f)
return;
if (fread(g_mmx_password_save, 1, sizeof(g_mmx_password_save), f) !=
sizeof(g_mmx_password_save))
memset(g_mmx_password_save, 0, sizeof(g_mmx_password_save));
fclose(f);
}

static void mmx_sram_write_file(void) {
char path[128], bak[140];
RtlEnsureSaveDir();
RtlSramFilePath(path, sizeof(path));
snprintf(bak, sizeof(bak), "%s.bak", path);
rename(path, bak);
FILE *f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "MMX SRAM mod: unable to write %s\n", path);
return;
}
fwrite(g_mmx_password_save, 1, sizeof(g_mmx_password_save), f);
fclose(f);
}

static void mmx_sram_activate(void) {
memset(g_mmx_password_save, 0, sizeof(g_mmx_password_save));
mmx_sram_read_file();
g_mmx_sram_active = 1;
g_mmx_password_prefill_frames = 0;
(void)snes_mod_register_frame_callback(mmx_sram_frame);
Expand All @@ -69,10 +92,10 @@ static int mmx_sram_password_valid(const MmxSramPassword *slot) {
}

static MmxSramPassword *mmx_sram_password_slot(void) {
if (!g_sram ||
g_sram_size < (int)(MMX_PASSWORD_SRAM_OFFSET + sizeof(MmxSramPassword)))
if (sizeof(g_mmx_password_save) <
MMX_PASSWORD_SRAM_OFFSET + sizeof(MmxSramPassword))
return NULL;
return (MmxSramPassword *)(g_sram + MMX_PASSWORD_SRAM_OFFSET);
return (MmxSramPassword *)(g_mmx_password_save + MMX_PASSWORD_SRAM_OFFSET);
}

static int mmx_password_digits_equal(const uint8_t *a, const uint8_t *b) {
Expand Down Expand Up @@ -135,7 +158,7 @@ static void mmx_sram_store_password(const uint8_t digits[MMX_PASSWORD_DIGITS]) {
slot->version = 1;
memcpy(slot->digits, digits, MMX_PASSWORD_DIGITS);
slot->checksum = mmx_password_checksum(slot->digits);
RtlWriteSram();
mmx_sram_write_file();
}

static void mmx_sram_prefill_password(const MmxSramPassword *slot) {
Expand Down