Skip to content

Game Data for Modders

Kevin Kline edited this page Oct 17, 2026 · 1 revision

Game data for modders

What you would need to know to change the game's data rather than its code: where enemies, encounters, bosses, chapters, flags, items and the party live, how each is laid out, and which pieces are copied across many files so that a change has to be made in every copy. This page is the mechanism; it quotes no game values. Offsets are the Japanese PlayStation release's unless a column says otherwise; the PC port's differences are listed where they matter.

1. Where things are

Thing Where Form
an area's enemies, encounter rows, map, placement script, messages WORLDnn/AREAnnn.EMI data sections (EMI Section Types)
an area's event logic (handlers, exits, hooks) the same file's code section at 0x801F2C00 MIPS code with a descriptor at its end
which boss a fight is, its flags, arena and formation GAME.EMI section 0, a 56-record table data inside a code section
a boss's behaviour BOSS/BOSSnnn.EMI, code at 0x800C1800 MIPS code
a story chapter SCENARIO/SCENAnn.EMI, code at 0x801F6C00 MIPS code, entered through a vtable
items, abilities, their names, prices and descriptions GAME.EMI section 0 (tables), the system pool at 0x80014000 (descriptions) fixed-stride records
the party's starting records START.EMI, eight records of 0xA4 bytes the save's own record format
which song an area or a battle plays the boot executable, a song table (file, sub-song) indexed by the song byte data in the boot EXE
the story flags, the item-spot flags, the chapter state RAM, inside the save block bit arrays

Code and data are not separable by section type: the boss table and the item tables are bytes inside code sections. A data mod to those is a byte patch into an overlay image; on the PlayStation that is just the file, on the PC port the overlays were recompiled into BOF3.exe and the tables moved with them.

2. Enemies

2.1 The species table, one per area

Every AREAnnn.EMI has a 1,160-byte section at 0x800E4000 (PC: kind-0 chunk, tag 0xC2000, 1,192 bytes). It is two things back to back:

+0x000  8 encounter rows x 9 bytes          (section 2.2)
+0x048  8 species records x 0x88 bytes      (PC: x 0x8C, name widened 8 -> 12)

A species record, offsets from the record's start:

PSX PC field
+0x00 +0x00 name, 8 bytes (PC 12) in the game's single-byte text codes, zero padded; the US disc has ASCII here
+0x0A +0x0E action-odds index: a byte in an engine table whose four 2-bit fields are the chances of attack / defend / escape / ability, one picked by Rand & 3
+0x0E +0x12 u16 flags
+0x10 +0x14 u16 zenny
+0x12 +0x16 u16 EXP
+0x14 +0x18 u16 level
+0x18 +0x1C eight ability ids, one picked by Rand & 7 when the action is "ability"
+0x20 +0x24 u16 max HP
+0x22 +0x26 u16 max AP
+0x24..+0x2A +0x28..+0x2E u16 attack, defence, agility, intelligence
+0x34 +0x38 four AI rows of 16 bytes (section 2.3)
+0x78..+0x80 +0x7C..+0x84 nine affinity classes: fire, ice, lightning, earth, wind, holy, psionic, status, death
+0x82, +0x83 +0x86, +0x87 the two size bytes the encounter placement uses to test the enemy is on screen
+0x84 +0x88 kind byte: 0 for an ordinary enemy, else an index into the boss-script table (section 3.3)

Two drop slots (an item id with its category, and a chance class) are in the record as well; the steal command reads the first slot and clears it. Their exact offsets in the file record are not pinned down in the notes yet.

The affinity classes are indexes into percentage ladders, not percentages, and the three ladders differ: the five elements share one ladder (neutral at class 2, and a multi-element hit sums the classes), holy has its own (neutral at 5), and psionic / status / death share a third (neutral at 2, highest bit wins). Changing a class by one step is a different size of change in each column.

A correction to the archival repo's reading: its enemy_table.py and BATTLE_RAM.md treat the section start as record 0, so they place the name at +0x48, the stats at +0x54 and the affinities at +0xC0, and conclude that "the fields run past the stride". They do not. The first 0x48 bytes are the encounter rows; shift the base by 0x48 and every field sits inside its record, with the kind byte at +0x84 where the PC-side reading has it.

2.2 Encounter rows

Eight rows of 9 bytes at the section start: bytes 0..7 are the species slot (0..7) in each of the eight battle positions, 0xFF for none; byte 8 is the row's weight. A random encounter rolls (Rand & 0xF) + 1 against the running sum of the weights, so the eight weights are meant to total 16. Positions 0..2 always appear if their slot is set; positions 3..7 appear with chances 10, 8, 6, 4 and 2 in 16. The placement then tries to fit the party and every enemy on screen in the field, and drops enemies that do not fit.

The same rows are the formations a boss record points at (section 3.1), so an event battle's line-up is one of the area's eight rows too.

2.3 The AI rows

Each species has four rows of 16 bytes:

+0   condition code
+1   change kind (1..7: scale a stat, set a resistance byte, change the action odds, ...)
+2   mask (which stat, or which element)
+3   value (a factor in tenths; the level case uses its square)
+4   action-odds index to switch to
+6   u16 message id shown when the row fires
+8   eight ability ids replacing the record's list

Conditions come in two families: tested before the enemy's turn (HP below a quarter or a half, AP low, one enemy left, the turn counter even or odd or a multiple of three, a party member's level far above, every other row fired) and tested after a hit (hit by an element, hit by an ability or an attack, HP at or below the damage just taken). Rows apply in order 0..3, each overwriting the odds and ability list, so a later row wins. A row fires once, marked by a per-row done bit on the battle object, except the "every time" element conditions (0x21..0x24). Condition 0x63 is an unused row. This is the five-section scheme the fan Enemy Database describes, and the two agree on every enemy compared.

The Volt is the worked example: one row is "hit by lightning, scale EXP by a factor, show a message". On the PlayStation the done bits start clear every fight because the battle objects live inside the battle overlay image, reloaded from disc on every battle entry. The PC port keeps those objects in static memory and never cleared the bit, so a Volt in a slot that had fired in an earlier fight paid nothing; this project repairs that (DIV-0082). Anything else the PlayStation resets by reload is a candidate for the same class of bug.

2.4 Replication: the same enemy in many areas

The species table is per area, and most species appear in several areas. Over the 200 areas there are 448 filled slots but only 175 distinct records; a Goblin is the same bytes in every area that has one, and a change to a species has to be made in every area file that carries it. The names sidecar in the archival repo (names/enemies.toml, from tools/enemy_table.py species) lists where each species appears.

Sound follows the same shape: BENEMY/ENEMYnnn.EMI is the bank-6 sound set for AREAnnn's eight slots, one program per slot, and the eight-slot set is one of 58 distinct groups. The slot index is the creature cue (0x600 + 2 * slot + tone), so a species moved to another slot changes which sounds it makes unless the sound file moves with it. The battle also reads the slot as the row of the species' AI, so slots are an identity, not just a position.

3. Bosses and event battles

3.1 One byte, three tables

A fight is a boss fight when the boss id byte is non-zero (RAM 0x801462E6; PC 0x904AAA). Everything else is table-driven from it:

rec      = Boss_EncounterTable[id]      56 records of 4 bytes, in GAME.EMI section 0 (PC: EventBattle_Records in .data)
  +0  battle flags            copied to the battle's flag byte
  +1  arena / camera id
  +2  formation index         a row of the CURRENT AREA's encounter rows (section 2.2)
  +3  file row
file     = Boss_FileTable[row]           41 entries: row 0 is BATTLE.EMI, rows 1..40 the BOSS files
entry    = Boss_EntryTable[id]           56 entries inside the battle engine, each a pc in the boss file's code

So a boss record does not say which enemies appear: the formation row of the area the fight starts in does, and the eight species are the area's. A boss must be dialled from the area whose table holds it. The boss id is set by one setter (Battle_StartBoss) with 49 callers, all in the scenario chapters with a constant id; ids 39..48 come from one dynamic call in chapter 15. For a random encounter the file is BATTLE.EMI in chapters below 8 and BATTLE2.EMI from chapter 8 on.

3.2 A boss file is a whole battle

BOSSnnn.EMI carries a copy of the battle engine and the battle game mode, the system message pool, its own pages and sounds, and the boss code at 0x800C1800. The entry table lives in the engine image and is the same in all 40 copies, which is why the boss images can be linked against fixed addresses. The enemy sound file for the area is skipped when a boss id is set; the boss file brings its own bank 6.

3.3 Kinds: the per-enemy scripts

The species record's kind byte (section 2.1) selects an entry in a 63-entry table of per-frame enemy scripts. Kind 0 is the generic enemy. A non-zero kind's script runs every frame of the fight for that enemy, driving its entrance, its action choice and its death through its own state tables, and may hang hooks on the enemy object (a hook pointer and two table pointers). A boss set-up (the entry-table routine) runs once at the fight's start and installs three hooks the battle flow calls: per frame, at the transition, and from the action phases. The set-up re-points the hooks as the fight progresses, so a boss's whole script is a handful of functions plus its tables.

Two things follow for a mod. A kind is an identity across files: the Nue is one kind in one area and a different kind in another, with different scripts. And an ordinary enemy given a boss kind byte will run that kind's script, which assumes its own file's art, sounds and tables are loaded.

4. Chapters and the scenario state

The story is a linear chain of 20 chapter overlays. The chapter number is one signed byte (RAM 0x8014686C; PC 0x8034E0), and the file is SCENA<nn>.EMI with id chapter + 0x295.

A loaded chapter is entered through a record in GAME.EMI: a 5-slot vtable inside the chapter's image, called in fixed roles:

slot called
0 every field frame
1 on an object trigger
2 the step hook (a cell entered)
3 the arrive hook (an area entered)
4 optional

Two further tables per chapter give the pcs the chapter's own code calls through. Chapters 17..19 are stubs on the disc; 17 is the staff roll and forces the number back to 15 when it ends.

Progress is driven by the running chapter itself: a chapter calls Scenario_RequestAdvance, which sets bit 0x80 of the byte after the chapter number, and the field loop then sets the chapter to n + 1 (or n + 2 when that would be 16, which is reserved for the attract demo and entered by game mode, never by the chain). Setting a chapter zeroes a 16-byte scenario state block at the chapter byte, zeroes that chapter's 8-byte progress record (at 0x80144E84 + n * 8) and zeroes the chapter's own row of condition flags (section 5), then loads the file and calls slot 0 of the vtable.

The 16-byte block is saved and restored whole; it is the first 16 bytes of the save's game block. A new game starts at chapter 0. A save edited to another chapter with the rest of the block zero starts that chapter from its own start routine, which places the party itself; chapter 17 (the ending's opening) has been reached this way.

No script opcode selects a chapter and no chapter is reached from an area's script: the chain, the new-game start, the demo and a load from a save are the only four entries.

5. Flags and conditions

Three bit arrays, all inside the save block, all driven by four eight-instruction helpers in the boot executable (Flag_Set, Flag_Clear, Flag_Test, Flag_Toggle, each taking a base and a bit index):

array PSX PC used for
story flags 0x80144F24 0x904030 the game's progress bits; the event scripts, the effects and the chapters test and set them
item-spot flags 0x80145000 one bit per searchable spot that has been emptied
condition rows Cond_Flags, 32 rows 8 bytes apart 0x903F90 the event script's if and switch conditions, banked

The story flags are the row at Cond_Flags + 0xA0. The event script's condition byte c names a bit in row kind & 0x1F of the condition rows (kinds 0x20..0x3F are the same bits negated, and 0xFA..0xFF compare six globals instead, the chapter byte among them). A script can switch the row with F9 b and switch back to the chapter's own row with FA, which is how one area script serves every chapter: each chapter has a row of its own, and setting a chapter zeroes it.

The event script itself is the byte stream at an area descriptor's +0x00, run on every area entry. It places the area's objects (who stands where, facing which way, with which animation bank, speed and movement script) under F0 c x ... [FD ...] FE (if / else), F1 (if not) and F4 c { F6 x ... F8 | F7 ... F8 } F5 (switch), ending at FF; an op below F0 is one of fifteen placement handlers by its high nibble. All 200 areas' scripts decode, and they are byte-identical between the PlayStation and the PC.

6. Areas: descriptor, exits, search spots

The area's code section ends in a 0x44-byte descriptor, reached from a 200-entry table in the engine. Its fields that matter for data:

offset what
+0x00 the placement script (section 5)
+0x20, +0x30 the exit links: 12-byte records, +0x30 the index of the last. A link is a run of cells from (+0, +1), +9 long, along z when +8 is set else along x; +2 the destination area, +4/+6 the arrival cell, +0xA flags. +2 = 0xFFFF means a conditional exit: +4 indexes lists of 10-byte alternatives (+0x24 the lists, +0x28 their counts), the first whose flag test holds wins
+0x34, +0x3C the choice handlers and the handler array, pcs into the area's own code
+0x40 the init routine, called once after load (may be 0)

Searchable spots are a per-area array of 8-byte records reached from a constant table in the boot executable, so the whole world's placements are readable without running the game: x cell, y cell, a flags byte (bit 0x80 runs along y), run length (0 disables the record), the bit index in the item-spot array (0xFF means no flag, so repeatable), the item id (or zenny divided by 40 when the category is 0xFF; bit 0x80 means the spot is empty), the inventory category, and a zero byte. Categories 0..3 are consumable, weapon, armour, accessory; key items are a separate list and no spot gives one. 517 records across the 200 areas, 395 of them empty spots.

7. Items, abilities and the party

The non-dialogue tables are fixed-stride records in code sections; the record index is the id a save or an enemy record holds:

table where record
consumables GAME.EMI section 0 14 bytes: name 8, u16 flags, u16 description id, u16 price
key items same 12 bytes: name 8, u16 description id, u16
weapons same 20 bytes: name 8, three u16, u16 power, u16 description id, u16 price
armour / shield / helm same 18 bytes: name 8, two u16, u16 power, u16 description id, u16 price
accessories same 16 bytes: name 8, u16, u16 effect, u16 description id, u16 price
abilities same 16 bytes: eight parameter bytes (byte 1's low two bits are the type: healing, support, attack magic, skill), name 8
new-game templates START.EMI eight character records of 0xA4 bytes, the save's own format

The description id is a message number in the system pool's second sub-table. An ability's type decides which of a character's four ten-slot learned lists it goes into. A spell's effect is a separate overlay (MAGIC*.EMI), chosen by an ability-to-file table; the steal roll, for instance, is inside the Pilfer and Steal overlays, not in the battle engine.

The character record (0xA4 bytes, eight of them at save block +0x90) holds the name, character id, level, EXP, persistent status bits, HP and AP, six equipment slots, the effective stat block, the nine affinity classes, evade and hit percentages, the six base stats, the four learned-ability lists, and six signed growth modifiers added to the table growth at each level. Equipped items are removed from the inventory, not flagged. The PC port widened the name field from 5 to 9 bytes and shifted every later field by 4 inside the same stride; its save file is the PlayStation's 0x10B0-byte game block from offset 0, zero padded to 0x12B0, same checksum (a u16 byte sum at block +0x70, the word itself excluded).

8. Things that are not where you might look

  • Enemy stats are not in the boot executable or the battle engine. They are in the area file, and ENEMYnnn.EMI holds sound only.
  • Boss line-ups are not in the boss file. They are the current area's encounter rows.
  • The chapter is not chosen by any script. Only the chain, the new game, the demo and a load set it.
  • Names for things with ids are not in the message blocks. They are in the fixed-stride tables; the message blocks hold dialogue and the system pool holds descriptions.
  • Music is not in the area file. A song byte selects a (file, sub-song) pair from a table in the boot executable.
  • On the PC port the overlays are compiled into the executable, so every table that sits inside a code section (bosses, items, abilities, the chapter vtables) lives in BOF3.exe's data, at PC addresses; only the data sections still come from DAT/.

Sources: LOADER_RECORDS.md and its loader_records/ reports, BATTLE_RAM.md, WORLD_ITEMS.md, TEXT_TABLES.md, STEAL.md; enemy_ai_data.md, trigger-mode-enemies.md, field_hidden.md, takeover-queue-bosses.md, scenario-roots.md, event-script.md, area-entry.md, save-interchange.md.