Author: MaKiPLshakotay2 (XeNTaX)HalferYagami Light

Complete list of original battle stages: List of battle stages

Info

.X file is uncompressed 3D stage model with texture embedeed. The file contains unused info (not used by FF8 engine), camera data, movement, translations and geometry data.

Battle stages DOES NOT contain pointers to next sections. All pointers are HARDCODED in FF8.EXE. Click me for battle stage pointer list

Name Usually starting with: Description
PlayStation MIPS assembly E8 FF BD 27 01 00 02 Used only in PS version; skipped in PC
Camera data + movement 02 00 08 00 20 00 Camera animations, movement (pre-keyed)
Model section 06 00 00 00 (See geometry section info) Divides to object (group) and it’s sub-objects + vert/triangle grouping
Texture 10 00 00 00 09 (Always) Contains one .TIM texture, 8 BPP

How is this file handled by the engine?

FF8 loads specific file and reads it from hardcoded (written in FF8 code) position (or reads MIPS PlayStation assembly that contains battle stage load code). This hardcoded position points to camera data (in PC). Camera data has size uint16, which is calculated and relative jump is made. Just after camera, a typical section based file handling is made.

Offset Length Description
0 uint32 Number of sections
4 uint32 Objects group #1
8 uint32 Objects group #2
12 uint32 Objects group #3
16 uint32 Objects group #4
20 uint32 End of groups = Texture animation block. Equals the texture offset below when the stage has no animation
24 uint32 Texture
28 uint32 Relative to EOF [unused in code]

The first value (Number of sections) is fixed 06 00 00 00; groups #1..#4 always follow, and group #1 is always at offset 20 (0x20). The last value (Relative to EOF) is the geometry section size and, like Number of sections, is not read by the engine.

Objects Group

Offset Length Description
0 uint32 Number of sections
4 uint32 Pointer to Settings #1
8 uint32 Pointer to ObjectsList
12 uint32 Pointer to Settings #2
16 uint32 Relative to end_of_group
Objects List
Offset Length Description
0 uint32 Number of objects
Num_of_obj*4 uint32 Relative pointer to segment (one .obj file) [01 00 01 00]
Settings 1
Offset Length Description
0 uint16 Unknown, fixed 2?
2 sint16 (probably, or uint16) Scale (01 is the smallest)
4 4 bytes PADDING
8 4 bytes Nothing?
12 4 bytes Nothing?
16 2 bytes if not FF FF, then model vanishes
18 sint16 X or Z Axis rot origin (Translation)
20 44 bytes Left null for storing some engine data AFTER loaded in-game
64 2 bytes if not FF FF or 00 00, then model vanishes
66 2 bytes Nothing?
68 …up to end Left null for storing some engine data AFTER loaded in-game

This might look difficult, but it’s like multi-pointer file that starts at different location given by executable. See list at the beginning of this wiki to see which stages camera starts at which position (where FF8.exe really starts to read file as BattleStage) [FF8 stores whole file, even with useless data before camera] If this still makes trouble, see this video: TODO

Camera data

Starts at ~0x5d4 (see How the engine handles this file?). [.text:00509820]

Offset Length Description
0 uInt16 Pointers count. Fixed (02 00) [Unused in code]
2 uInt16 Relative jump to CameraSetting. Should be (08 00)
4 uInt16 Relative jump to CameraAnimationCollection. Should be (20 00)
6 uInt16 Camera data size (starting from 0) [unused in code]
8 24 bytes Camera Settings
32 ?? bytes Camera Animation Collection

Camera Setting

This 24-byte block is not a fixed structure: it is byte-code executed every frame by the shared battle animation-sequence VM (computeAnimationSequence — the same interpreter used by monster animation sequences and camera sequences). BS_UpdateCameraSequence runs it with the camera opcode handlers (opcodes < 0xC0 are camera actions dispatched by CameraSeq_DispatchActionOpcode (renamed from a2ParamAnimSeqForCamera); 0xC0+ are the generic VM control/arithmetic opcodes). See Function addresses.

This script drives the battle-intro camera. Stage scripts typically start with camera opcode 05 00 = “play the camera animation whose ID is read from camera variable 0” — and variable 0 is filled at battle load with the encounter’s camera data byte from scene.out. This is how each encounter picks its intro camera animation out of the stage’s collection below.

Example — a0stg000.x camera setting bytes: 05 00 C3 10 E5 7F C1 00 CB 7F EA 06 E6 07 E6 02 01 ... (play anim from var 0, then loop while the camera-animation-playing flag is set, then end).

Camera Animations Collection

Offset Length Description
0 uint16 NumOfSets
2 *(Set index) uint16 Relative pointer to camera Animation Set
2 *(Set index)+ 2 uint16 Camera EOF
CameraAnimationSetPointer CameraAnimationSet CameraAnimationSet
CameraAnimationSet

Each set holds exactly 8 animation pointers: the engine masks the requested animation index with & 7 (BS_GetCameraAnimationPointer). The same function bounds-checks the requested set index against NumOfSets and silently ignores the request if it is out of range (no animation plays).

The 8 pointers are signed int16, each relative to the set start and multiplied by 2 to get the byte offset. A slot is empty when its target word is 0xFFFF (idle monsters reuse one 0xFFFF for several slots, so consecutive empty pointers increase by 1).

Offset Length Description
0 int16 (signed) [8 of] AnimPointer (x2 = byte offset from set start)
2*AnimPointer Chain of control-word blocks CameraAnimation

Camera Animation (WIP)

All this is based on best effort knowledge from reversing by Maki. I'm just trying to understand it.

Control Word

Offset Length Description
0 uint16_t (bit varies) Main controller. If 0xFFFFU END

FOV

FOV mode = (Control Word >> 6) & 0b11. Modes 2/3 read one/two words respectively; there is no padding word (earlier revisions of this page listed one - the “Same” case is a single word, “Different” is two consecutive words).

0 Keep
Offset Length Description
None None FOV unchanged (keeps the previous value)
1 Default
Offset Length Description
None None Start = 0x200
None None End = 0x200
2 Same
Offset Length Description
0 uint16_t Start
None None End = Start
3 Different
Offset Length Description
0 uint16_t Start
2 uint16_t End

ROLL

ROLL mode = (Control Word >> 8) & 0b11.

0 Copy current
Offset Length Description
None None Start = End = the camera’s current roll value
1 Default
Offset Length Description
None None Start = 0x000
None None End = 0x000
2 Same
Offset Length Description
0 uint16_t Start
None None End = Start
3 Different
Offset Length Description
0 uint16_t Start
2 uint16_t End

LAYOUT

Control Word & 0b0000'0000'0000'0001U

LAYOUT = Control Word & 1. Both layout paths produce the same 18-byte on-disk frames (the difference is only whether coordinates are resolved through the camera translation at parse time vs. play time; LAYOUT=1 forces interpolation mode 0xFB). You loop reading 18-byte Animation Frames until the next word has bit 15 set (int16 < 0, i.e. 0xFFFF), which terminates the list and is consumed.

0 Default
Offset Length Description
0 uint16 Frame duration; if bit 15 is set (int16 < 0) the list ends
18 bytes Animation Frame (the duration is its first field)
1 Other

Same 18-byte frame layout as mode 0; coordinates are pre-resolved through the camera translation and interpolation mode is forced to 0xFB.

Time

Time is calculated from number of frames. You set a starting camera position + look-at and an ending one, then the frame's duration = how many frames to interpolate between them. The engine accumulates duration*16 as the running timing value. The frame list ends when the next word has bit 15 set (an int16 < 0, in practice 0xFFFF); that terminator word is consumed.

Animation Frame

Each frame is 18 bytes (verified against BS_Camera_ReadAnimation @0x503C70 and every vanilla monster camera section). The two interpolation-mode bytes were previously missing (the frame was documented as 16 bytes), and the coordinates are signed int16 in Z, X, Y order - not unsigned x,y,z.

Offset Length Description
0 uint16 Frame duration (ticks to interpolate over)
2 uint8 Position interpolation mode
3 uint8 Padding (skipped; always 0 in vanilla data)
4 int16 Camera position Z
6 int16 Camera position X
8 int16 Camera position Y
10 uint8 Look-at interpolation mode
11 uint8 Padding (skipped)
12 int16 Look-at Z
14 int16 Look-at X
16 int16 Look-at Y
Dependency

` CAMERA: -Camera Settings -Camera Animation Collection (even three collections in a0stg006.x and up to 7 in a0stg101.x!) | | -Camera animation Set (Always 8 camera animations, may be empty (0xFFFF); look for example to a0stg127.x collectionId==1; there are 8 animations, but 7 of them are 0xFFFF (pointers increase by 2)) | | - Camera animation`

Example

a0stg006.x:
0x5d8 -> 02 00 08 00 20 00

  1. Get EOF-> *(0x5d8 + 8) -> 32
  2. Jump to EOF -> 0x5d8 + 32 = 0x5F8 (This is now Camera Animation Collection)
  3. Get pointer to correct anim collection. In this case we will use AnimCollectionID == 0, so: *(0x5F8 + 0*2 + 2) -> 0x0c
  4. Jump to Anim collection data: 0x5F8 + 0x0c = 0x604 (This is now Camera Animation Set)
  5. Jump to Camera animation by cameraAnimSetID, let’s take for example cameraAnimSetID == 7, so: *(0x604 + 7*2) -> 0x25E. Now carefully, jump by multiplying it by 2!
    1. 0x604 + (0x25E * 2) = 0xAC0

Therefore: a0stg006.x Camera animation 7 in camera collection 0 is at 0xAC0

Geometry

Geometry contains groups, that contains Triangles and/or quads poligons. Models always stars at 01 00 01 00, and needs to be after camera data, either it’s not model itself, but some other data.

Group

Offset Length Description
0 4 bytes Always 01 00 01 00 / Header of object
4 2 bytes Uint16 / number of vertices.
6 Number of vertices * 6 Vertex data, short X; short Y; short Z;
6 + (Number of vertices *6) (AbsolutePosition MOD 4) + 4 Padding
varies (just after above) 2 bytes uint16 / number of triangles
varies (just after above) 2 bytes uint16 / number of quads
varies (just after above) 4 bytes padding
varies (just after above) number of triangles * 20 Triangle data. If NumOfTriangles = 0, then instead of any triangle data, there’s quad data.
varies (just after above) number of quads * 24 Triangle data. If NumOfQuads = 0, then instead of any quad data, there’s either next header 01 00 01 00, or end of group.
Triangle
Name Type Description
F1 (A) uint16 A of face indice
F2 (B) uint16 B of face indice
F3 (C) uint16 C of face indice
U1 Byte U of first texture coordinate
V1 Byte V of first texture coordinate
U2 Byte U of second texture coordinate
V2 Byte V of second texture coordinate
CLUT_ID UInt16 (BIT operated)* Index to CLUT_ID operated by BIT (see below)
U3 Byte U of third texture coordinate
V3 Byte V of third texture coordinate
Special (see below / After QUAD table) 8 Byte See below (after QUAD table)
Hide Byte/Bool Bool. Hides or shows texture
Red Byte Texture colourization (Red)
Green Byte Texture colourization (Green)
Blue Byte Texture colourization (Blue)
PSone GPU related Byte PSOne instruction
Quad
Name Type Description
F1 (A) uint16 A of face indice
F2 (B) uint16 B of face indice
F3 (C) uint16 C of face indice
F4 (D) uint16 D of face indice
U1 Byte U of first texture coordinate
V1 Byte V of first texture coordinate
CLUT_ID UInt16 (BIT operated)* Index to CLUT_ID operated by BIT (see below)
U2 Byte U of second texture coordinate
V2 Byte V of second texture coordinate
Special (see below) 8 Byte See below (after table)
Hide Byte/Bool Bool. Hides or shows texture
U3 Byte U of third texture coordinate
V3 Byte V of third texture coordinate
U4 Byte U of fourth texture coordinate
V4 Byte V of fourth texture coordinate
Red Byte Texture colourization (Red)
Green Byte Texture colourization (Green)
Blue Byte Texture colourization (Blue)
PSone GPU related Byte PSOne instruction

Special Byte: This byte, needs to be divided to two bytes. So, for example, a 0xE5 needs to be treated like two bytes: 0x0E 0x05

Singular of char Example Description
First char/ 4 BIT B2 –> 0B Unknown
Second char / 4 BIT B2 –> 02 Texture page number

CLUT ID

The most important bit’s are the first two on one byte, and last two on last byte. Example: 00000000 00111100 (00 3C) should be read like this: 00111100 00000000 (3C 00) And the CLUT ID is revealed by watching this four bits: 001111[00 00]000000

Same applies to reading CLUT colors (16bits) where they are: 1b-T, 5b-B, 5b-G, 5b-R (Or RGBT if reordered as shown above)

Complete list (without replacing bit order - as is in HEX editor/memory):

CLUT ID BIT HEX
#1 00000000 00111100 00 3C
#2 01000000 00111100 40 3C
#3 10000000 00111100 80 3C
#4 11000000 00111100 C0 3C
#5 00000000 00111101 00 3D
#6 01000000 00111101 40 3D
#7 10000000 00111101 80 3D
#8 11000000 00111101 C0 3D
#9 00000000 00111110 00 3E
#10 01000000 00111110 40 3E
#11 10000000 00111110 80 3E
#12 11000000 00111110 C0 3E
#13 00000000 00111111 00 3F
#14 01000000 00111111 40 3F
#15 10000000 00111111 80 3F
#16 11000000 00111111 C0 3F

Texture animation

Optional block pointed to by geometry header offset 20 (present when that value differs from the texture offset). It animates parts of the stage texture by copying rectangles inside VRAM every few frames — waterfalls, scrolling conveyors, blinking lights. The engine runs it each frame in BS_UpdateTextureAnimation; the pointer to the block is kept in dword_1D98A50. See Function addresses.

The block starts with an array of uint16 entry offsets (in words, relative to the block start; a 0 entry is an unused slot), each pointing to one animated region:

Offset Length Description
0 sint16 Destination X (VRAM)
2 sint16 Destination Y (VRAM)
4 sint16 Width
6 sint16 Height
8 uint8 Number of frames (0 = continuous 1-line scroll)
9 uint8 Speed
10 uint16 Frame counter [runtime]
12 sint16 * 2 * nbFrames Per-frame source origin (X, Y) in VRAM (Y is stored minus 256)

Each tick the counter advances by speed; on a frame change the engine blits the source rectangle (srcX, srcY + 256, width, height) over the destination rectangle, cycling through the frames. Only 17 retail stages use this block.

Texture

Contains one TIMs..

UV calculation algorithm

Float U = (float)U_Byte / (float)(TIM_Texture_Width * 2) + ((float)Texture_Page/(TIM_Texture_Width * 2));

Texture page calculation

Byte TPage = InputBytes[TexturePage_index] & 0F;

Bitwise TPage byte AND 0F, to delete first 4 bits

int TPageINT = TPage * 64;

For 16 bit TIM’s, the texture page is 64 pixels wide

Example

For 24-bit TIM:

0xB2 byte is: 2*48 = 96

Unsure about this one. It could be 42.667 instead of 48. It takes 1.5x the space of 16 bit. I haven’t seen a file that uses 24-bit yet.

For 16-bit TIM:

0xB2 byte is: 2*64= 128

For 8-bit TIM:

0xB2 byte is: 2*128 = 256

For 4-bit TIM:

0xB2 byte is: 2*256 = 512

Each time you half the number of bits you double the amount of data you can store in the same space. So the calculated Texture Page is different.

Face order / Translation/ triangulation

Quad wing order

` ABCD —> ABDC example: f 1 2 3 4 to: f 1 2 4 3`

Quad triangulation face order

` ABDC > ABD ACD (same for VT) example: f 1 2 4 3 to: f 1 2 4 f 1 3 4`

Triangles VT order

` A/T1 > A/T2 B/T2 > B/T3 C/T3 > C/T1`

Example

` (Also with quad triangulation face order!) f 1/1 2/2 7/4 6/3 to: f 1/1 2/2 7/4 f 1/1 6/3 7/4 where: A=1 B=2 C=6 D=7`

Function addresses

Function Address Description
computeAnimationSequence 0x50DB40 Shared animation-sequence VM (verified IDA function)
BS_UpdateCameraSequence 0x509610 Runs the camera byte-code with camera opcode handlers (verified IDA function)
CameraSeq_DispatchActionOpcode (formerly documented as a2ParamAnimSeqForCamera) 0x509810 Dispatches camera opcodes below 0xC0 (verified IDA function)
BS_GetCameraAnimationPointer 0x503520 Masks/bounds-checks the animation index and resolves its pointer (verified IDA function)
BS_UpdateTextureAnimation 0x50CB20 Per-frame stage texture animation (VRAM blit) (verified IDA function)