ROM Hack Pokémon Sacred Gold & Storm Silver

  • Thread starter Thread starter Drayano
  • Start date Start date
  • Views Views 5,642,947
  • Replies Replies 2,286
  • Likes Likes 61
I signed up here to report on how I solved the Safari Zone gate issues for my save plus the additional findings along the way. I did use AI in the process of solving my issue and the following content was authored by AI based on that work:

Sacred Gold — Safari Zone arc (R47/R48 + D47 Gate) flag regression: full save-repair recipe

I hit a save state where the entire Safari Zone Rocket arc had reverted — Routes 47 and 48 both had their grunts back, and the Safari Zone Gate (D47) was permanently blocked by the two door grunts — despite the arc being well past in my playthrough. The in-game Olivine flag-fixer NPC did not restore any of it. Tracing through the save with DSPRE Reloaded + a Node save-editor:

What got reset
  • Every "grunt defeated" visibility flag the arc's completion scripts set, across both the routes and the gate: 0x3FDF, 0x3FE0, 0x3FE3, 0x3FE4, 0x3FE5, 0x3FE6, 0x3FE8. All seven were cleared.
  • 0x3FE1 stayed set (it's set earlier in the arc), and 0x3FFF (the arc master) also stayed set — which is why scripted progression past the arc still worked, masking the regression.
  • 0x3FE7 is a transient flag set during R47 and cleared at the arc finale — its correct done-state is OFF, so don't restore it.

Where the flags map on D47 (header 174), Event File 168
  • Door-blocking grunts: Overworlds 15 and 16, both visibility flag 0x3FE0 (decimal 16352), trigger script 18 on contact. This is the flag specifically controlling the gate door — easy to overlook because it's separate from the route grunts' flags.
  • Post-arc ally NPCs (OW 17/18/19) hang off 0x3FDF, separate from the door pair.
  • The "Run script 22 when Var 24880 (0x6130) is 0" trigger on the same map is a red herring for this bug — 0x6130 is shared with the Saffron Rocket-grunt arc (Saffron's Script 22 writes 0x612E/F/130 as a contiguous trio), and in my case it was already 2 from Saffron. The door grunts persist purely on the flag, not the trigger.

Save-level workaround (if you're stuck)
  • Set 0x3FDF, 0x3FE0, 0x3FE3, 0x3FE4, 0x3FE5, 0x3FE6, 0x3FE8. Leave 0x3FE7 clear. Recompute the P1 general-block checksum: CRC16-CCITT-FALSE over [0x40000, 0x4F618), stored u16 LE at 0x4F626.
  • PKHeX can't reach the 0x3FE_ range flags in HGSS — it caps the flag spinner below that — so this needs a small Node or Python script, not the editor UI.
  • Restoring the whole 0x3FE_ region as a blanket fix (~57 flags) causes a black-screen crash on Safari-area load. The fix has to be targeted to the seven flags above.

What's actually wrong (best guess)
  • Some event during my playthrough cleared all seven of these flags as a group. I can't pinpoint the trigger — possibly a scripted reset elsewhere, possibly an emulator/save-sync issue on my end.
  • Whatever the cause, the in-game Olivine flag-fixer NPC's restore list does not cover this arc (it didn't repair any of the seven for me). Lowest-risk ROM-side patch would be to add this seven-flag set to the fixer's restore list, gated on 0x3FFF being set so it only re-applies after the arc was supposed to be complete.

Happy to share the Node save-editor / DSPRE .ev parser scripts I wrote if useful.
CC: @DeadSkullzJr @Drayano
 
Last edited by TwistedMaro,
Well I have been down a bit of a rabbit hole since fixing my Safari Zone bug leading to my working on a full reverse engineering of Sacred Gold and Soul Silver. I am currently working on a tool which breaks out as much as possible into an entirely data-driven, composable and layered pipeline that injects maximally minimal code hooks.

I believe I have found a number of bugs in the 1.1.2 patches here by doing this because I am doing a full baseline data extraction including scripts and maps and events and flags. This data gives me baseline HGSS in a fundamentally different form because I've designed it to be agnostic to bytes in the ROMs. Instead like stated above I just have hooks for every type of edit and a data driven build pipeline that compiles down and in that compile process I can make sure that flags and basically all data is non-colliding and going into a safe space for that type of edit.

So with full HGSS extracted to data and then full SacredGold/StormSilver extracted to data I can do a diff and see the full semantic edit set that makes up SGSS. Then running the validation over this and against vanilla baseline I can already see many flag collisions or instances where for example the two grunts blocking the door in the Safari Zone seem to by copy pasted and not unique or where flags are used by multiple scripts or events.

The good news:
A - I can statically detect a bunch of bugs or potential bugs in the current versions (including what is likely causing them to work poorly on flash carts)
B - The rebuild pipeline uses highly-abstracted concepts instead of being tied directly to ROM bytes or concepts so flags get symbol-named etc
C - Due to the way re-assembly is working I am able to rebuild SGSS avoiding those classes of bugs entirely

I will keep ya'll posted as this progresses and share more if I have something concrete.


EDIT:
Oh yeah the way I am building this tool/pipeline is not SPECIFIC to this hack but I am using this hack as my dogfooding subject. In reality it will mean that you could edit a bunch of JSON/images/audio files and build whole new hacks AND use the extraction pipeline to reverse engineer any hack built on HGSS into abstract data layers.

EDIT 2:
This is fundamentally different from decomp work which I would have considered trying to tackle but my research lead me to the realization that the effort/vision there is too grand for what I actually am hoping to get out of this so instead this is more of a generalized code patching with minimal hook injection and an intelligent data entity build system which doesn't rely on ROM specifics to augment the systems which have been hooked and cataloged.
 
Last edited by TwistedMaro,
I'm trying to patch on mobile and it's giving me xdelta3 internal error
Post automatically merged:

I'm trying to patch on mobile and it's giving me xdelta3 internal error
 
Hello everyone! This is my first post, so may the moderators excuse me if it violates any conduct rules.
I made this account specifically to share something I found about a very specific bug in Pokemon Sacred Gold, regarding Moltres.

TL;DR A friend of mine got a corrupt save after catching moltres outside its original area. He asked Gemini AI and it said it was the AP. He came to me for help trying to recover the save on another emulator, that didn't work, so i made Fable 5 dig into the bytes to fix it and it managed to do so. Python script below.

DISCLAIMER SECTION

AI DISCLAIMER: I do not endorse the use of closed-source AI. It is an arms-race-to-the-bottom where few people profit immensely and most of us simply pay the price in terms of water, electricity, housing, taxes, climate change and, perhaps most importantly, mental capacity and critical thinking. The technology has its uses and can be beneficial if used responsibly. And that's a huge IF. In any case, I decided to share what it found as I believe it could be of much help to anyone encountering the issue and perhaps even to the mod creator himself.

CODE DISCLAIMER: the code is provided as-is. You, as the user, are solely responsible for anything you do with it and anything you do to your data. Always backup data you care about before messing with it in any way.

FOLLOW UP DISCLAIMER: in the following section, the AI states "if the script doesn't fix your file post your save so we can have a look at it". I cannot guarantee having time and/or access to the AI to fix a different issue. However if I can I will try to help.

END OF DISCLAIMER SECTION

So a little bit of context: my friends have been playing Pokemon Sacred Gold on Android via MelonDS. One of them found and caught Moltres and his save was corrupted, ie when loading the game would let him choose the slot but a black screen would follow. We tried a couple of fixes suggested by Gemini, but nothing worked. As a last ditch effort, having access to a subscription to Claude AI and being Fable 5 still available, we just gave it the save file and in less than a hour (and a couple hundred dollars of equivalent tokens) later it managed to fix it.

While I have a little experience with programming and embedded development, I know nothing about the amazing world of Nintendo's, modding, roms and emulators, so I can't really speak on anything the AI made. But I can say that it does indeed work.

I have done some research both on this website and elsewhere on the internet and there doesn't seem to be a definitive fix. I cannot post external links as this is a new profile, but if you search for a post named "Need help on pokemon storm silver" on Reddit you will find a comment saying that this issue can be fixed by editing the save file and changing the character's position, then reloading, with a user saying it saved his file.

START OF AI-WRITTEN SECTION

TL;DR: The infamous "caught Moltres, saved, now Continue = black screen forever" bug corrupts exactly ONE BYTE of your save file. The Python script below repairs it. You keep Moltres and all your progress. Verified working.

Who this is for
You caught Moltres in the Cinnabar Gym (Sacred Gold / Storm Silver), saved, and now the game shows your trainer info on the Continue screen but black-screens forever when you load. New Game still works, and the crash follows the save file to any emulator or even a clean vanilla HeartGold ROM. This has been reported in this thread since 2018 (Jimmyzzao, shutout55138, AMH9000, SaintDane) with no fix ever found - people were told to restore a backup or start over.

What is actually wrong
I did a byte-level forensic comparison of the save's two internal slots (HGSS keeps your last two saves; the backup slot still holds the pre-capture state) and bisected the difference by live-testing each candidate byte in an emulator. Result: the Moltres capture script performs a stray write into the save's Pal Pad region - byte 0x18BA of the active slot flips to 0x84. All checksums remain valid, so the game happily accepts the save and then hangs during field initialization at Continue. It is NOT anti-piracy (HGSS AP never writes to the save), it is not the Moltres data itself (that's why editing Moltres out with PKHeX never fixed it - the Pokemon in your box is perfectly fine).

The fix
Restore that one byte from your own backup slot and recompute the checksum. The script below does it automatically, makes a .backup copy first, and refuses to touch anything if your save doesn't match the known pattern. Works on raw 512 KiB .sav files (melonDS, flashcarts, SRAM exports) and DeSmuME .dsv files.

1. Install Python 3 (redacted python website link because new account, just google python dowload) if you don't have it
2. Save the code below as fix_moltres_blackscreen.py
3. Run: python fix_moltres_blackscreen.py "path/to/your/save.sav"
4. Load the game - it works, and Moltres is waiting in your PC box

Code:
#!/usr/bin/env python3
"""
Fix for Pokemon Sacred Gold / Storm Silver (HGSS Drayano hack):
"black screen when selecting Continue after catching Moltres in the Cinnabar Gym"

Restores the single corrupted byte (0x18BA of the active save slot, inside the
Pal Pad area) from the save's own backup slot and recomputes the CRC16.
You KEEP Moltres (it is in your PC box), your Pokedex entry, EXP, items.

Usage:  python fix_moltres_blackscreen.py "path/to/your save file"

Accepts: raw 512 KiB .sav (melonDS, flashcarts, DeSmuME SRAM export)
         or DeSmuME .dsv (footer preserved). A .backup copy is created first.

Only works if you saved exactly ONCE after catching Moltres (the normal case,
since after that save you can never load the game again). If both slots are
corrupted the script refuses and changes nothing.
"""
import struct, shutil, sys, os

GENERAL_SIZE = 0xF628      # HGSS "general" block size, footer included
HALF = 0x40000             # the save contains two ping-pong slots
POISON_OFF = 0x18BA        # inside SAVE_PALPAD, entry[1]+0x52
MAGIC = 0x20060623


def crc16_ccitt(data):
    crc = 0xFFFF
    for byte in data:
        crc ^= byte << 8
        for _ in range(8):
            crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF
    return crc


def main(path):
    raw = open(path, "rb").read()
    body, tail = raw[:512 * 1024], raw[512 * 1024:]   # keep any .dsv footer / padding
    if len(body) < 512 * 1024:
        sys.exit("ERROR: file is smaller than 512 KiB - not a HGSS save.")
    d = bytearray(body)

    halves = []
    for i, base in enumerate((0, HALF)):
        cnt, size, magic = struct.unpack_from("<III", d, base + GENERAL_SIZE - 0x10)
        stored = struct.unpack_from("<H", d, base + GENERAL_SIZE - 2)[0]
        calc = crc16_ccitt(d[base:base + GENERAL_SIZE - 0x10])
        ok = (magic == MAGIC and size == GENERAL_SIZE and stored == calc)
        halves.append((cnt, ok))
        print(f"slot{i}: save-counter={cnt}  structure={'OK' if ok else 'INVALID'}")
    if not (halves[0][1] and halves[1][1]):
        sys.exit("ERROR: a save slot failed validation - this is a different kind of "
                 "corruption, this fix does not apply. No changes made.")

    newer = 0 if halves[0][0] >= halves[1][0] else 1   # the slot the game loads
    older = 1 - newer
    nb, ob = newer * HALF, older * HALF
    cur, prev = d[nb + POISON_OFF], d[ob + POISON_OFF]
    print(f"byte 0x18BA: loaded-slot={cur:02X}  backup-slot={prev:02X}")

    if cur == prev:
        sys.exit("The two slots agree at 0x18BA - your save does not match the known "
                 "corruption pattern (or you saved more than once after the capture). "
                 "No changes made. Consider posting your save for analysis.")

    shutil.copyfile(path, path + ".backup")
    d[nb + POISON_OFF] = prev
    struct.pack_into("<H", d, nb + GENERAL_SIZE - 2,
                     crc16_ccitt(d[nb:nb + GENERAL_SIZE - 0x10]))
    open(path, "wb").write(bytes(d) + tail)
    print(f"FIXED: byte 0x18BA restored {cur:02X}->{prev:02X} in slot{newer}, "
          f"checksum recomputed. Backup saved as: {os.path.basename(path)}.backup")


if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.exit(f"Usage: python {os.path.basename(sys.argv[0])} \"path/to/save.sav\"")
    main(sys.argv[1])

Limitations / notes
- Works if you saved once after the capture (the normal case - after that save you couldn't load anymore). If you managed to save twice before quitting, both slots are corrupted and the script will refuse; post your save and it can be looked at.
- Separate issue, don't confuse them: on DeSmuME with a base ROM that is NOT anti-piracy patched, HGSS's AP also black-screens Continue on late-game saves (it corrupts the heap proportionally to your badge count). If the fixed save still black-screens ONLY on DeSmuME but loads on melonDS, your ROM needs the AP patch - the save is fine.
- The same class of bug (the hack's event scripts writing out of bounds) is known for other Sacred Gold events (the Mew event visibly spams RAM errors on some emulators), so save in a separate slot/backup before doing special legendary events.

For the mod's maintainers - root-cause details for fixing it in the hack
(Drayano / DeadSkullzJr's 1.1.x line - everything below references the pret/pokeheartgold decompilation naming.)

- Where/when: the corruption happens in RAM during the Moltres capture event in the Cinnabar Gym map (MAP_SEAFOAM_ISLANDS_CINNABAR_GYM = 457, internal MAP_D11R0106), BEFORE any save: AMH9000's 2019 report crashed just by using Dig to leave the room after the capture, without saving. Saving afterwards checksums the already-corrupted state, which is why the resulting save is structurally valid but poisoned.

- What exactly gets corrupted: a 16-bit in-place add of +0x40 on the little-endian u16 at offset 0x18BA of the loaded save block (observed 0xC444 -> 0xC484). That address belongs to the SAVE_PALPAD chunk (chunk base 0x17E0, 16 entries x 0x88; the hit is entry[1]+0x52) - nothing a gym event script should ever touch.

- Likely mechanism (testable hypothesis): an out-of-bounds script-variable write. The corrupted u16 sits exactly 2 x 1387 bytes past the event-work variable array (u16[368] at save offset 0xDE4), i.e. it is where "variable index 1387" (would-be var ID 0x456B) would land - far outside the valid array, with no bounds check in the engine. An AddVar-style command with a wrong operand (or a computed var index overflowing) matches both the location and the exact +0x40 delta. The Mew event's documented RAM-error spam on melonDS/TWiLightMenu looks like the same bug class in another custom script, and this could also explain the readme's long-standing "flags have a tendency to reset for some unknown reason" issue.

- How to catch it red-handed: set a write watchpoint on save-data+0x18BA (melonDS has a GDB stub; DeSmuME dev builds have memory watchpoints), run the Moltres capture, and the faulting ARM9 PC should land in the script interpreter's variable/math command handler - then the offending command is in map D11R0106's script container. Auditing all custom legendary-event scripts for out-of-range var IDs would likely fix Mew and Moltres in one pass.

- Why it black-screens only on Continue: the poisoned byte is consumed during post-load field-system init (infinite hang, not an assert). New Game never reads it, and the Continue menu only reads trainer info - hence the classic "menu fine, load dead" symptom. Restoring the single byte toggles the hang on/off 100% reproducibly.

How it was found (for the curious)
The save's two internal slots are consecutive saves - one before the capture, one after. Diffing them gives every byte the capture session changed (~800 bytes). Cross-referencing the pret/pokeheartgold decompilation mapped each change to its game structure (party EXP, Pokedex flags for Moltres, roamer movement, map objects - all legitimate), and live-bisecting the remaining candidates in an automated emulator harness isolated the single byte whose restoration makes the save load. Fix verified end-to-end: game loads, Moltres in box, all progress intact, checksums valid.

Hope this saves someone's 200-hour file. It saved mine.

END OF AI-WRITTEN SECTION

P.S.: Not sure why Claude speaks in first person, assuming I would be posting everything as-is. Either way I take no credit beyond thinking that the AI could handle such low level things well.

Have a good one everyone, enjoy your games.
 

Site & Scene News

Popular threads in this forum