Boot path
Everything between the disc spinning up and the moment you press NEW GAME. The publisher logos fade by, the wordmark appears - and behind that the game reads its archive directory, streams its first code overlay and enters the “what mode am I in” state machine that runs for the rest of the session. This page follows that chain, the title screen, and the scripted opening NEW GAME starts. The per-address tables live on boot internals.
At a glance
- Entry point
main()=FUN_80015E90; it never returns in normal play- Archive directory
- The first three sectors of
PROT.DAT(6 KB) land at0x801C70F0; every later load is a table lookup - Mode machine
- 14 modes, each an INIT / per-frame pair, in a table at
0x8007078C; the current mode is the word_DAT_8007B83C - Overlay window
- Slot A at
0x801CE818holds one of field / battle / menu / cutscene / minigame code at a time - Title screen
- Has no mode of its own: it runs under
CARD(modes 22/23), the same pair as the pause menu, from code at the tail of PROT 0899 - NEW GAME
- Writes mode 2 (field) and opens the prologue scene
opdeene; name entry comes later, intown01 - In the port
engine-session::BootSessionruns the per-frame order;engine-mode::mode::ModeSeatholds the mode word;engine-title::title_overlayis the title tick- Confidence
- Confirmed - mode table read off the disc; title tick and launch write pinned by live watchpoints
The boot chain
A cold boot is six writes of a literal into the mode word. Each one hands the frame to the next screen, and none of the screens is named in the mode table - which is why the order has to be read off the stores rather than the table's dev labels.
- Hardware initGPU, CD, sound, heap; the PROT.DAT directory and the CDNAME name map into RAM
- Mode 16 · READ INITone frame: upload the four
init.paklogo textures - Mode 17 · READthe publisher logos fade in, hold and fade out
- Mode 22 · CARD INITstream PROT 0899 into slot A
- Mode 23 · CARDthe title screen, ticking as a spawned actor
- Mode 2 · MAIN INITNEW GAME: stream the field overlay 0897, load
opdeene - Mode 3 · MAINthe field loop - the game proper
Almost every PlayStation game bundles its files into one archive so the disc can stream them; Legaia’s is PROT.DAT, with a table of contents saying “entry N starts at sector S”. Reading that table is job one. Job two is the asset-type dispatcher: each asset’s size word carries a type byte, and one router hands the bytes to the matching handler (asset-type). Job three is the mode machine below.
The main loop
After hardware init, main() spins forever: run the current mode’s handler; if the handler wrote a different mode, tidy up and switch. A mode’s INIT handler runs for one frame and usually streams an overlay into slot A; its per-frame handler runs the game from then on, through the master frame driver that ticks actors, VMs and draws. Writing the mode word is the only way to change what the game is doing.
Most gameplay code is not in the executable. The field VM, dialog renderer and battle logic live in overlays streamed into slot A. “Zero callers in SCUS_942.54” never means dead code - it means the caller is in an overlay. Init stages in order: internals.
The game modes
Walking a town, fighting, browsing the pause menu, watching a movie - to the game these are modes, and exactly one is active. Half the table is dev tooling the retail disc never enters; these are the ones a player sees:
| Mode | Disc name | What it really is | Overlay |
|---|---|---|---|
| 2/3 | MAIN | field and town gameplay, including the three kingdom overworlds | 0897 |
| 12/13 | MAPDSIP | world-map display - a head swap over the field overlay | 0981 over 0897 |
| 16/17 | READ | publisher logos at boot | 0895 (init.pak) |
| 18/19 | GAME OVER | game over | 0902 |
| 20/21 | BATTLE | battle; setup is in the executable, the overlay comes in via the field | 0898 |
| 22/23 | CARD | the title screen and the menu: pause menu, save, options, shops | 0899 |
| 24/25 | OTHER | the five warp minigames, one overlay each | 0972..0977, 0980 |
| 26/27 | STR | FMV movies | 0970 |
- The pause menu runs under CARD, not the field mode, and mode 23 runs no frame driver - so nothing in the world moves while the menu is up.
- The mode word does not say which minigame. All five share modes 24/25; the sub-id the door warp wrote (
op0 − 100) picks the overlay (warp flow). - The dev names mislead. CONFIG is the debug menu, READ is the logo screen, CARD is the title and the whole menu. The full 14-row table, with every INIT handler address and the dev modes, is on internals.
Code that pages in: two slots and one index rule
The executable holds the engine - loaders, the renderer core, the sound libraries, the mode table. Most game logic is not in it. Field scripts, battle, the menu, movies and each minigame are separate code images in PROT.DAT, streamed into one of two fixed windows of RAM when a mode needs them. That is why a function with no caller in the executable is not dead: its caller is usually in an image that is not loaded yet.
- One index rule. Both loaders add
0x381to their parameter and index the in-RAM directory, which counts two words ahead of the extraction numbering - so the entry loaded isparameter + 0x37F. Parameter 2 is the field overlay 0897, 3 is battle 0898, 4 is the menu 0899. - The census is closed. A scan of the whole executable for both loaders finds sixteen call sites, with constant parameters for the fixed images and computed ones for the bands: a minigame's warp sub-id, a spell's cast module, a battle stage.
- Parameter 0 has one producer:
main()itself, loading the boot logo image before the mode loop starts. Parameter 1 has none, so entry 0896 is unreachable from any loader call. - Three init handlers load nothing. The battle mode's setup lives in the executable; the logo mode calls straight into the image
main()already loaded; and one dev mode writes mode 0 and bounces back to the debug menu it came from. - A wipe is not the game-over image. A party wipe routes to the title's CONTINUE flow. The game-over overlay 0902 has an init handler and no retail path that stores its mode.
- Swaps save what they cover. The minigame and map-display modes back up the scene name or the field image's first
0x4000bytes before loading over them, and restore on the way out - which is how a minigame returns you to the room you left.
The practical consequence for tracing: an address in the overlay window names a routine only together with the image that is resident. Per-mode handler addresses and what each loads: internals; how an image is identified and extracted from the disc: static overlay pipeline.
The title screen
The title screen is a small program of its own: an attract countdown that eventually plays the opening movie, a Press-Start wait, a two-row NEW GAME / CONTINUE menu, the memory-card manager, and the launch. It runs as a spawned actor under the CARD pair, whose INIT loads PROT 0899 - which is also why the title shares code with the save screen. The tick (FUN_801DD35C) is a 25-way sub-mode switch.
- Attract. A countdown starting at
0x8000drains each frame; when it underflows the game switches to movie mode and playsMV1.STR(cutscene), then comes back to the title. Input re-arms the countdown only while more than0x10frames are left; in the last few frames the input block is skipped, so a held button cannot hold the movie off. - NEW GAME fades out, writes mode 2 and the scene id
opdeene. CONTINUE detours through the card-load path first and then writes the same mode 2. - No arrow cursor. The two menu rows are a band of the title sheet; the selection is shown bright vs dim.
- The menu opens on NEW GAME on the US build, saves or no saves. Startup raises the row to CONTINUE only when its card scan finds a save, and that scan compares every filename against a
BISCUS-94254PRO-prefix no US save carries (they areBASCUS-), so the count stays zero. The row is never reset afterwards: a later title - after a party wipe, or a backed-out Continue - opens on whichever row it last held. The port does the same on both hosts.
State struct, countdown instruction and the packed pad-mask layout the tick reads: internals.
Where the title overlay lives on disc
The title code is the tail of PROT entry 0899 - the same entry whose head is the options-menu bundle - streamed raw and uncompressed straight into the overlay window: 74 sectors from LBA 47227, in five DMA bursts that all land inside the entry. The same tail carries two embedded TIMs, the save-menu UI atlas and the memory-card icon strip. Sector ranges and burst offsets: internals.
What NEW GAME launches
Press NEW GAME and you do not get a name-entry screen. You get a scripted opening - the creation-myth crawl, the Seru intro, the Mist story, a fly-in over Rim Elm - and only once you stand in Rim Elm does “Select your name” appear. Every leg is an ordinary field scene chained by its own script, not a movie.
- The fresh-state seed. Gold is set to a hard-coded 500, a story-flag region is zeroed, and the executable’s starting-party template is expanded into the live
0x414-byte character records, default names included. - The intro skip. Once
opdeene’s timeline raises a scratchpad flag bit, a confirm press fades out, sets thetown01entry coordinates and issues a name-based scene change totown01. The target name is a literal in the field overlay, which is whyopdeene’s own data holds notown01string. - Name entry opens from
town01’s opening timeline (field-VM op0x49sub-op 3): a 6×17 letter grid plus a Backspace / Default / End row, and a Yes/No commit. The cancel button (Circle) takes the last letter. A name holds at most nine letters and 57 pixels. The name lands in the character record at+0x2A7. - How the grid moves. A sideways step wraps inside its own row and walks past the
|separators; a step down into the control row keeps its column, so the way back up lands on the letter it came from. Start jumps the hand to End. End accepts any name with a length, a name of blanks included, and the Yes/No prompt that follows opens on No, tests confirm before Up/Down and has no cancel. Yes stores the name as typed, trailing blanks and all.
Skip conditions, the scene-change packet and the name-entry grid’s addresses: internals.
Boot-time art
Three separate sources feed the boot screens.
- Publisher logos - PROT 0895
init.pak. Four uncompressed TIMs (PROKION, Contrail, “SCEA Presents”, an epilepsy warning) played by the bundle’s own sequencer in the order SCEA, Contrail, PROKION. The fade is not alpha: the quads are opaque and the sequencer scales their vertex colour, so the PSX texture blendtexel × colour / 128darkens them to black. The warning is uploaded and never drawn - its sprite descriptor is the one no emit call passes. PROKION does not fade out: it holds at full while an additive grey quad over the screen rises a step a frame to white, and the white background carries into the loading frames. The port draws that wash on both hosts and is checked against the retail sequencer frame by frame - logo, level, wash grey and the tail's white. - Title wordmark - PROT 0890. One 256×256 sheet holds the orb, wordmark, Press Start prompt, copyright lines and the NEW GAME / CONTINUE footer; retail samples it in bands, and a demo-build label on the sheet is never drawn.
- System-UI textures - raw TOC entry 0,
init_data. The 118 sectors between the TOC and extraction entry 0 are two TOC entries the extraction index skips:init_data(the menu font, the boot cursor and several sprite sheets, whichinit.pakloads and uploads) andgameover_data(the GAME OVER screen's art).
Per-logo offsets, quad rectangles and pacing; the wordmark's source rectangles; the two head entries' TIMs and loaders: internals. The play page and the native window both run the logo stage ahead of the title card.
Debug flags
Two RAM values decide whether the game behaves like the shipped disc or a dev station. On real hardware both read “retail”; a cheat device can flip them, which is how the debug menu on the retail disc becomes reachable.
| Address | Role | Retail value |
|---|---|---|
_DAT_8007B8C2 | dev / retail loader selector, read as a halfword at 40 sites; != 0 takes the PROT-index path, == 0 opens a host path that ends in break 0x103 | 1, written once by main() |
_DAT_8007B98C | debug-mode word tested by the input dispatcher and ~14 field-overlay gates; poking its top byte _DAT_8007B98F to 1 makes every gate read active, and SELECT+△ opens the debug menu | 0 |
History: why both flags were once read backwards
A sweep for absolute lui+offset references saw no writer for the loader flag, because the one write is gp-relative, and concluded it booted at 0; the executable’s header carries b_size = 0, so no BSS is cleared either. The debug byte returned zero byte-granular references because consumers read the word. Both are under do-not-re-walk; the gp-relative blind spot is why the address reference scan has a gp-relative sibling sweep.
Not every debug-menu entry works once it is reached. On a retail console three of them fail:
- TMD TEST (mode 6) stores its two draw buffers at the fixed addresses
0x80400040and0x80419040instead of taking them from the heap. A 2 MB console has no RAM there. The addresses wrap onto the BIOS area and the game's own code, so the console crashes. The branch is thebeqat0x8001F6D8; replacing it with anopshould send the mode down the heap path, but that has not been tested. - TEST (mode 10) has an empty start-up handler, so it hangs.
- READ (mode 16) jumps into the boot overlay
init.pak, which is no longer loaded once the debug menu has replaced it.
The mode table these entries come from is on internals. Found by Stann0x.