khdays-port

khdays-port is a native PC port of Kingdom Hearts 358/2 Days with modern controls, resolutions, and mod support.

README

khdays-port

Discord

A native PC port of Kingdom Hearts 358/2 Days, built on khdays-decomp.

The game is the decompilation's C, compiled for the PC; the port supplies what the Nintendo DS hardware did underneath it -- graphics, sound, input, the card, interrupts and the system calls of its SDK -- and, around it, what a PC version wants: modern controls, rendering, resolutions, a single-screen mode and replaceable content. This is how other decompilation-based ports work: the game code is the original's, readable and moddable; the platform layer is new.

[!IMPORTANT] This repository contains no ROM, game assets, audio, video, text, SDK files or game binaries. You need your own legally obtained copy of the game.

[!WARNING] The port is a work in progress and may have bugs. Reports are very welcome, in the issues or on Discord: what happened and what you were doing when it did.

AI-assisted coding tools were used to help make this possible.

Status

The whole game runs natively from the decompilation: the title and its menus, Story Mode (opening movie, days, cutscenes, the field), Mission Mode (character select, camp, missions, results), combat and magic, the shop, and a player's save loaded, played and saved again. Crashes found while playing are fixed as classes in the build's source preparation, and what they show about the C goes back to the decompilation (docs/DECOMP_FINDINGS.md).

Today it builds for Windows x64 (64-bit). It plays with the keyboard, the mouse or a gamepad, with remappable controls and optional enhancements (an analog camera among them). The picture is the port's own: the 2D composed by the port on the graphics card from what the game asks for, and the 3D's geometry answered by the port and drawn there too -- at up to eight times the DS's resolution, in widescreen, and with HD textures, models and 2D graphics from mods; the DS's own engines remain only as the reference the port is checked against. The sound is the port's own: the ARM7's sound driver in C, mixed at the PC's rate, with its own volumes and every sound replaceable from mods. So are the movies: decoded by the port, shown at full colour, their sound at its own volume, each replaceable by a video from a mod. No machine code of the DS runs: the port has no ARM interpreter, and what the decompilation keeps as assembly and the game calls is C the port wrote from it. The game's files reach it from the port as well: each read copied at once from a mod's file or the extracted data, with no card in between (the emulated card stays as the reference). The game's time is the port's too: its frames, its tick and alarms on the PC's clock, its interrupts taken where the game waits or unmasks them, nothing simulating the DS's timers or its display; and the 3D is drawn at the monitor's refresh rate, between the game's frames, which still come 30 a second as on the DS. The game's memory is the port's: a range Windows places, with each DS region at its DS address from its start, the game's pointers kept as references into it -- the DS's own addresses hold nothing. The HUD, the menus and the game's text are drawn anew by the port, each element placeable. Underneath, the game still sees a DS -- two screens one above the other, and the ARM7's services, which the port answers on the PC: the clock from the PC's, the touch screen from the mouse, the save from its files.

On top of the game, a new edition from content the user provides: HD textures, models and 2D graphics, new rooms, sounds and videos, a single-screen mode, and Mission Mode's multiplayer.

Planned: the port on other systems -- Linux first, then, from one ARM64 build, macOS, Windows on ARM and the Nintendo Switch.

Installing

On Windows, the installer from the releases page is all you need besides your own copy of the game: it asks for your ROM (the European release of KINGDOM HEARTS 358/2 Days; it finds it by itself where KH Melon Mix keeps one in your KINGDOM HEARTS HD 1.5+2.5 ReMIX folder), prepares the game's data from it, and can also make the HD pack from your HD collections, add the port to Steam (PC and Steam Deck), and install the mod studio. Nothing of the game comes with it. The port tells you when a newer release is out. More: installer/README.md.

Getting started

To build the port from source instead:

  1. Check and extract your copy of the game (Python 3.11+):

    py .\tools\verify_rom.py "E:\path\game.nds"
    py .\tools\extract_data.py "E:\path\game.nds"
    

    The data goes to data/extracted/<hash>/, which is never committed.

  2. Build (Windows, Visual Studio 2026 with the C++ workload, CMake, Ninja, and for the preparation Python's pycparser):

    py -m pip install pycparser
    py .\port\build.py --config Release
    

    port/tools/prepare.py generates the build's sources from the decompilation -- the game's C rewritten with every pointer a reference (build/port/gen-x64); the executable is build/port/obj-release-x64/khdays-native.exe (--config Debug: build/port/obj-x64/).

  3. Run it from the repository root, so it finds data/ and keeps the save in saves/khdays.sav:

    .\build\port\obj-release-x64\khdays-native.exe 2> khdays.log
    

    The runtime reports to stderr; 2> keeps it for a bug report. Started without the data (or with it incomplete), it says so in a message box in the system's language, with the folder it looked in.

    The save is the DS's backup memory as a file, in the same format as a DS save (a 64 KB raw backup), so a save from the DS loads. It is written whole each time the game saves -- a new file beside it, then put in its place -- so a crash of the game or the PC while saving leaves the save as it was before, or as it is after, never half of one.

  4. When something seems wrong -- music late, the game stopping for a moment -- the session log says what the game was doing: the Debug build writes one for every session in saves/logs/ (Release: turn on Session log in the settings, or KHDAYS_LOG=1): its scenes, the files it read and how long they took, when each music was asked for and when it was heard, and every stretch over 50 ms without a frame. Press F12 when you see it happen: the log gets a mark there, and what the DS's two screens show then is saved in saves/marks/ (mark_<n>_frame_<frame>.bmp, with the upper screen's layer registers and the pause's pieces in the log). Send the log and the pictures with the bug report.

Controls

By default the game is played as on the DS.

  • Keyboard: arrows = D-pad, Z = A, X = B, S = X, A = Y, Q = L, E = R, Enter = START, right Shift = SELECT.
  • Mouse: hold the left button on the lower screen to touch it with the stylus.
  • Gamepad (Xbox, PlayStation, Switch Pro, Steam Deck, or any controller your system recognises as a gamepad): connect it whenever you like, even in the middle of a battle. The buttons follow the DS's layout by position, whatever is printed on them: the right-hand face button is A, the bottom one B, the left one Y and the top one X; the shoulders are L and R; Start or Options is START; Select, Share or View is SELECT. The left stick moves like the D-pad (the DS's eight directions). With several gamepads connected, the first one plays.

Settings. Press Esc, the gamepad's Guide / Home button or L3 (click the left stick in; Steam and the Steam Deck keep Guide for themselves), or click the title's settings key where the title is the theater's. The menu covers the window, the game dimmed and paused behind it (in a multiplayer session the game goes on). Every section is on one screen -- Controls, Enhancements, Sticks and camera, Video, Sound, Multiplayer, Language, General -- and works with the mouse alone, the keyboard alone or a gamepad alone (its shoulders move between the sections). A change takes effect when you make it: the picture behind the menu follows the video settings, each volume plays a sample of its kind (the game is heard at it when it resumes), and a new binding works from the first press after the menu closes. The Controls section shows every action with its two keys and two gamepad buttons side by side: choose a cell and press the new input; the Kingdom Hearts II layout and the defaults of each section are there too. A setting that waits for something says so beside it (the movies' choice from the next movie; the nickname, online play and the server from the next multiplayer session while one is open).

Language. The game and the settings menu follow your system's language when the game has it (English, French, German, Italian or Spanish; English otherwise). Settings → Language chooses one of the five instead: the menu changes at once, the game's own text when the screen you are on ends (a mission, a menu of the game, the title) -- a screen of the game loads its text when it is built, so every one is built whole in one language. KHDAYS_LANGUAGE=es (or en, fr, de, it) wins over both. Where the screen names one of the game's Config options, it uses the game's own words in that language.

Your choices are kept in saves/settings.ini, a text file you may also edit by hand (KHDAYS_SETTINGS=<path> uses another file). If the port cannot read it, it starts with the defaults and keeps the old file as settings.ini.bad.

Enhancements (Settings → Enhancements; all off by default, so the game plays as on the DS until you switch one on). Each does only what the game's own controls can do, through the same parts of the game:

  • Analog camera: the right stick turns the field camera and tilts it, as far as the tilt of the stick, at up to the game's own turning speed (adjustable, and invertible, under Sticks and camera). On the DS the camera is turned by dragging on the touch screen, by Select's look view, or with L/R when the game's Config sets them to the camera.
  • Command menu on the D-pad: in a field, the gamepad's D-pad moves through the command deck instead of moving Roxas, by making the presses the DS itself uses (X, or X held with a direction); menus, dialogs and open panels keep the D-pad. Which directions work depends on the game's own Config option for the command deck: with X alone moving it (the new-game default), only Down can.
  • Analog movement: Roxas moves in the exact direction of the left stick; pushed less than the run threshold (75 % by default) he walks, at half his speed by default, his steps slowed to match. Fully pushed he runs at the game's own speed, never faster.
  • Separate lock-on: lock on (and off) and switch to the next target on buttons of their own, by making the presses the game uses: its lock-on is R pressed twice quickly, a single R moves to the next target once locked (with Controls: Type B, L tapped goes to the previous one). The game takes two R presses within 9 of its frames (0.3 s) as the double press that lets go, so switching quickly on the DS drops the lock: here the next target waits for that window instead, and a quick series of switches keeps the lock. On the DS a single R press also resets the camera and a long one switches the chase cam; the DS's R stays available for those. Out of the field -- menus, tutorials, the pause -- there is no lock-on, and these buttons are the DS's R (lock on, next target) and L (previous target), so they turn pages as R and L do there.

The Kingdom Hearts II layout (Settings → Gamepad preset; Kingdom Hearts III uses the same buttons for these actions) puts attack on the bottom face button, jump on the right, guard on the left, DS X on the top, the shortcuts on L1, lock-on on R1, the next target on R2, the camera's reset on R3 (the DS's R), the command menu on the D-pad, the camera on the right stick and SELECT on View / the touch pad, and switches on the enhancements it uses. The keyboard is not changed.

Picture

By default the picture is the DS's: each screen 256x192, drawn exactly as the DS draws it, its pixels scaled up sharp to fill the window at 4:3. The port draws it through the graphics card (Direct3D 12 or Vulkan, whichever the system offers), and needs one: without, it says why in a message box and its log, and does not start.

Settings → Video changes it; the picture behind the screen follows at once:

  • Display mode: a window (the default) you can move and resize, a borderless window over the whole screen, or full screen (the screen for the game alone, at your desktop's resolution). Alt+Enter switches between the window and the full screen you chose last (the borderless window if none) while the settings menu is closed, and keeps it.
  • Internal resolution (1x, 2x, 3x, 4x, 6x, 8x, or the window's own size): the 3D -- the field, the characters, the effects -- drawn at that many times the DS's pixels. "The window's" draws at the next whole multiple up and shrinks it on the graphics card to the window's own pixels (with sharp scaling, the largest that fits, its pixels square); a picture drawn larger than the window is always shrunk so, each pixel the average of what is under it, never stretched. The 2D -- the HUD, the menus, the text, the lower screen -- stays the DS's pixels, as the game drew them, unless a mod replaces it (below). It is the same picture, only sharper: the 3D follows the same rules as at 1x, and wherever the 2D covers or blends with it, it does so as on the DS.
  • Antialiasing (off, 2x, 3x, 4x): the picture made that many times larger, then shrunk to the size shown, each pixel the average of what is under it -- the 3D's edges come out smooth instead of in steps. 2x is four times the graphics card's work, 4x sixteen; off by default. (A mod's textures, and the HD replacements, are read at the size they are drawn whatever this says: smaller copies of each are kept, so a model far away does not break into steps that flicker.)
  • Aspect ratio (4:3, 16:9, 16:10, or the window's): the 3D view of the upper screen widens to it, and the field's HUD keeps to the corners of the wider picture as it does on the DS -- the command deck bottom left, the portrait and gauge bottom right, the hearts counter top left. What the game centres (dialogs, menus) and what follows the 3D (markers over targets) stays where it was, and the lower screen stays 4:3 in the middle, where the mouse still touches it. The game sees nothing different: it still computes its 4:3 view.
  • Screen scaling: sharp (the DS's pixels as squares) or smooth.
  • 3D texture smoothing: off as on the DS, or blended between texels.
  • HD graphics: the replacements in your mods (below), on by default.
  • Dump graphics: each texture and 2D tile sheet written out the first time it is drawn, into saves/dump/graphics.
  • Movies: HD, the default -- the HD collection's own videos where you have copied them (below), the other movies at full colour -- at full colour (the decoded frames as they are), or as on the DS (15-bit colour with its checkerboard dither).
  • Picture pace: smooth, the default -- the 3D drawn at your monitor's refresh rate (60, 120, 144, 165 Hz...), placed between the game's last two frames (the game itself runs at 30 frames a second, as on the DS), the picture up to one game frame behind -- or as on the DS, a new picture each game frame. Camera cuts and scene changes are not smoothed across. The 2D (HUD, menus, text) and the movies change when the game changes them.
  • Show FPS: a counter in the window's corner -- the pictures drawn a second and the game's own frames a second.
  • Graphics API: what draws the picture -- automatic (the system's choice: Direct3D 12 on Windows), Direct3D 12 or Vulkan, changed at once. If the graphics card stops answering on Direct3D 12, try Vulkan. Should the card stop answering anyway (Windows resets its driver), the game carries on: the picture is made again, and an internal resolution lost twice drops to 1x for the session.

The choices are kept in the [video] section of saves/settings.ini:

[video]
internal_resolution = window   # the window's size (the default), or a multiple: 2 to 16 in the settings
antialiasing = off        # off, 2x, 3x or 4x: made that many times larger and shrunk
aspect = 16:9             # 4:3 (the DS's), 16:9, 16:10, or window
scaling = sharp           # sharp (the DS's) or smooth
texture_smoothing = off   # off (the DS's) or on
hd_graphics = on          # the mods' replacements
dump_graphics = off       # write what is drawn to saves/dump/graphics
movies = hd               # hd (your HD videos, the rest pc), pc (full colour) or ds (the DS's)
picture_pace = smooth     # smooth (the monitor's rate) or ds
show_fps = off            # the live counter
backend = auto            # auto, direct3d12 or vulkan

The field on one screen. With one screen ([video] screens = one, the default) the field's HUD is laid out for a PC screen, made of the game's own pieces: the mission's objective and gauge at the top left, its badges and chests counted at the right of the objective, the hearts counter under it; in the top right corner the map, drawn over the field as in Kingdom Hearts II, with the enemy's health over it; the command deck at the bottom left and the party at the bottom right. The panels' dark ground is half see-through, their text and edges are not. The INFORMATION banner shows at the top left, over everything, for five seconds each time its message changes, instead of staying on screen; at a mission's start the mission's panel and map show at once, before the game brings in the rest of its HUD.

Badges on the mission panel. In a story mission the lower screen's mission panel shows, left of the chest icon, the mission's badges with the camp's mission list's own icons -- the ordeal badge and the blazon, then the unity badge -- grey until found, lit once your save holds them or you pick one up; on one screen it shows how many are found of how many. The DS shows them only in the list. Mission Mode puts out no badges, so it shows none.

The players' names in multiplayer. In a Mission Mode session the field's party shows each player's name left of their J label, in white in the game's own font -- yours by your bar, the others' by theirs. The DS shows only the labels.

The title as the HD collection's theater menu. If you have KINGDOM HEARTS HD 1.5+2.5 ReMIX, tools/hd/theater_title.py makes a mod from your copy (nothing of the collection is in the port) that shows the title's menu, on one screen, as the collection's 358/2 Days theater draws its main menu -- its logo, key art, bars and settings key, animated as there -- with the game's own options in its font, in the five languages. It reads the theater's files as OpenKh's extractor writes them (Image/Mare.hed_out/original):

python tools/hd/theater_title.py "<collection>/Image/Mare.hed_out/original"

The pictures go to mods/Theater title/layouts/title/; the screens after the menu (the save list, the theater's list; the difficulty, unless the new game's dialogs below are made) are the game's. Its settings key opens the settings menu when clicked and, while you play with a gamepad, shows your gamepad's button for the settings in the collection's own icons (the left stick's click by default: the collection has no icon for the Guide button). The boot logos are the theater's too: its Disney, SQUARE ENIX and copyright screens in