DragonWarrior4NESRecomp
DragonWarrior4NESRecomp is a port of Dragon Warrior IV for the NES with AI-generated HD graphics and Game Genie cheat code support.
- Type: PC port
- Game: Dragon Warrior IV
- Runs on Linux and macOS
- By arichmondphoto
- Latest release v1.0.71, 2026-10-29
- Source: https://github.com/arichmondphoto/DragonWarrior4NESRecomp
README
Dragon Warrior IV (USA) — Recompiled (NESRecomp port)
Static recompilation of Dragon Warrior IV (NES) using mstan/nesrecomp, cycle backend. Also uses RetroPortingToolKit/recomp-ui console-agnostic launcher and in-game settings UI for static recompilation game ports. Includes a HD Graphics Pack and supports Game Genie cheat codes. HD graphics generated by AI (OpenAI GPT-Image-2.5-sunburst for character sprites, maps, titles, etc. Open AI GPT-Image-2 for monsters). The rest of the project was coded with Claude.
Not 100% play tested - gameplay seems normal. If you find a bug or a graphic that was missed in the HD pack, please report it with details. Screenshots are appreciated.
I do not have access to a Windows machine, so this was authored with what I have available with testing in mind (MacOS on Apple Silicon and Linux x86_64).
ROM (not included)
Dragon Warrior IV (USA), NES 2.0 header, 524,304 bytes.
- CRC32 (no header):
506E259D - SHA-1 (no header):
1b3dc265ba0d7e4b2cef9b3f8b0be0847df67c3c - Board: MMC1 SUROM — 512 KiB PRG, 8 KiB CHR-RAM, 8 KiB battery PRG-RAM
User brings their own legally-obtained ROM. I will not provide a ROM or tell you where to find one - don't ask.
Build and Play on macOS (Apple Silicon)
Download latest release, unzip and follow directions in READ ME FIRST.txt.
Building requires the Xcode command line tools and Homebrew.
git clone https://github.com/arichmondphoto/DragonWarrior4NESRecomp.git # private repo: sign in when prompted
cd DragonWarrior4NESRecomp
./build_macos.sh "/path/to/Dragon Warrior IV (USA).nes"
The first run installs cmake/ninja/sdl2/python, builds the game, packages it as
Dragon Warrior IV.app and installs it in /Applications (or ~/Applications).
Open it from Launchpad, Spotlight or the Applications folder. After pulling updates,
run ./build_macos.sh again to rebuild and reinstall.
- Settings, battery saves and save states:
~/Library/Application Support/DragonWarrior4Recomp(kept across rebuilds; anything in the oldbuild/savesis copied over once). - The app bundles SDL2, the launcher assets and your ROM — don't share the app.
DW4_NO_INSTALL=1 ./build_macos.shleaves the app indist/;./play_macos.shstill runs the unpackaged build.- A download for other players:
./build_macos.sh --releasebuilds the app without your ROM and zips it with aREAD ME FIRST.txt(installing, opening an app from outside the App Store, adding the ROM) asdist/DragonWarriorIV-macOS-<arch>-<version>.zip. Players pick their own ROM with Browse For ROM on first launch (remembered inrom.cfg), or drop a.nesfile in the data folder; the HD pack starts switched on. The app runs on Macs of the build machine's architecture, and on the oldest macOS its bundled Homebrew libraries allow (both are written into the read-me). It is ad-hoc signed, not notarized, so players confirm it once in System Settings > Privacy & Security. - Cheats: the launcher's Cheats page takes Game Genie codes (6 or 8 letters), each with an optional name and
an on/off switch. They are saved in
cheats.txtin the same folder and apply when the game starts. Codes that rewrite the game's code run it on the built-in interpreter for that session (same speed in practice).
Keyboard: arrows = D-pad, Z = A, X = B, Return = Start, \ = Select. Gamepads work out of the box.
Esc = menu, F7 = save states, F8 = rewind, Tab = fast-forward, F11 = fullscreen (on a Mac keyboard you may need fn+F7 etc.).
Build and Play on Linux (AppImage)
Download latest release, unzip and follow directions in READ ME FIRST.txt.
Requires Git and Podman or Docker (sudo pacman -S podman on Arch/Manjaro).
git clone https://github.com/arichmondphoto/DragonWarrior4NESRecomp.git
cd DragonWarrior4NESRecomp
./build_linux.sh "/path/to/Dragon Warrior IV (USA).nes"
./dist/Dragon_Warrior_IV-*.AppImage
- The build runs in a Debian 12 container (
linux/Dockerfile: toolchain, Python, SDL2 built from source with X11/Wayland/PipeWire/PulseAudio/ALSA/controller support, each loaded at run time), so it works the same on any distro and the AppImage runs on distros with glibc 2.36 or newer (2022 onward).--nativebuilds on the host instead (it prints the packages each distro needs; the result then needs a distro as new as yours). - The AppImage holds the game, SDL2, the launcher's assets and the HD pack (on by default), never the ROM:
pick it with Browse For ROM on first launch (remembered), or drop a
.nesin the data folder. Saves, settings,cheats.txtand the Mods selection live in~/.local/share/DragonWarrior4Recomp($XDG_DATA_HOME), set by the AppImage'sAppRun(NESRECOMP_DATA_DIR, patch 0017). ./build_linux.sh --releasealso packs it with a player read-me (linux/READ_ME_FIRST.txt) asdist/DragonWarriorIV-linux-<arch>-<version>.tar.gz.- The steps themselves are
linux/build_in_env.sh(build dirbuild-linux/; the HD pack's captures are cached there).
Build (headless baseline)
git submodule update --init
cmake -S nesrecomp/runner/cyc/project -B build-headless -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DNESRECOMP_ROM="$PWD/rom/DragonWarriorIV.nes" -DNESRECOMP_GAME_CONFIG="$PWD/game.toml" -DNESRECOMP_HEADLESS=ON
cmake --build build-headless
./build-headless/nes_game rom/DragonWarriorIV.nes --frames 600 --screenshot boot.png
Windowed build: cmake -S . -B build (needs SDL2 dev package).
HD texture packs
HD packs (Mesen format) are enabled in the build (MODS HD_PACKS). Packs are
presentation-only: the game runs identically with them on or off (frame hashes
match over the Chapter 1 route).
Project standard: every HD pack is authored at 10x (<scale>10, 80x80
pixels per tile, a 2560x2400 picture — the presenter's ceiling), so it stays
sharp at every Window Scale from 1x to 10x.
- Text and windows — the font, window borders, cursors and arrows, smoothly
upscaled 10x from the game's own 8x8 tiles (
tools/smooth_pixels.py), so every letter keeps its Dragon Warrior IV shape. Generated from your ROM at build time. - Battle monsters — AI-generated artwork (
hd/monsters/source/DW4Sprites.jpg) replacing the 8-bit monsters.hd/monsters/mapping.jsonmaps each sheet sprite to a game monster ID;boxes.jsonholds each sprite's box on the sheet.
How the monsters are built (tools/build_hd_pack.sh):
tools/capture_monsters.pyforces one battle per monster ID by writing a fixed battle (slots$6E45-$6E48,$FF= empty; counts$6E49-$6E4C; flags$6E44, as the game's own setup at bank 24$A1ADdoes) into a save state taken just after the route's first encounter, and dumps each battle (tiles, palettes, sprites, name).tools/force_formation.pystarts any of the game's 34 fixed formations the same way.tools/capture_necrosaro.pystarts the Necrosaro battle (formation 25) and drives the real transformations: his HP ($727E) is set to 1 and Ragnar boosted, A is tapped until the form counter$6E7Egoes up, and each of the 8 forms is dumped.tools/make_monster_hdpack.pycuts each sprite from the sheet (transparency rebuilt from the black background), fits it to the original monster's pixels at 10x and slices it along the original's pieces: background cells (keyed by CHR bytes + exact palette) and 8x8 sprites (pattern table$0000, stored un-flipped). Repeated tiles inside a monster are told apart by a neighbouring tile (tileNearby) or byhmirror/vmirror. Necrosaro's forms share most of their tiles, so each form's tiles are gated on$6E7E(whenin mapping.json). The presenter draws a sprite's HD slice only where the 8-bit sprite pixel is opaque, so for these gated sprites the art is also keyed onto the blank background cells beneath them (gated on the cell's position), which keeps it from being clipped to the 8-bit silhouette.tools/render_monsters_hd.py+tools/score_hd_sweep.pyrender every monster withNESRECOMP_HDPACK_DEBUG=1and report any original pixels still showing.
To change a pairing, edit hd/monsters/mapping.json (sheet index → monster ID;
captures for sprites keyed on a special capture) and rebuild;
tools/review_sheet.py draws side-by-side review pages. no_art lists the game
monsters with no sprite on the sheet (sea monsters and two recolours); they keep
their 8-bit art.
How the map tiles are built (tools/build_hd_pack.sh):
tools/capture_field.pyloads every map (279 areas + the overworld) by rewriting the exit table at$05A0one step from Burland Castle's exit, and dumps each map's metatiles ($7600), sub-palettes ($76C0), CHR and palette.tools/field_sheet.pylists every unique map tile and each palette it is drawn with (field_tiles.json; without--json-onlyit also draws the numbered artwork sheets).tools/slice_field_atlases.pycuts the atlases into 160x160 tiles (finding each atlas's gutters, which image generators shift slightly).tools/make_field_hdpack.pykeys each tile's four 8x8 quarters by CHR bytes + palette for every palette version. A quarter shared by several map tiles is told apart by its neighbouring quarter (tileNearby); repeating ground tiles get their edges evened out so they tile without seams.- Palettes and night: the art is made for one palette per tile (stored in
hd/field/ids.json); every other palette the game draws it with (other towns and dungeons, and night, captured withcapture_field.py --night, which sets the clock at$62EDpast$78) gets a recoloured copy fitted from the palette colours. - Fades: screen transitions step every colour down by
$10three times; each version also gets entries for those three palettes, with the same art at the matching brightness, so the HD art fades instead of flashing 8-bit.
- Palettes and night: the art is made for one palette per tile (stored in
To redo art for some tiles: tools/export_ai_tiles.py exports every tile at
160x160 (pixel and smoothed versions, atlases, README), and
tools/scenario_batch.py runs them through Scenario in batch. (unused - used GPT-Image-2.5-sunburst instead)
- Walking characters — AI-generated artwork for every character that walks or
stands on the maps (party, townspeople, soldiers, animals, ships, map
monsters):
hd/sprites/source/<ID>.png, one 4x2 sheet per character in thetools/export_chatgpt_sprites.pylayout, generated with OpenAI's image API (tools/openai_sprites.py);hd/sprites/ids.jsonties each ID to its frames.
How the characters are built (tools/build_hd_pack.sh):
tools/capture_types.pycaptures all 108 character types from the game's character table (bank 8$979B, loaded by bank 28$9A37→ bank 8$85D3): it points a town's people at other table entries (patched ROM, via patch 0008) so every type is loaded, including the other chapters' heroes.tools/sprite_sheet.py --typeslists every character with its 8 frames (4 directions x 2 steps, WRAM$7200+slot*64), merged with what the map captures show (palettes, the party leader's own tiles).tools/check_ai_sprites.pyfits each sheet to the 8-bit outlines (whole sheet, then per frame) and reports frames that don't match.tools/make_sprite_hdpack.pycuts each frame into its four 8x8 sprites, keyed by CHR bytes + colours, for every sprite palette set (day, special, night and fade steps). Shared tiles are told apart byhmirror/vmirrorand neighbouring sprites (spriteNearby); people lying in bed (tiles the game turns 90/180/270 degrees) get the art turned the same way. The pack sets<options>spriteCells(patch 0009) so the art is drawn over the whole 8x8 sprite, not clipped to its 8-bit pixels.
-
Title screen — AI-generated painting in layers,
hd/title/layers/(layout.jsonplaces them; painted on the guides fromtools/export_title_kit.py).tools/make_title_hdpack.pydraws the sky with the castle and the blank plate as still<background>s, the two cloud strips as backgrounds that scroll with the game's two cloud bands (line:Y+wrap, patch 0013), and reveals each painted letter as the game types it (8x8 cells, every other title cell almost transparent). The phase (blue sky / sunset and every fade step) is read from one castle cell and one plate cell (tileAtPosition). Without*_daylayers the blue-sky look is recoloured from the sunset art along a light-to-colour ramp.tools/capture_extras.pyrecords the intro every 4th frame (title_seq.json, with when each letter is finished). -
Castle doors — the three in-between frames of the big doors opening (drawn straight into the nametable, so not map tiles):
hd/field/doors/DOOR1.png, cut bytools/door_frames.pyinto map tiles DR01-DR12 in every palette the closed door uses. -
Endor casino — captured without a playthrough by warping from the route (the exit table at
$05A0pointed at map 4, submap 1; coins in WRAM$62AD). Poker:tools/capture_casino.pywrites chosen cards into the deck (WRAM$7600, rank*4+suit,$34= joker) and deals them, so all 53 cards and the back are recorded;tools/compose_deck.pybuilds one consistent deck from parts of the owner's card sheetshd/casino/cards/C01-C09.png(chosen indeck.json: one blank face, one pip per suit, the four ace pictures, the joker and back used whole), laid out like the game's own cards, with ranks and J/Q/K letters fromglyphs.png(rendered once bytools/make_card_glyphs.py, Liberation Serif Bold);tools/make_casino_hdpack.pykeys it as 5x7-tile cards (shared tiles told apart bytileNearby). Slot machine:tools/capture_slots.pyplays three rounds (stand below a machine, step up to face it);tools/make_slots_hdpack.pydraws the owner's paintinghd/casino/slots/SLOT.pngas a still<background>(read from the JACKPOT sign, with the fade steps; resampled onto the game's layout by the straight-line x/y maps inhd/casino/slots/layout.json; its text window is always blanked, since the text tiles are partly transparent) and the seven reel symbols (8x8 sprites in 16x16 groups) cut out of the painting's own pay table (boxes inlayout.json;SYMBOLS.pngis the fallback); lamps and coin digits are smoothed.tools/export_casino.pymakes the art package for OpenAI's image API. Without the art only the smoothed tiles are added. The arena needs nothing extra (HD monsters and text). -
Night town crowd —
hd/field/sheets/N01.png(tiles T118-T127), cut bytools/slice_field_sheets.py. -
More monsters —
hd/monsters/extra/M###.png(monsters the atlases didn't cover). -
macOS:
./build_macos.sh(or./package_macos_app.sh) generates the pack and bundles it inside the app (Contents/Resources/mods; nothing is copied to your Library folder). In the launcher open Mods, turn on HD text, windows, monsters, maps, characters, title and casino (once — the setting is kept in your Mods catalog), then start.DW4_NO_HD=1skips it. -
Tiles are matched by their 16 CHR-RAM bytes and exact palettes. Bump
HD_VERSIONinpackage_macos_app.shwhen the pack's content changes.
Framework patches
patches/nesrecomp/*.patch are fixes to the pinned nesrecomp checkout, applied
by tools/apply_patches.sh (run automatically by build_macos.sh). It restores the files the
patches touch to the pinned commit, then applies every patch in order, so any earlier state is fine.
They are framework bugs, kept here until they can go upstream:
-
0001-cyc_sdl-hd-window-native-size.patch— with an HD pack the window was sized from the 4x HD picture (3072x2880 at Window Scale 3, clamped by macOS) and integer scaling stepped in HD-picture units, so the game drew small inside an oddly sized window. Window, logical size and integer steps now use native 256x240 pixels; HD textures are sampled linearly. -
0002-cyc-window-scale-max-10.patch— Window Scale was clamped to 8 in config.ini, the launcher bridge and the in-game menu; oneCYC_WINDOW_SCALE_MAX(10) now applies everywhere. -
0003-cyc_sdl-fit-window-to-display.patch— Window Scale is in points on macOS, so large scales (e.g. 10x = 2560x2400 points) ran off the screen. The window now uses the largest whole scale that fits the display (the setting is kept for bigger screens). On a Retina display Window Scale 5 already shows a 10x pack at full detail (2 pixels per point). -
0004-cyc-trim-side-borders.patch— Dragon Warrior IV (like many NES games) masks the leftmost 8 pixels, leaving a black bar. New setting Trim side borders (in-game menu > Display;CropSidesin config.ini; on by default) trims 8 pixels from both sides so the picture stays centred (240x240). -
0005-cyc-launcher-trim-side-borders.patch— also offers that setting in the launcher's Display settings (needs recomp-ui patch 0002). -
0006-cyc_ui_imgui-hidpi-menu-scale.patch— on a Retina display the in-game menu drew at half size in the top-left with its text clipped: Dear ImGui lays out in points but nesrecomp never set the renderer's HiDPI scale (as ImGui's SDL example does). Ratio 1 (Windows/Linux) is unchanged. -
0007-hdpack-memory-conditions-read-wram.patch—memoryCheck/memoryCheckConstantconditions only read the 2KB internal RAM, so a condition on$6000-$7FFFwas always false. They now read the cartridge work RAM, where Dragon Warrior IV keeps its battle state (the pack gates Necrosaro's forms on$6E7E). -
0008-allow-prg-mismatch-env-for-tools.patch— with the environment variableNESRECOMP_ALLOW_PRG_MISMATCHset, the host accepts a modified ROM (and save states made with the original, and a mod catalog made for the original). Onlytools/capture_types.pyuses it: it patches Dragon Warrior IV's character table (bank 8$979B) so a town's people load every character type in turn. Without the variable nothing changes. -
0009-hdpack-sprite-cells.patch— a new pack option,<options>spriteCells: sprite HD art is drawn over the sprite's whole 8x8 cell with the art's own alpha (the PPU side channel now also records the sprite on its transparent pixels; empty OAM slots are excluded), instead of only on the 8-bit s