Play Pokémon HeartGold / SoulSilver on a single screen.
The top screen on its own. The menu is drawn onto the world, in the game's own words and font — nothing is swapped in from the touch screen — and takes its colours from the theme you choose when patching.
v0.3 — play-tested on French SoulSilver. v0.2b was played start to finish on French SoulSilver and HeartGold, and play-tested on US HeartGold. A few of v0.3's new screens have not been played yet. See Compatibility and Status.
The DS puts half of HGSS on the touch screen — the bag, the party, the Pokédex, and critically the battle command menu. That makes the game awkward on a handheld like the Steam Deck, where you want one screen filling the panel. This patch reroutes the game so that whatever you actually need is on the top screen, automatically.
Mostly this is rerouting, not redrawing. The DS has two independent 2D engines, and
POWCNT1 bit 15 decides which one drives the upper LCD; the patch rewrites the ~87 places
the game sets that, and adds a small resident hook for the cases that need to change while
you play.
The exceptions are the battle command menu, binary questions in the overworld, the questions Oak asks at the start of a new game, the overworld menu, and the shop and PC menus. They are drawn onto the scene rather than swapped in. Even there nothing is replaced: the labels are the game's own words in the game's own font, pulled out of the ROM you supply and rasterised at patch time, so a French build says ATTAQUE / SAC / POKéMON / FUITE and an English one FIGHT / BAG / POKéMON / RUN without the patcher knowing which is which.
| on the top screen | |
|---|---|
| Overworld | the world (unchanged) |
| Bag, party, Pokégear, main menu, trainer card, options | their UI |
| Pokédex | the species grid, and the area map on a Pokémon's detail level |
| PC box | the box itself — moving Pokémon, item storage |
| Shops and the PC | their menus drawn on the world; the screen only moves when you choose |
| Field binary questions | the world, with Yes / No drawn in the dialogue window |
| Field lists | the world, with the game's own top-screen list on it — fossil revival, the apricorn juice stand and most one-off NPC choices |
| Overworld menu (X) | the world, with the menu drawn on it; B closes it |
| Name entry | the keyboard |
| New game | Oak's speech, with his questions answered on his own screen |
| Flying, Dig, Escape Rope | the animation, from the moment you confirm |
| Evolution | the Pokémon, its move-learning text, and compact Yes / No choices |
| Battles | the scene, with the commands drawn on it - the Safari Zone's, the Bug Contest's and Pal Park's too; the screen only moves once you choose |
Battles are the interesting part. The scene never leaves the top screen while you are choosing: FIGHT / BAG / POKéMON / RUN are drawn onto it, laid out the way the game's own cursor moves, and the highlight follows the D-pad. The Safari Zone, the Bug Contest, Pal Park and the first rival battle get their own words (BALL / BAIT / MUD / RUN, BALL in place of BAG, FIGHT alone), and a double battle's second Pokémon gets CANCEL, as on the touch screen. The screen only changes once you have actually picked something — the move list, the bag and the party come up then, and the scene returns the instant the turn starts. Binary prompts ("… changer de Pokémon?") get a small Oui / Non box in the same message window instead of taking the screen.
Nothing is timed: there is no delay to wait through and no press that moves the screen out from under you.
Binary questions in the overworld work the same way. Nurse Joy's offer, and every other field prompt that uses the shared two-button controller, keeps the world visible and puts Yes / No or Oui / Non in the right-hand end of the dialogue window. Up and down move the game's own selection; A and B keep their normal meanings.
The move-learning questions after an evolution use a separate controller, but receive the same treatment. When the game asks whether to forget a move, or later whether to stop trying to teach it, the Pokémon remains visible and the existing localized Yes / No tiles mirror the game's own selection. The patch does not handle the buttons or result.
The overworld menu works the same way: X draws it in the top-right corner of the world instead of taking the screen, laid out two columns by four because that is the grid the game's own cursor walks. In the Safari Zone, the Bug Contest, Pal Park and the Union Room it is drawn in each one's own arrangement, as the touch screen shows it. Picking an entry opens it on the top screen exactly as before.
Shops and the PC are drawn the same way — the clerk's ACHETER / VENDRE / QUITTER, and
all three of the PC's menus including its 2×3 storage grid. These are the same object
underneath, so one renderer covers them, but a menu is only drawn if the patch recognises
it by its own words — every one of them, not just the first. Anything it has not seen is
handed to the game's own top-screen list instead: HGSS still contains the list window it
inherited from Diamond and Pearl, which reads the very same menu, so an unknown NPC choice
appears on the world in the game's own window and words rather than taking the screen.
That includes lists whose entries are laid out in two lines or in columns on the touch
screen — Battle Frontier prize shops, the Game Corner's coins, drink machines, the
day-care's level column, Pokéathlon courses — which are folded onto one line, and the
Battle Frontier move tutors' lists, whose prices line up in a column. A list that would land on a
box the script already has up (Mom's savings) and an entry holding formatting the patch
does not know keep the touch screen.
Starting a new game works the same way. GARÇON / FILLE for the gender question and OUI / NON for the confirmations are drawn beside Oak's text, so the screen stays on him throughout — it only changes for the naming keyboard, which genuinely needs the touch screen. The selected band takes the theme's colour, and the obsolete lower-screen indicator is suppressed before it can flash. The tutorial menu still swaps: three options of running text will not fit beside the question the way two words do.
L + R swaps the screens manually at any time, as an escape hatch. The game never sees those two keys, so the swap does not also turn a bag pocket or, with the L=A button mode, press A; a lone L or R reaches the game a tenth of a second late.
Everything the patch draws takes its colours from one of three schemes — the menus on the world, and the band behind whichever choice is selected:
--theme |
menus | selected choice |
|---|---|---|
classic |
white, like the game's own dialogue box | grey |
heartgold |
white with a gold tint and frame | gold |
soulsilver |
white with a silver tint and frame | silver |
Colour only: nothing moves and no wording changes. The window picks the theme matching
your game, and you can override it; patch.py uses classic unless you pass --theme.
- Your own dump of Pokémon HeartGold or SoulSilver (French SoulSilver/HeartGold and US HeartGold are the tested ones — see Compatibility)
- Nothing else, if you use the standalone patcher for Windows, macOS or Linux
- To run from source: Python 3.9+ and
ndspy —
pip install ndspy
Windows, macOS, Linux: download the patcher for your system, drop your ROM on the window
and press Patch. Nothing to install - it picks the theme to match your game, and you can
override it. Each system also has a command-line version that takes the same arguments as
patch.py. See docs/USAGE.md for which file is which.
From source:
python patch.py "Pokemon - Version Argent SoulSilver.nds" --theme heartgoldThat writes Pokemon - Version Argent SoulSilver-1screen.nds next to it. Your original
file is never modified.
Then in your emulator show only the top screen — in melonDS,
Config → Screen sizing → Top only. Full setup notes and the complete control list are in
docs/USAGE.md.
Save files are ordinary battery saves and work on the unpatched ROM too, so you can move between the two freely.
| game code | game | status |
|---|---|---|
IPGF |
SoulSilver (France) | play-tested — the reference build |
IPKF |
HeartGold (France) | play-tested, including a fresh start through Oak's speech |
IPKE |
HeartGold (USA) | play-tested — the opening, the drawn menus and the themes |
other IPGx / IPKx |
HGSS, other languages | untested — good chance of working |
Nothing is hardcoded per region. The patcher finds the code it edits by instruction shape, and every address the resident hook needs is resolved from the ROM being patched: the game-state block is read out of the main loop's own literal, the overlay values come from the overlay table, the Pokédex entry point is found structurally, and the battle phase word is read from the overlay loader's own literal. Measured across French and US, ARM9 code addresses are identical and only data shifts (US = FR − 0x20); the patcher measures that shift twice from independent places and refuses if they disagree, rather than emitting a subtly broken ROM.
If you try another language, please open an issue either way.
Diamond/Pearl/Platinum are not supported — different code, different overlays.
v0.3 has been played on French SoulSilver in melonDS: Oak's speech, ordinary battles,
evolutions, the X menu, saving, shops, the PC, drink machines, the day-care, field
questions and L + R, with the Pokédex, PC boxes, Fly, Dig and Escape Rope, the Pokégear and
the opening cinematic checked again. Its other changes were built against the full
decompilation and their routines unit-tested under a CPU emulator (tools/test_hook.py),
but have not been played: the Safari Zone, Bug Contest, Pal Park and first-rival battle
menus, double battles, the X menu in those zones and the Union Room, the Battle Frontier
lists and move tutors, the Game Corner and Mom's savings. What follows is what v0.2b was
played through.
Everything below has been played through, not merely implemented: the overworld and its menus, battles including sub-screens, the bag through every pocket, the party menu including reordering and summaries, the Pokédex with its area map, the PC boxes for Pokémon and items, the Pokégear's map, radio, phone and configuration, shops and NPC choice menus, flying, Dig and Escape Rope, evolution and the moves it teaches, and a new game from the title screen through Oak's speech to walking outdoors.
The drawn menus — the battle commands, the overworld menu, the shop's and all three of the PC's — were each play-tested through several rounds of correction, as were the field and post-evolution prompts, the opening cinematic, the move relearner and saving from the overworld menu. The three colour themes were checked on both editions.
Notably that covers the bag and the PC box, which were still raw assembly in pret's decompilation when the source audit was done — nothing but play could have cleared them — and the Pokégear, which was the largest touch surface found anywhere in the audit.
Known gaps, in order of how likely you are to meet them:
- 19 applications set no display routing of their own and inherit whatever is current. Each has now been checked against the decompilation: Oak's speech, the Pal Pad and the rankings viewer were wrong and are handled, as are three screens outside that list (the evolution scene, the fly map and the move relearner). The verdict for every one is in docs/AUDIT.md; the Pal Pad, the rankings, berry pots, the apricorn box and mail have still not been played.
- Some screens deliberately stay on the touch screen because they genuinely need the stylus — the trainer-card signature, the Ruins of Alph puzzles, the Pokéathlon and the Union Room's group drawing. These are not fixable: moving them up would make them visible but untappable.
If you find a screen on the wrong side, a savestate makes it quick to diagnose — see docs/USAGE.md.
Touch is physically the lower LCD, so anything shown on top cannot be tapped — every screen the patch moves up has to be fully navigable with buttons.
A source-level audit against the decomp found the Ruins of Alph puzzle to be the only genuinely touch-only app it covers, and cleared everything else it covers. The bag and PC box were raw assembly there and could only be cleared by playing them, which has now been done. See docs/AUDIT.md for the method, the results and what is still unchecked.
The whole toolchain is here: ROM unpacking, the display-site scanner, savestate diffing,
build and emulator-driving helpers. patch-gui.py is the window; tools/build_exe.py
freezes it into the released Windows, macOS and Linux builds and tools/make_icon.py draws its icon.
tools/test_hook.py runs the resident hook's routines under an ARM emulator; GitHub
Actions rebuilds the hook and runs those tests on every push, and a v* tag builds the
patcher on every system and drafts the release. See docs/DEVELOPMENT.md to
get set up, docs/AUDIT.md for the button-navigation audit and what
the decomp says about the game's own screen routing, and
docs/FINDINGS.md for the reverse-engineering
write-up — memory addresses, what each discovery was, and the dead ends, so none of it
needs rediscovering.
This repository contains no game data — no ROMs, no save files, no extracted assets. It is a patcher that operates on a dump you provide of a game you own. Pokémon is a trademark of Nintendo / Creatures / GAME FREAK, who are not affiliated with this project.
Tooling is MIT licensed — see LICENSE.
Overlay identities come from pret/pokeheartgold, whose overlay numbering matches the retail ROM. Struct layouts and the names in docs/DECOMP.md were checked against the full decompilation at ctrl-cheeb-del/pokeheartgold; it is used as a reference only, and nothing from it is copied here. ROM handling uses ndspy. Built and tested against melonDS.

