Harvest Moon: Back to Nature (PS1) functional decompilation
  • C 96.1%
  • Python 3.2%
  • Makefile 0.4%
  • Java 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-23 15:37:26 +07:00
include Mouse in menus: field menus, shops, kitchen, card screens, name entry, birthday; README, DECOMP.md 2026-10-23 11:27:24 +07:00
re@b1b24b642b Linux x86_64 (Steam Deck) and Windows (MinGW-w64) builds: Makefile WINDOWS=1 (exe, console, UTF-8 manifest, PYTHONUTF8, EnterCriticalSection clash), -lm for difftest, VirtualProtect for HM_LAYOUT=ps1, plat.h in the generated-object deps, mkdir/rename/mtime portability in saves, maps, editor, music export, README: Building on Linux and the Steam Deck (distrobox recipe), Building on Windows (MSYS2, cross-compile) 2026-10-23 15:37:26 +07:00
src Linux x86_64 (Steam Deck) and Windows (MinGW-w64) builds: Makefile WINDOWS=1 (exe, console, UTF-8 manifest, PYTHONUTF8, EnterCriticalSection clash), -lm for difftest, VirtualProtect for HM_LAYOUT=ps1, plat.h in the generated-object deps, mkdir/rename/mtime portability in saves, maps, editor, music export, README: Building on Linux and the Steam Deck (distrobox recipe), Building on Windows (MSYS2, cross-compile) 2026-10-23 15:37:26 +07:00
test Merge branch 'qtext' into integ 2026-10-23 12:35:54 +07:00
tools Linux x86_64 (Steam Deck) and Windows (MinGW-w64) builds: Makefile WINDOWS=1 (exe, console, UTF-8 manifest, PYTHONUTF8, EnterCriticalSection clash), -lm for difftest, VirtualProtect for HM_LAYOUT=ps1, plat.h in the generated-object deps, mkdir/rename/mtime portability in saves, maps, editor, music export, README: Building on Linux and the Steam Deck (distrobox recipe), Building on Windows (MSYS2, cross-compile) 2026-10-23 15:37:26 +07:00
.gitignore Maps data-driven (map format v1): every per-map fact from maps/ (index.json + a directory per area: map.json, model/textures/schedule/resident .bin), written from assets/ by the game's exporter (make maps, --export-maps, first start) and checked by reading back; area loads (Farm_LoadArea, Farm_LoadAreaAsync, mini-game arenas) keep the original CD reads for timing and take the map's bytes; ovl11 place tables (walk rectangles, warps, clock modes, default destinations, festival column) written from index.json after each ovl11 load; destination rules, scenes, grids, field banks, light, texture animations, music volume and mine facts read from places/groups/areas instead of id/kind/index tests; DECOMP.md Map data (inventory, format, what stays in code); re: --maps/--export-maps 2026-10-22 13:06:08 +07:00
.gitmodules .gitmodules: relative URL for re, so a clone from any host (Forgejo, Gitea) fetches re from the same host 2026-10-21 12:30:16 +07:00
DECOMP.md Merge branch 'qtext' into integ 2026-10-23 12:35:54 +07:00
Makefile Linux x86_64 (Steam Deck) and Windows (MinGW-w64) builds: Makefile WINDOWS=1 (exe, console, UTF-8 manifest, PYTHONUTF8, EnterCriticalSection clash), -lm for difftest, VirtualProtect for HM_LAYOUT=ps1, plat.h in the generated-object deps, mkdir/rename/mtime portability in saves, maps, editor, music export, README: Building on Linux and the Steam Deck (distrobox recipe), Building on Windows (MSYS2, cross-compile) 2026-10-23 15:37:26 +07:00
README.md Linux x86_64 (Steam Deck) and Windows (MinGW-w64) builds: Makefile WINDOWS=1 (exe, console, UTF-8 manifest, PYTHONUTF8, EnterCriticalSection clash), -lm for difftest, VirtualProtect for HM_LAYOUT=ps1, plat.h in the generated-object deps, mkdir/rename/mtime portability in saves, maps, editor, music export, README: Building on Linux and the Steam Deck (distrobox recipe), Building on Windows (MSYS2, cross-compile) 2026-10-23 15:37:26 +07:00

de-hmbtn

Functional decompilation of Harvest Moon: Back to Nature (USA, SLUS-01115) for native macOS/Linux builds, with no PS1 hardware emulation (the game's C runs on a native runtime).

The game began as a complete static recompilation from re-hmbtn (submodule re/, the native runtime). Decompiled C replaced the recompiled functions one by one, and the game stayed playable at every step. Each decompiled function behaves exactly like the original; byte matching is not a goal. make difftest checks equivalence against the recompiled original.

Status:

  • Functions: all 2675 game functions (executable and every overlay) and all 677 Sony PsyQ SDK functions are decompiled C. make coverage ROUTE=day1 reports no recompiled game code on the route. About 92 % of the functions have real names; the rest are func_<address>.
  • Checks: make difftest compares 2952 functions against the recompiled original (0 failures; 465 are only weakly exercised). make check also runs 12 whole-game routes (lockstep: RAM and CPU time identical to the original every game frame; statestep: game state identical, as objects) at 4:3, at 16:9 and at 50 % zoom.
  • Native libraries: the pad, CD, memory card, sound (SPU) and GPU libraries and the basic system libraries are native C on the host. LIBSND, LIBGS, LIBGTE and LIBMCX are still decompiled C on PS1-style guest memory.
  • Game state: moved from emulated PS1 RAM into host-owned C structs (player, NPCs, animals, inventory, clock, field, menus, overlays, sound; DECOMP.md "Host-owned state"). Draw packets, loaded assets, text buffers, save buffers and the two developer tools stay in PS1-format memory on purpose.
  • Data: the game reads named files from assets/ (no disc needed after the first start), and all 255 areas are data files in maps/ that the in-game map editor changes.
  • PC features (not in the original): widescreen and zoom, HD rendering, 60 fps / display-rate frame interpolation, Enhanced audio with master/music/SFX volume, no-slowdown timing, text speed, mouse in menus, typing names, save files, and the map editor (see the sections below).
  • Still to do: names for the remaining func_/D_ symbols and unk fields, a few PS1-memory leftovers (DECOMP.md), and playing on a real Windows PC and on stock SteamOS (Linux x86_64 on a Steam Deck and a Windows cross-build are checked, see below).

Requirements

  • A disc image of SLUS-01115 (USA), for the first build. Redump SHA-1 of the raw image: cb26381fa1336bdd15f07469e59f1143ccf37b03. No game data is in this repository.
  • clang or gcc, GNU make, Python 3, pkg-config, SDL3 (built and checked with 3.4.18), and a re-hmbtn checkout: git submodule update --init. For the HD renderer a Vulkan driver (Linux, Windows) or Metal (macOS); without one the game falls back to the software renderer.
  • macOS: Xcode command line tools and brew install sdl3. Linux: see "Building on Linux and the Steam Deck". Windows: see "Building on Windows".

Build

make DISC=/path/to/HARVESTMOON.bin   # first build: extracts the game's files into assets/
make                                 # later builds
build/hmbtn-de                       # play: reads assets/, no disc needed
build/hmbtn-de /path/to/HARVESTMOON.bin   # with setting data=disc: play from the disc image

The first build runs re/tools/extract.py (the recompiler needs the executable and the overlays from assets/) and records the disc image in .disc; make assets extracts again. A checkout that still has the former extracted/ converts with one command, make (it extracts from the disc recorded in .disc, or make DISC=...); extracted/ can then be deleted.

Players need neither Python nor make: the game binary extracts the assets itself. hmbtn-de game.bin (or .cue) with no assets/ around shows "Preparing game files... N%", writes assets/ next to the binary and starts the game; later starts need no disc. hmbtn-de --extract game.bin [--assets DIR] only extracts. Both check the disc: another region's disc or a damaged image is refused with a message.

Building on Linux and the Steam Deck

Checked on a Steam Deck (x86_64, AMD VanGogh GPU, Vulkan/RADV, Arch-based CachyOS "deckify" kernel 7.2, gcc 16 and clang 23, SDL3 3.4.18): the build is warning-free apart from a few gcc string-truncation notes, make difftest (2952 functions, 0 FAIL, 465 weakly exercised), make lockstep-all DATA=files (12 routes), make statestep-all (12 routes) and make coverage ROUTE=day1 give the same results as on macOS. The Original-audio WAV of the first cutscene route (--audio original --wav) and the HD renderer's picture (Vulkan, SPIR-V shaders, --renderer gpu --gpu-screenshot) are byte-identical to the macOS build's (Metal). Not checked: the game in a window on the Deck (Gaming Mode, fullscreen), audio through the Deck's device, stock SteamOS.

Packages (Arch, CachyOS, SteamOS inside a container; the names are Arch's):

sudo pacman -S --needed base-devel git clang python sdl3 pkgconf vulkan-icd-loader vulkan-radeon
# Intel: vulkan-intel, NVIDIA: nvidia-utils; no GPU driver: vulkan-swrast (software Vulkan, slow)

Other distributions: the same tools, and SDL3's development files (libsdl3-dev on Debian 13 / Ubuntu 25.04 and later; older ones need SDL3 built from source).

git clone --recurse-submodules <this repository> hmbtn && cd hmbtn
make DISC=/path/to/HARVESTMOON.bin -j6   # the compiler is cc (gcc on most systems); CC=clang for clang
build/hmbtn-de                           # from the checkout directory (see below)

CC=clang make (the environment, not make CC=clang) also reaches the test scripts, which call make again. A build takes about a minute on the Deck (-j6). The whole suite on the Deck (6 jobs): difftest 15 min, lockstep-all 12 min, statestep-all 3.5 min, coverage 3 min.

Where the game keeps its files. All relative to the working directory, so start the game from the folder that holds it: assets/ (also found next to the binary or one or two levels above it, so build/hmbtn-de finds the checkout's), maps/ beside it, hmbtn_settings.ini, saves/ and the memory card images hmbtn_slot1.mcd, hmbtn_slot2.mcd in the working directory. In your home directory all of it is writable; nothing goes to /usr, /etc or the XDG directories. As a Steam shortcut ("Add a Non-Steam Game"): target build/hmbtn-de inside the checkout and "Start In" the checkout directory, or the saves and settings end up in whatever directory Steam starts it in. The first start with a disc image and no assets/ extracts the game files next to the binary (3 s on the Deck; --extract game.bin --assets DIR only extracts; DIR's parent must exist).

Display. The Deck's screen is 1280×800 (16:10): in the settings (Graphics page) choose Aspect 16:10 (a 384×240 field fills the screen; 16:9 and 4:3 leave bars) and Display Fullscreen, or write aspect=16:10 and display=fullscreen into hmbtn_settings.ini before the first start. Gaming Mode's gamescope gives a window the whole screen anyway. The picture always keeps its shape, with black bars as needed.

Controller. SDL3 lists the Deck's own controls as a gamepad ("Steam Deck Controller"); the game takes the first two gamepads as players 1 and 2 and maps them by position as on the PS1 (Controls). Untested: with Steam Input in Gaming Mode the Deck might show up twice (the raw pad and Steam's virtual pad), which would make player 2 mirror player 1; if it does, use Steam's "Gamepad" template or turn "Enable Steam Input" off for the shortcut.

Building on a Deck with the read-only SteamOS (the root file system is read-only and a system update removes what pacman installs): build and run in a container, which keeps your Deck as it is. distrobox (needs podman, which SteamOS includes; install it into your home with curl -s https://raw.githubusercontent.com/89luca89/distrobox/main/install | sh -s -- --prefix ~/.local). This container recipe is untested (the Deck above runs CachyOS, not SteamOS):

distrobox create --name hmbtn --image archlinux:latest   # once
distrobox enter hmbtn                                    # a shell in the container; your home is shared
sudo pacman -Syu --needed base-devel git clang python sdl3 pkgconf vulkan-icd-loader vulkan-radeon pipewire libpulse
git clone --recurse-submodules <this repository> ~/hmbtn && cd ~/hmbtn
make DISC=/path/to/HARVESTMOON.bin -j6
build/hmbtn-de                                           # window, GPU and audio come from the Deck

To start it from Gaming Mode add a Non-Steam Game whose target is ~/.local/bin/distrobox-enter with the arguments -n hmbtn -- sh -c 'cd ~/hmbtn && build/hmbtn-de'. The binary links the container's SDL3, so run it inside the container, not on the host.

Building on Windows

Untested at runtime on a real Windows PC. What was checked: the whole game cross-compiles and links for x86_64-w64-mingw32 (MinGW-w64 gcc 16 against the SDL3 3.4.18 MinGW development package) to one hmbtn-de.exe that needs only SDL3.dll and Windows' own DLLs, and under Wine 8 (headless) that exe boots, extracts the disc into assets byte-identical to the macOS ones (including with \ paths), and plays the first cutscene route with an Original-audio WAV byte-identical to the macOS and Linux builds'. Not checked: a window, the HD renderer (SDL_GPU's Vulkan driver takes the SPIR-V shaders; without a Vulkan driver the game falls back to the software renderer), audio devices, gamepads, make itself on Windows. The test suite (make check and the scripts behind it) is for macOS and Linux only; on Windows build and play.

MSYS2 (https://www.msys2.org), in the MSYS2 UCRT64 shell:

pacman -S --needed git make mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-sdl3 \
      mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-python
git clone --recurse-submodules <this repository> hmbtn && cd hmbtn
make CC=gcc DISC=/c/Games/HARVESTMOON.bin -j6       # the disc image as an MSYS2 path (/c/... is C:\...)
build/hmbtn-de.exe                                 # from the UCRT64 shell: SDL3.dll is found on its PATH

MSYS2 sets OS=Windows_NT, which the Makefile takes for a Windows build (WINDOWS=1): .exe, a console program (its log shows in the window), a manifest that makes file names UTF-8 (tools/win/hmbtn.rc, needs windres from the gcc package, Windows 10 1903 or later), and PYTHONUTF8 for the generators. clang (mingw-w64-ucrt-x86_64-clang) works the same with CC=clang. To start the game by double-click, put SDL3.dll (from /ucrt64/bin) next to hmbtn-de.exe; it keeps assets/, maps/, hmbtn_settings.ini and saves/ in its folder (the working directory) as everywhere else.

Cross-compiling from macOS or Linux (this is what was checked), with MinGW-w64 (brew install mingw-w64, or the distribution's mingw-w64-gcc) and the SDL3 MinGW package (https://github.com/libsdl-org/SDL/releases, SDL3-devel-3.x.y-mingw.tar.gz):

make build/gen/funcs.h assets/manifest.json DISC=/path/to/HARVESTMOON.bin   # the generated C and the assets, on the host
SDL=/path/to/SDL3-3.x.y/x86_64-w64-mingw32
PKG_CONFIG_PATH=$SDL/lib/pkgconfig make WINDOWS=1 CC=x86_64-w64-mingw32-gcc \
    WINDRES=x86_64-w64-mingw32-windres O=build/obj-win -j8 build/hmbtn-de.exe
cp $SDL/bin/SDL3.dll build/                                                 # next to the exe

Game data

The game reads its data from assets/, named files with the original bytes (re/recomp/runtime/rt_assets.h, list in assets/manifest.json): the executable SLUS_011.15 (the one SYSTEM.CNF boots), the XA audio STR/01..04.XA, the disc's ISO 9660 sectors (disc/), and the archive A_FILE.BIN cut into named regions: overlays (overlay/ovl11.bin), the 255 areas (area/00_farm_spring/{header,textures,schedule, resident}.bin), cutscenes, sound banks and sequences, message banks, portraits, icons, ... The game's loaders ask for them by kind and index (include/asset.h, DECOMP.md). Or it reads a disc image. Either way the game's CD library (LIBCD, native: re/recomp/runtime/psyq/ libcd.c) reads them through the same drive model at their disc sectors, so the game finds, reads and streams the same data. Music and voices stream from the XA sectors at the disc's rate into the SPU's CD input.

The assets directory is --assets DIR, else assets/ in the working directory, next to the binary, or one or two directories above it (so build/hmbtn-de finds the checkout's assets/). A disc image argument is then optional; one that does not exist is ignored. With neither, the game stops with a message saying how to provide the disc.

The maps come from maps/ beside assets/ (or --maps DIR): index.json (the area table, places, destination rules) and a directory per area (map.json: exits and triggers, camera limits, props, people, music, flags; model.bin, textures.bin, schedule.bin, resident.bin). The game writes them from the assets on a start without them; make maps (or hmbtn-de --export-maps) writes them anew. Edits to them change the game; as written they give the original game byte for byte (DECOMP.md "Map data").

Setting data (files or disc, in hmbtn_settings.ini) chooses the source. Headless runs without --settings-menu (the tests) use the disc image when given one, else the assets (make lockstep DATA=files plays the decompiled build from assets/). The source can be changed in the settings menu (Game page) while playing; the drive reads its next sector from the new source, also in the middle of a load or an XA track. Without a disc image (or without the assets) the setting cannot be changed: the menu says No disc image given.

Setting loading (fast or original, Game page) decides how long reads take. original keeps the drive's timing (the timing the tests check, to the emulated cycle: with loading=original a route played from the files leaves the same game RAM at every vblank as from the disc image). fast (the default with a window or --settings-menu) completes every read at once, so loading takes only the game's own fades; headless test runs never use it. Loads the game polls once per frame then take fewer frames (title intro, area changes, portraits), as the former files mode did; game logic waits for the data either way.

Settings

Settings are in the game: SETTINGS on the title screen (under NEW GAME and CONTINUE), Select on the Start status screen in the field (M on the keyboard), or Settings in the pause menu (Esc in the field). The window has pages, Graphics, Controls and Game: Left/Right on the page names at the top (or L1/R1, Q/R on the keyboard) switch pages. The window is centred, in widescreen too, over the screen it was opened from, which keeps running underneath (the status screen keeps its page; it sees no buttons) and is dimmed like the game's own dims, so that screen's windows recede behind it.

Setting Values
Graphics HD (GPU renderer, Metal or Vulkan), Original (the exact 320×240 picture)
Resolution 1x–8x internal resolution in HD
Aspect 4:3, 16:9, 16:10, 21:9: widescreen field in HD
Zoom 100% to 50% in HD at every aspect (keys - and =): more of the field, HUD unchanged
Text k8x12 (the game's letters redrawn with the free k8x12 pixel font), Pixel, Original; HD at internal scale ≥ 2
Filter Smooth, Sharp
Display Window, Fullscreen
VSync Off, On
Data Files (assets/), Disc (the image given on the command line); switches at once
Saves Files (save files with a load list, mid-day saves), Memory card (the game's own card screens)
Loading Fast (reads complete at once), Original (the CD drive's pace, as the tests play)
Timing Original (the PS1's CPU time: a frame whose work runs over its 2 vblanks makes a lag frame), No slowdown (every game frame within its 2 vblanks); switches at once
Text speed Original (the game's pace, what the tests play), Fast (2 letters a frame), Faster (4), Instant (a whole box at once; a longer text scrolls up a line at a time); switches at once
Audio Enhanced (the same music and sounds rendered at the device's rate, 48 kHz usually), Original (the PS1's own 44.1 kHz mix, exact); switches at once
Master vol., Music vol., SFX vol. 0% to 100% in steps of 10 (Left/Right), default 100%; the volume of everything, of the music, of the sound effects and voices
Geometry Precise (3D world without vertex wobble, perspective-correct textures), Original (as the PS1); HD
Frame rate Display (the field's motion interpolated at the screen's refresh rate; VSync On recommended), 60 fps, 30 fps (the game's own); HD

Changes apply immediately and are saved to hmbtn_settings.ini in the working directory. The picture keeps its shape in any window or fullscreen, with black bars as needed.

Setting audio (Game page) chooses what is heard; the game itself never notices. The PS1 sound chip's voices always run exactly as before (envelopes, key status, sound RAM: what the game reads back), and their own mix is original, the PCM the tests check. enhanced (the default with a window or --settings-menu) renders the same voices again from the same SEQ, VAB and XA data at the output device's rate in floating point: band-limited (windowed-sinc) resampling of every voice instead of the chip's linear interpolation, the PS1 envelopes evaluated per output sample (no steps), smoothed volume and pan changes, a note the game restarts keeps fading out on a spare voice instead of being cut, the game's reverb (type and depth) computed at the full rate without 16-bit clamping, the XA streams resampled from their own rate, and a soft limiter only at the end. Headless test runs without --settings-menu always hear original; --audio original|enhanced sets it for one run (and --wav records whichever is in effect, at its rate).

The volumes (settings volume_master, volume_music, volume_sfx, Game page: Master vol., Music vol., SFX vol.; --master-volume N, --music-volume N, --sfx-volume N, 0-100, for one run) scale the heard output only, in both renderers: the sound chip's voices, the game's own volumes, the map's musicVolume and the reverb input stay exactly as the game left them. The game's sound driver (LIBSND, _SsVmKeyOnNow / vmNoiseOn) tells the runtime at each key on whether the voice is a sound effect (SsUtKeyOn*: Snd_PlaySe, the menu sounds) or a note of a song's sequence; that group is kept with the voice until its next key on. Music is the songs (every sequence voice), SFX every sound-effect voice and the XA streams (the game plays only spoken lines and animal cries there). Master scales both. A voice's reverb is scaled with its group (the Original mix runs a private copy of the reverb per group while a volume is below 100%, the Enhanced renderer a second reverb), and a changed volume takes ~10 ms to arrive (no click). With all three at 100% the output is bit for bit the unscaled one, so headless test runs (which keep 100%, whatever the settings file says) are unchanged; --export-music always writes the songs at full volume.

Setting timing (original or smooth, Game page: Original / No slowdown) decides what happens when a game frame's work does not fit its 2 vblanks. The runtime still charges the PS1's CPU time for the game's loops, and the vblank, the CD drive, the sound chip and the timers run on that clock; original (the default, the timing the tests check) lets such a frame run into the next vblank, so the game loses a frame there, as the console does. With smooth the frame's work stops taking emulated time once the frame has used its budget, so its VSync(2) still comes after exactly 2 vblanks; waits on hardware (CD reads at loading=original, the memory card, DMA, the sound driver's loads) take their time as before, and sound, XA streams, the frame pacer and frame interpolation run as always. Only frames that would have lagged behave differently. On the routes such frames are rare and are at loading points (the title screen loading, the first frame after a save is loaded, the start of a day, the save screens): in the field, festivals and the town a game frame's work takes 0.4–0.6 vblanks (median; at most 1.4 in 99.9 % of frames), so the game does not slow down there with either setting. The frames that take longer in play are synchronous loads: each use of a tool reads its animation from the CD in the middle of a frame (6 vblanks; 5 with loading=fast, the game's own VSync(3) before the read), which timing leaves alone. Headless test runs without --settings-menu always play original; --timing original|smooth sets it for one run.

Setting textspeed (original, fast, faster or instant, Game page: Text speed) speeds up the dialog text. The game types one letter every other game frame (every frame while ✕ is held); with Fast, Faster and Instant every frame reveals 2, 4 or as many letters as fit the box (up to its fourth line), and the scroll of a long text takes 7, 4 or 1 frames a line instead of 14. Every code of the text that the game reacts to by position (a wait for ✕, the end of a page, a choice, a portrait or event code) stops the frame's run of letters, so waits, questions and scenes work as before; the game simply waits for the text to end sooner. The typing sound plays once per frame's run. Headless test runs without --settings-menu always play original; --textspeed original|fast|faster|instant sets it for one run.

Widescreen shows more of the field (farm, village, houses) to the left and right. The date window moves to the right edge; message boxes and menus stay in the middle; the title, menus and other 2D screens stay 4:3. Zoom draws the field's world (ordering tables named with rt_gpu_world_lists in Gfx_DrawFrame) smaller around the screen centre; the HUD and message boxes keep their size. The game code involved widens its off-screen culling (Tmd_*OnScreen, View_SetFrustum, Screen_IsNear, Props_Draw, the crop tile range, weather particles) by rt_view_margin_x/y(), and also culls vertically and guards the packet buffer then; all of this is 0 / off in headless test runs.

Outdoors, a widened or zoomed-out view would show the black past the map's edge where the original camera limits only keep the 320x240 view on the map (src/ovl11/render_camera.c). The field is drawn from the camera target moved a little away from that edge (at most a quarter of the half screen, a smooth function of the target), and what still lies past the edge is filled with a skirt of ground: tiles continuing the nearest flat ground polygon of the map's edge (its texture, mirrored tile to tile, and colour), linked at the back of the ordering table so the whole map draws over them. The game's own camera target is unchanged; indoors (black around the rooms, as in the original) and at 100 % / 4:3 nothing changes.

Precise geometry draws the 3D world (maps, buildings, props: every polygon Tmd_SortObject sorts) at the exact projected positions instead of the PS1's whole pixels, so edges stay still and straight while the camera moves, and textures are mapped with perspective instead of the PS1's affine warp. Prim_Sort stores each SXY as the original does, then hands the packet word to the runtime (psx_gte_store_sxy), which keeps the vertex's unrounded position and depth outside game RAM for the GPU renderer (see re-hmbtn's README). On route day1 that covers 99.4% of the world OTs' polygon vertices; the rest are the sprites (characters, animals, items: Anim_DrawPolys POLY_FT4s placed at a projected point), which keep their whole-pixel positions. Measured on the farm (walking down for 60 game frames, 4x, native pixels): the frame-to-frame scroll judders by 0.163 px RMS with original geometry and 0.042 px with precise; each 32-pixel patch of the picture by 0.212 px and 0.028 px. Vertices sit 0.42 px RMS (at most 0.87 px) from their exact projection with original geometry, 0 with precise; the affine texture error removed is 0.16 texel on average and up to 1.2 texels (2.3% of textured triangles over 0.5 texel, day1).

Frame rate Display or 60 shows the field's motion smoothly: the game draws a new frame only every other vblank (30 per second), and the GPU renderer shows pictures in between, with each moving thing part of the way from its place in one game frame to its place in the next (re-hmbtn's README: frame interpolation). What a vertex belongs to comes from the draw code: Prim_Sort names the model vertex each SXY was projected from (psx_gte_store_sxy_key), and the sprite drawers Anim_DrawSprites, Anim_DrawPolys and Anim_DrawStreamed (characters, animals, items, shadows) tag their corners with the animation and the point the sprite is placed at (psx_gte_tag_sprite), all outside game RAM. The HUD, text, message boxes, menus and fades are shown as in the newest game frame; area changes, camera cuts and teleports snap. The pictures are one game frame (about 33 ms) behind the game, at Display about 25 ms more. Measured headless on the farm (walking down, 2x, present_seq; motion of the world per picture, native pixels):

Presented at Step per picture Spread of the steps (std)
30 fps (60 Hz display) 3.15 and 0, alternating (mean 1.59) 1.565
60 Hz, interpolated 1.568 0.023
120 Hz, interpolated 0.785 0.015
144 Hz, interpolated 0.649 0.015

Every 32-pixel patch of the picture moves evenly (21:9, zoom 80 %: per-patch step spread 0.024 px median, 30 fps 1.22 px); an animal walking past moves 0.32 px each picture instead of 0.64 px every other one. In a window at Display, on a 100 Hz screen: 96–104 pictures per second, the presentation times within 1.2–1.6 ms of an even line (--perf), Metal and Vulkan alike.

The window and its code (src/ui/settings_menu.c: pages of table rows, one entry per setting) are not part of the original game; the menu is offered only with a window (or --settings-menu), so headless test runs see the original title menu. Command-line options are listed in re-hmbtn's README.

Music export

build/hmbtn-de [disc] --export-music DIR [--audio original|enhanced] [--song N] writes every background music song (BGM_TABLE, 1-36; --song N only that one) as DIR/song_NN.wav, Enhanced at 48 kHz unless --audio original (44.1 kHz). It boots the game without a window to its title menu, starts each song as the game does (its bank, sequence and its own reverb type and depth), and records it through the selected renderer. The loop is the sequence's own (LIBSND's loop markers): each file holds the song up to the end of one loop, then up to 10 s of the repeat fading out, and a RIFF smpl chunk with the loop's first and last sample, so loop-aware players and engines repeat it without a seam. DIR/export.log lists each song's loop (samples and seconds), the seam check, peak and RMS. Takes about 40 minutes for all 36.

Editing textures

build/hmbtn-de --map-tool export-textures maps/00_farm_spring (or maps for every map) writes the map's textures as PNG files, textures/*.png, plus textures.json (VRAM place, depth, palette and coding of each). Edit them in any image editor: an image as a palette PNG with its own colours, or recolour a palette strip (*_clut_*.png) to recolour everything drawn with it. On its next load of that map the game uses the PNGs when they are newer than textures.bin; --map-tool import-textures DIR writes textures.bin from them and reports palette changes (new colours go into free palette entries; a palette only these images use is rebuilt; otherwise the nearest colour). PS1 colours have 5 bits per channel; alpha 0 is transparent. Unchanged files give the original bytes (--map-tool check-textures maps).

--map-tool export-model DIR [out.glb] writes the map's model as glTF 2.0 (one node per object, vertex colours, UVs on the texture pages, textures embedded) for any viewer; --map-tool import-model DIR in.glb takes back moved vertices, UVs and colours from such a file as long as nothing else changed. Details: DECOMP.md "Editing textures", "Exporting models".

Map editor

The game has a map editor for the files in maps/: exits and triggers, camera limits, props, people, the map's own values, the farm's starting tiles and ground textures. Open it with build/hmbtn-de --editor [MAP] (MAP: a directory of maps/, e.g. 38_beach_spring, or an area index; default the farm in spring): it starts a new game, skips its introduction and opens the map. Or press F2 in the field of your own game, where the player can walk. It needs the GPU renderer (Graphics HD). Its text is readable at every Resolution: at 1x it uses a pixel font of its own, at 2x and up the game's font. While it is open the game waits as under its menus.

The area is drawn by the game from a free camera, with the editor's overlays: exits green (with their destination), other triggers orange, camera limits yellow, props blue, people magenta (their walking steps joined), walk areas blue and animal warps pink, the farm's or a room's tile grid, and the point under the mouse red. ≠N on a label: N linked maps differ there (below).

Everything works with the mouse (the keys stay):

  • Toolbar: Select (1), Move (2), Rotate (3), New exit (4), Tiles (5), Paint (6); Play, Save, Undo, Redo; GRID, LABELS, AREAS; Open a map; Help. Every button has a tooltip.
  • Camera: right drag turns, middle drag moves, the wheel zooms, a double click centres on the point. A right click without moving opens a menu for what is under the mouse (delete, pick arrival, new exit here, play from here, centre here, undo/redo/save; tile commands).
  • Scene: click selects (hover highlights); Move drags it (Shift: 100-unit grid), the corners of a selected box resize it; Rotate turns a prop by dragging sideways (Shift: 45 degrees); New exit draws its box by dragging on the ground.
  • Panel: a number is changed by dragging it sideways, the wheel over it, -/+, or a click and typing (Enter); x/y/z in one row. destination and music open a list (type to filter, wheel, click); flags open their bits as checkboxes. Actions are buttons.
Keys Action
1 … 6 the tools
W A S D, Q E, R F, T, Home move, turn, tilt the camera; top view; the map's centre
Tab, M or Esc, Del the next thing; the map's values; delete
[ ] (Shift: finer) turn the selected prop by 45 degrees (a 64th turn)
N, P new exit at the mouse; pick the arrival point of the selected exit on its destination
O, F3, Ctrl+N open another map; rename this map's directory; copy this map as a new area
Ctrl+S, Ctrl+Z, Ctrl+Y save; undo; redo
F5, F1, F2, G L V play from the mouse (F5 there returns); help; close; grid, labels, areas

Linked maps. A place exists as several maps: its four seasons, the farm also by upgrade (00_farm_spring … 17_farm_6_winter, 24 maps), houses by size. The maps of the same area kind and id are linked: an edit of a trigger, prop, person's step, the camera limits or a map value (not the variant) goes to every linked map that had the same value there, value by value. Where a linked map already differs in the original files (a prop only in winter, an exit's destination per season), it keeps its value and the editor asks: KEEP THEIRS or CHANGE THEM TOO. The panel lists the linked maps (= the same, ≠ different for the selected thing) and has 1 SEASON to edit this map only. Textures and models are per map.

Tiles (5). The farm field (45 × 25 tiles) and the greenhouse (7 × 7) by the game's grids. Click a tile, drag a rectangle, Shift adds, Ctrl toggles, Ctrl+A all, Esc none. The panel shows the selection: ground (untilled, tilled, watered, seeded), the object and its growth stage, walkable, and for one tile its raw Tile values. The palette on the left has the objects the field code knows, drawn by the game's own sprites: weed, stone, branch, stump, the 2 × 2 large rock, boulder and big stump, the crops of every season and grass. It offers only what the game allows on the selected tiles (crops on tilled soil, nothing planted on the farm in winter, ...); the tooltip says why not. Two things can be edited:

  • NEW GAME: the farm a new game starts with, maps/farm_tiles.json. A new game picks one of three layouts at random; the editor shows one (A, B, C) and edits all three (ALL 3). The game's init puts crops ripe on tilled soil, everything else on untilled ground. While the tile tool shows it, the field shows the layout; the game's own tiles come back afterwards.
  • THIS GAME ("edit save"): the tiles of the running game, kept when the game is saved: ground, objects, crops and their stage.

The farm's tiles are one grid for every season and farm variant: they are game state, not part of a map file.

Paint (6). Ctrl+click a textured ground polygon to take its texture (coordinates, CLUT, page) as the brush; move it on its texture page in the panel or turn it; click or drag over other polygons to give them the brush. Saved into the map's model.bin with the structure unchanged. The farm field's quads are not painted: the game retextures them from their tiles.

Workflow:

  • Edit and play-test. Changes show at once and the game uses them before they are saved: F5 plays the map from the point under the mouse, with the edits; F5 again goes back. The top bar says * unsaved while something differs from its file.
  • Save. Ctrl+S (or the Save button) writes map.json of every changed map, model.bin of painted maps and farm_tiles.json. Before a file is replaced it is kept as .bak (the version before) and, the first time, as .orig (as exported); index.json likewise.
  • A new area. Ctrl+N (or the panel's copy as new area) copies the open map, with its unsaved edits, under a new name: a new directory <index>_<name> with the next free area index and a new area id, its model, textures, schedule and resident data copied, listed in index.json. The copy opens in the editor. To get there, select an exit on another map (or make one), choose the new area as its destination and pick the arrival point; save; play. The copy's own exits lead where the original's do.
  • Rename. F3 renames the open map's directory and every name of it in index.json and the other maps' exits.
  • Back to the original maps. build/hmbtn-de --export-maps (or make maps) writes every map, index.json and farm_tiles.json anew from the game files: areas made with Ctrl+N are then no longer listed (their directories stay; delete them by hand). A single file is restored from its .orig, or the version before the last save from its .bak.

Not editable in the editor: model geometry and texture images (--map-tool, above), and the walk areas and animal warps (shown; they belong to the place in index.json). A new area has the next free index and is tested up to index 255 (one copy on the original maps).

Controls

Gamepads keep the PS1 layout by position (re-hmbtn's README) and vibrate where the game vibrates the pad (tools, fishing, the horse race; the Options menu's Vibration setting applies). The keyboard and mouse use the PC layout (Controls page: Layout PC), or the PS1 keyboard layout of re-hmbtn (Layout PS1, exactly the keys of before).

PC layout In the field In menus, message boxes, cutscenes
W A S D, arrow keys walk move the cursor
Shift (hold) run (○ held)
E talk, pick up, open (✕) confirm (✕)
right click talk, pick up, open (✕) back (○)
Space, left click use the tool, hold to charge (□) confirm (✕)
Enter status screen (Start) confirm (✕)
Esc pause menu back (○)
I, Tab rucksack (△) Tab: Start
M map (Select) Select (settings on the status screen)
mouse wheel down, R next tool (R1) wheel: cursor down; R: R1
mouse wheel up, Q previous tool (R1 + ←) wheel: cursor up; Q: L1
F / G next / previous rucksack item (R2 / R2 + ←) F: △ (item info)
Z / X whistle for the dog (L1) / the horse (L2) L2 / R2
C □
Backspace back (○); on the name screens: delete the last letter
F12 screenshot screenshot
- / = zoom out / in zoom out / in

Name screens (the player's, the farm's, the dog's and the animals' names): with the PC layout type the name on the keyboard. A printable key (letters in both cases, digits, space and the punctuation the game's font has: ! ? . - + ( ) / : ; ' " & ~) enters its letter, the grid's cursor jumps to that letter's cell (changing the page when it is on the other one) and the usual OK sound plays; keys the font has no letter for are ignored. Backspace deletes the last letter, Enter finishes the name (as ✕ on the grid's OK) and, in the confirmation, answers Yes; Esc is the back button (deletes the last letter, or leaves the screen when the name is empty, or answers back at the confirmation). Arrow keys and gamepads move the cursor and use ✕ as before; Tab is Start. While a name screen is open, W A S D, E, Space and the other letter keys type instead of acting. Characters come from the operating system's text input (SDL_TEXTINPUT, started only while a name screen is open), so any keyboard layout types what its keys show. A typed letter is entered exactly as ✕ on its cell is (NameEntry_TypeCell), so the name and the saved game are the same as a pad would have entered. The PS1 layout and gamepads see no change. rt_input_text_* (re/recomp/runtime/rt_input.h) is the runtime side; the debug automation types with text STRING and key BackSpace / key Return.

Enter (any confirm key) also acts as Start on the title's press-start screens. In cutscenes Start is Tab only, so holding a confirm key to hurry the text never skips a cutscene. Esc on the title screen asks "Quit the game?".

Every key is rebindable on the Controls page: ✕ / Enter on an action, then press a key, mouse button or the wheel; it is added to the action's keys (Backspace instead clears them, Esc cancels). A key used by another action of the same context is not taken silently: the menu names the action and offers to move the key. The same key may serve one action in the field and another in menus (E: interact / confirm); F12 and the zoom keys work everywhere, so they clash with any other use. "Defaults" restores the layout. Bindings are saved as bind.<action>= lines (re-hmbtn's README); a file that binds one key twice keeps the first binding, logs the other, and the Controls page shows it in red marked ! with the action it clashes with.

How it works (not in the original game): src/ui/pc_controls.c turns the runtime's actions (rt_input.h) into PS1 pad bits in Pad_Update (port 0), once per game frame, where the game reads the pad. The context comes from game state: the field with no menu (g_systemFlags 0x18000000), message box, cutscene or pause menu, or anything else. Keys held while the context changes count again only after a release, so Space used on a tool does not confirm the message it opens. Next / previous tool and item are the game's own R1 / R2 (+ ←): Player_CycleTool / Player_CycleItem (src/ovl11/player_tools.c) always step forward in the original (the direction argument is overwritten); in the PC layout they step back on ←. The PS1 layout, and every headless run without --settings-menu, sees the pad bits exactly as before.

Mouse in menus (PC layout, in the window; not in the original game). Every screen with a hand cursor follows the pointer: moving the mouse puts the cursor on the item under it (with the cursor sound, and only while the mouse actually moves, so keyboard and gamepad navigation are not fought by a resting pointer); a left click selects it as ✕ would; a right click goes back as ○; the wheel moves the cursor up / down as before. A click on nothing does nothing. Where a screen shows arrows, clicking one presses ← / → (status screen pages, the item box's pages, recipe pages, the quantity box of shops: its − / + arrows; the wheel over a quantity or over a settings value changes it by one). In the settings window a click on the left / right half of a value is its ← / → arrow, a click on a page tab opens the page, and the wheel over a value changes it. Covered: the settings window, pause menu, save list and its questions, title menu, calendar, message box choices and yes/no boxes (the common code, so every dialogue, shop answer and the name / birthday confirmations), the Start status screen (page arrows), rucksack, item box, world map, Options / Guide / Settings pages, name entry grid, birthday picker, the shop and tool-upgrade lists with their quantity box, the order form, the animal pickers, the kitchen's four screens, the bedtime prompt (diary choices) and the original memory card screens (file, slot, question, animal exchange). Not covered: the TV, mini-games and races (no menu), the memory card screen's fixed marker. The OS pointer is hidden during field play and shown in menus while it moves.

How the mouse works: a screen registers its items as rectangles of the 320x240 screen each frame it takes input (include/mouse_menu.h, src/ui/mouse_menu.c: the item's index goes into the screen's own cursor variable, so the original code moves, confirms and plays its sounds as for the pad; nothing is registered without the PC layout, so game state and every test run are unchanged). Pad_Update (pc_controls.c) turns a pointer move or click into the cursor move and a ✕ / ← / → press of the frame after the screen registered it. The runtime maps the window to screen pixels (rt_input_mouse, over the 4:3 picture, widescreen margins left and right); menus sit in 0..320.

Skipping cutscenes

While a cutscene plays in the field, Hold START: Skip shows at the bottom right (Hold Tab: Skip with the PC layout); hold Start (Tab on the keyboard, rebindable as "Start" in the controls) for 1.5 seconds to skip it. The hint fills in red while Start is held; letting go earlier does nothing. Skipping runs the rest of the cutscene script at once, leaving out only the waiting (messages, walks, timers, camera moves, fades): event flags, items, time changes, area changes and the cutscenes it leads into all happen as if it had been watched, so the game continues as normal. The skip stops for questions, which still need an answer. Skipping the arrival takes the player through the whole introduction (Mayor, flashback, the house) in 10–20 seconds of area loading instead of more than four minutes, with the same event flags as watching it; the Mayor's village tour takes about 5 seconds.

Like the settings menu, this is not part of the original game (src/ui/skip_cutscene.c) and is offered only with a window or --settings-menu.

Saving

With Saves on Files (the default with a window), games are save files in saves/ in the working directory (next to hmbtn_settings.ini), one .hmsave file per save:

  • Loading: CONTINUE on the title, or "Read diary" at the bed, opens the list of saves, newest first, each with farm and player name, date, time of day, money and play time. ✕ loads, □ deletes (after a question), ○ goes back.
  • Saving: the bed's diary has a new choice, Make diary entry: it saves the game as it was when the diary was opened and play goes on, with no sleep and no time skipped. "Make diary entry and then sleep" saves the next morning, as the game does. Both show the list: the first row is a new save; choosing an existing one overwrites it (after a question).
  • Autosave: every morning the game is also saved to saves/autosave-1..3.hmsave, the oldest of the three being replaced.
  • Memory cards: the diaries FARM0..FARM2 on the memory cards (hmbtn_slot1.mcd, hmbtn_slot2.mcd) are copied into saves/ once (saves/imported.txt remembers them).

A mid-day save loads exactly where it was made: same area, position, facing, time of day, weather, tool in hand and game state, and the people and animals around as they were: NPCs with their position, facing, walk and schedule step, the dog, horse, livestock and other animals with their positions and held / following state, and the camera. The game's own load starts the day in the house; the game's own area transition then takes the player to the saved place (behind the loading screen), and the saved state is put in RAM again there. On the first frame after loading, the character and animal tables equal the save moment; as on any area entry they stand still during the short fade-in. What stays approximate: weather particles and ambient effects, spinning props, the music (the same song, from its start) and sound effects. Mid-day saves from before this (file version 1) still load, with the characters placed as on entering the area.

A save file holds the game's own 32 KB memory card image unchanged (src/ovl15/saveload.c describes it; images made in the field have no card icon/title header), a header with the list's information and an 80×60 thumbnail, and for a mid-day save the area, the position and the RAM of the characters, animals and camera (file version 2). With Saves on Memory card the game's card screens run as on the PS1; headless runs without --settings-menu always do. The save code (src/ui/save_files.c, src/ui/save_menu.c) is not part of the original game.

Quitting

In the PS1 layout, and on the title screen in either layout, Esc pauses the game and asks Quit the game? in a game-style window: Yes (Enter / ✕) quits, No (Esc / ○ too) goes back to the game exactly where it was. This is runtime code (re-hmbtn), drawn over the picture while the game waits, so game RAM is untouched.

In the PC layout, Esc in the field opens the pause menu (src/ui/pause_menu.c, not in the original): the field waits as under the game's own menus, with Resume, Settings and Quit (asks first: unsaved progress is lost). Esc in a menu goes back. Closing the window still quits at once.

Working on the decompilation

See DECOMP.md. In short:

make difftest    # each decompiled function vs. its recompiled original, random states
make lockstep    # route vs. the pure recompilation, game RAM every game frame
make coverage    # same route; lists game code still running recompiled
make lockstep ROUTE=day1   # routes: tools/routes/<name>.script, listed in DECOMP.md (Routes)
make lockstep-all DATA=files   # every route, the decompiled build reading assets/
make statestep-all         # every route: game state compared as objects, not addresses
make check                 # difftest + lockstep-all (the full suite, about 20 minutes)
make state-schema          # regenerate the host-state schema after adding a region
make maps                  # export the areas to maps/ (the map data files)
make route-coverage        # every route: remaining functions some route runs, per segment

Some routes start from a memory card (your own saves, never committed): tools/routes/cards.json maps the card roles late and day3 to card files. The defaults in tools/routelib.py are paths of the first machine: on another one (the Deck above) map every role the routes use (late, day3, date_summer7, date_summer12, farm1) there.

make statestep-all compares against the recordings in build/state/ (made with make statestep-record, they hold game state and are never committed): copy that directory along when checking on another machine.

Per-function context packs (ctx/, generated from your own disc, not in git) contain Ghidra pseudocode and the exact recompiled C.

  • tools/psyq_names.py names SDK functions using psx_psyq_signatures and generates include/psyq_names.h.
  • tools/targets_first_cutscene.json lists the functions on the milestone path.

Debug automation

HM_DEBUG=<file> runs a command script against the live game, one step per vblank. src/debug/debug.c documents every command. Commands that call game code run between game frames with the emulated clock frozen, so they don't disturb timing; skip_cutscene is the exception: it plays out over game frames like the in-game skip.

cat > /tmp/day.txt <<'EOF2'
wait 5700              # tools/routes/new_game.script reaches the farm by then
skip_cutscene          # skip the intro cutscenes as holding Start does: ends in the house
use_exit 0             # out to the farm: the Mayor's tour starts
skip_cutscene          # questions are answered with x
state                  # area, position, facing, day/time, control
collision_map          # walkability grid around the player (the game's own collision probes)
walkto -3888 2786 run  # A* path around fences, rocks and buildings (searches near the player)
walkto -50 50 run      # on to the field
spawn 0x109            # a cow next to the player
exits                  # this area's triggers; use_exit N takes one
use_exit 0             # into the house
shot /tmp/house.ppm
quit
EOF2
HM_DEBUG=/tmp/day.txt build/hmbtn-de game.iso --headless --fast --frames 100000 \
    --script "$(cat tools/routes/new_game.script)"

Other commands:

  • input and waiting: press, advance (✕ until the player has control), wait_control; PC-layout input (with --settings-menu): key LShift+W 30, key E, key Esc, mouse left, mouse wheeldown 2, mouse_at X Y (the pointer at 320x240 screen pixels: hover and click in menus, then mouse left; framerate 0 makes gpushot follow the game at once);
  • the world: setpos, teleport, clear_field, tiles (raw farm tiles);
  • the player and calendar: money, held_tool, day/season/weekday/weather (shown from the next area load);
  • raw state: flag, peek/poke, wait_peek (wait until RAM holds a value; logs the vblank), ramdump PATH (2 MB of guest RAM);
  • settings as the in-game menu changes them: setting display 1, setting scale 2, setting geometry 0, setting framerate 0|1|2 (30 / 60 / display), setting controls 1 (PS1 layout), setting saves 0|1 (memory card / files), setting volume_music 0-100;
  • save_file [PATH [RAM]]: a mid-day save file as the diary's "Make diary entry" makes it, from wherever the player stands (RAM: also dump the RAM it was made from);
  • gpushot PATH: the GPU renderer's frame at internal resolution (with --renderer gpu);
  • the map editor (with --editor): wait_editor (until it is open on a loaded map), editor_cam X Z DIST YAW PITCH, mouse_world X Z [Y] (the mouse to where that point is drawn; without Y on the ground), mouse_at X Y, mouse_down/mouse_up [left|right|middle], key_down NAME/key_up NAME (a key held across commands: Shift-click), editor_state (tool, selection, hover, the point under the mouse, camera, linked maps, the tile selection);
  • present_seq DIR HZ N: for the next N vblanks, alongside the following commands, the pictures a display at HZ shows with frame interpolation, as DIR/f00000.ppm, ... (the same pictures as a window at that refresh rate; with setting framerate 0, the game's own);
  • resume_ramdump PATH / wait_resume: write guest RAM on the first frame after a mid-day save loaded / wait until it has (compare with save_file's RAM to check a resume).

The hooks do nothing unless HM_DEBUG is set; lockstep runs are unaffected.

How it fits together

  • tools/gen_thunks.py reads the /* <segment> <ADDR> */ marker above every decompiled function. It generates register-ABI thunks under the recompiler's names, so recompiled code, jump tables and interrupt handlers call the C version unchanged.
  • The recompiler emits the replaced functions as orig_*, and difftest uses them as the reference.
  • Calls go to named C functions; CALL(fn) is only for function pointers stored as guest addresses (no code is called by a numeric address any more).
  • Decompiled code reaches guest memory through typed accessors (M32, GVAR, ...); the game's own state lives in host structs (g_player, g_npcs, g_clock, ...), not at PS1 addresses.
  • Game memory keeps the PS1 layout in its first 2 MB, with 32-bit guest pointers (gptr); the area data and the packet buffers live above, in 32 MB of RAM (DECOMP.md, "Memory layout").