FF8/FileFormat X
By MaKiPL. Thanks for help in research for: shakotay2 (XeNTaX), Halfer, Yagami Light. Complete list of original battle stages by Kaspar01: List of battle stages
Contents
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 |
---|---|---|
Unused data | E8 FF BD 27 01 00 02 | This data is unused by FF8 Engine |
Camera data + movement | 02 00 08 00 20 00 | Camera animations, movement (pre-keyed) |
Geometry | 01 00 01 00 (See geometry section info) | Divides to object (group) and it's sub-objects + vert/triangle grouping |
Texture | 01 00 00 00 09 (Always) | Contains one .TIM texture |
How is this file handled by the engine?
FF8 loads specific file and reads it from hardcoded (written in FF8 code) position. This hardcoded position points to camera data. 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 (NULL?) |
8 | uint32 | Objects group #2 |
12 | uint32 | Objects group #3 |
16 | uint32 | Objects group #4 |
20 | uint32 | Texture |
24 | uint32 | Texture (Double texture available?) |
28 | uint32 | Relative to EOF |
Objects group:
Offset | Length | Description |
---|---|---|
0 | uint32 | Number of sections |
4 | uint32 | UNKNOWN |
8 | uint32 | ObjectsList |
12 | uint32 | Object group settings? |
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] |
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
==
Info/Script
Currently unknown, takes up to ~1400 bytes from start. Unused in-game. Can be null'ied and nothing happen.
This header accesses opcodes related to Direct3D "shadow" stuff. Is highly connected to AF3DN.p OpCode that accesses this portion of data is "unsigned divide by 2".
Camera data
Starts at 0x5d4, operates on 4 bytes translation of X Y and Z axis. Due to orthogenical view also operates camera in screen view making it possible to move camera from 2D generated screen. TODO: One thing is missing- how does the camera start and structure of camera key-movement. Global parameters of camera translations are known.
Offset | Length | Description |
---|---|---|
0 | uInt16 | ??/ Fixed 2 (02 00) |
2 | uInt16 | ??/ Fixed 8 (08 00) |
4 | uInt16 | UNKNOWN |
6 | uInt16 | Camera data size (starting from 0) |
8 | uInt16 | Camera Data |
10 | uInt16 | UNKNOWN |
12 | uInt16 | UNKNOWN- Has something with looping/timer? |
14 | (Camera_data_size)-12 | Camera Data (?) |
Camera data:
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) + 2 | 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 Other way is to treat first one as 6 BIT's, and second one as 2 BIT.
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
Contains one TIMs with various size 512x256, 673x256, 768x256 (8BPP).
UV calculation algorithm
Float U = (float)U_Byte / (float)(TIM_Texture_Width * 2) + ((float)Texture_Page/(TIM_Texture_Width * 2));
Texture page calculation
string StrByte = InputBytes[TexturePage_index].ToString("X2"); //Gets TPage byte as HEX text StrByte = "0" + StrByte.Substring(1); // Deletes the first char/ 4 bits Byte TPage = Byte.Parse(StrByte); // Parses result as new byte int TPageINT = TPage * 128; //For 8 bit TIM's, the texture page is 128 sized
Example: 0xB2 byte is: 2*128 = 256
TIM Texture width/height resolving
//Updated - 19.07.2015 by MaKiPL Byte[] Stage = File.ReadAllBytes(@"PATH TO .X stage file"); //Load file to memory Byte[] TIMtexture = { 0x10, 0x00, 0x00, 0x00, 0x09 }; //Initialize 8BPP header int TIMoffset = ByteSearch(Stage, TIMtexture, 0); //Search in Stage for TIM header int TIMoffsetCLUTetc = TIM + 18; // Logic pass UInt16 CLUTsize = BitConverter.ToUInt16(Stage, TIMoffsetCLUTetc); //GET CLUT size to UInt16 TIMoffsetCLUTetc += 2 + (CLUTsize * 512) + 8; // Logic pass - pass CLUT offset, to get to sensitive data UInt16 szerU = BitConverter.ToUInt16(Stage, TIMoffsetCLUTetc); //Get raw UInt16 value of width from texture data UInt16 wysoU = BitConverter.ToUInt16(Stage, TIMoffsetCLUTetc + 2); //Get raw UInt16 height value from texture data (real) int width = szerU * 2; //Calculate real texture width (For 8BPP = width*2)
Face order / Translation/ triangulation
1.Quad wing order:
ABCD ---> ABDC example: f 1 2 3 4 to: f 1 2 4 3
2.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
3.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