27
edits
Changes
From Final Fantasy Inside
m
Currently unknown=====Objects List====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204, takes up 204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint32| Number of objects|-| Num_of_obj*4| uint32| Relative pointer to ~1400 segment (one .obj file) [01 00 01 00]|} =====Settings 1====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | 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 from start. Unused | 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| . Can be ..up to end| Left nullfor storing some engine data AFTER loaded in-game|} This might look difficult, but it'ied and nothing happens 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
Other way is to treat first one as 6 BIT's, and second one as 2 BIT.
string StrByte = InputBytes[TexturePage_index].ToString("X2"); //Gets TPage byte as HEX text StrByte = "0" + StrByte.Substring(1); // Deletes the first char/ 6 bits Byte TPage = Byte.Parse(StrByte); // Parses result as new byte int TPageINT = TPage * 128; //For 8 16-bit TIM's, the texture page : 0xB2 byte is : 2*64= 128 sized
ExampleFor 8-bit TIM: 0xB2 byte is:2*128 = 256
=== For 4-bit TIM Texture width/height resolving ===: //Updated - 19.07.2015 by MaKiPL0xB2 byte is: 2*256 = 512
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 Each time you half the number of bits you double the amount of data you can store in Stage for TIM header int TIMoffsetCLUTetc = TIM + 18; // Logic pass UInt16 CLUTsize = BitConverterthe same space.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 = BitConverterSo the calculated Texture Page is different.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)
'''1.====Quad wing order:'''====
'''2.====Quad triangulation face order'''====
'''3.====Triangles VT order:'''====
====Example:====
→0 Default: fix size of animation frame
By MaKiPL. Thanks for help in research for: shakotay2 (XeNTaX), Halfer, Yagami Light.
Complete list of original battle stages by Kaspar01: [[FF8/BS_list|List of battle stages]]
== Info ==
.X file is uncompressed 3D stage model with texture embedeed. The file contains basic unused info (currently unknown, probably for editor?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. [[FF8/BattleStage/Pointers|Click me for battle stage pointer list]]
{| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"
! style="background:rgb(204,204,204)" | Description
|-
| Info/ScriptPlayStation MIPS assembly
| E8 FF BD 27 01 00 02
| Currently unknown. Probably header + info, that is not needed Used only in game itself. Can be null'ied and nothing will happen ingame. PS version; skipped in PC
|-
| Camera data + movement
| 02 00 08 00 20 00 (usually 0x5d4/8)
| Camera animations, movement (pre-keyed)
|-
| GeometryModel section| 01 06 00 00 01 00 (See geometry section info)
| Divides to object (group) and it's sub-objects + vert/triangle grouping
|-
| Texture
| 01 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. {| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | 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| Texture [unused in code]|-| 24| uint32| Texture|-| 28| uint32| Relative to EOF
|}
== Info/Script ==Objects Group===={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | 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|}
== Camera data ==
Starts at ~0x5d4 (see How the engine handles this file?).
[.text:00509820]
{| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"
! style="background:rgb(204,204,204)" | Offset
! style="background:rgb(204,204,204)" | Length
! style="background:rgb(204,204,204)" | 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====
{| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"
|-
| 0
| char (based on disassembly)| CameraAnimMode?|-| 1| byte?| StopEnemyBeforeAnim?|-| 2| Bitfield? (1B)| UHM?|-| 3| ???| DefaultEndofCameraPosition_Zoom?|-| 4| ???| ???|} ====Camera Animations Collection==== {| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | 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=====Seems to be 8 pointers in each set.{| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-|0|ushort|AnimPointer*2 |-|AnimPointer|Varies 144-146-148 bytes usually|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 ==={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint16_t (bit varies)| Main controller. If 0xFFFFU END|}==== FOV ==== Control Word & 0b0000'0000'1100'0000U===== 1 Default ====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| None| None| Start = 0x200|-| None| None| End = 0x200|}===== 2 Same ====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint16_t| Start|-| 2| uint16_t| Padding|-| None| None| End = Start|}===== 2 Different ====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint16_t| Start|-| 2| uint16_t| Padding|-| 4| uint16_t| End|} ==== ROLL ==== Control Word & 0b0000'0011'0000'0000U===== 0 Unknown ===== TODO===== 1 Default ====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| None| None| Start = 0x000|-| None| None| End = 0x000|}===== 2 Same ====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint16_t| Start|-| None| None| End = Start|}===== 2 Different ====={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint16_t| Start|-| 2| uint16_t| End|} ==== LAYOUT ==== Control Word & 0b0000'0000'0000'0001U===== 0 Default ===== You'll loop through the data till the first value is less than 0. pushing back to a variable length container.{| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| int16_t| if < 0 break; could be related to time of frame.|-| 2 | 16 bytes| Number Animation Frame|} ===== 1 Other ===== TODO===Time=== Time is calculated from number of frames. You basically set starting position World+lookat and ending position, then mark number of camerasframes to interpolate between them. Every frame is one draw call and it costs 16. Starting time needs to be equal or higher for next animation frame to be read; If next frame==0xFFFF then it's all done.=== Animation Frame ==={| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | Offset! style="background:rgb(204,204,204)" | Length! style="background:rgb(204,204,204)" | Description|-| 0| uint16_t| is frame durations shot
|-
| 2
| 2 bytesVertice<uint16_t> (x,y,z)| Number of camera animations?World|-| 8| uint16_t| is frame ending shot
|-
| ?? / Camera data10| VariesVertice<uint16_t> (x,y,z)| Currently unknownLook At
|}
=====Dependency===== CAMERA: -Camera Settings -Camera data contains:Animation Collection (even three collections in a0stg006.x and up to 7 in a0stg101.x!) | |# Prekeyed -Camera animation Set (Always 8 camera movementanimations, 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 zoomanimation =====Example===== a0stg006.x:<br>0x5d8 -> 02 00 08 00 20 00 # Camera FOV Get EOF-> *(horizontal and vertical0x5d8 + 8)-> 32# Orthogenical projectionJump to EOF -> 0x5d8 + 32 = 0x5F8 (This is now Camera Animation Collection)# RotationGet pointer to correct anim collection. In this case we will use AnimCollectionID == 0, so: *(0x5F8 + 0*2 + 2) -> 0x0c# Special effects like earthquakeJump to Anim collection data: 0x5F8 + 0x0c = 0x604 (This is now Camera Animation Set)# MoreJump to Camera animation by cameraAnimSetID, unknown?This file does not contain camera animations let's take for victoryexample cameraAnimSetID == 7, so: *(0x604 + 7*2) -> 0x25E. Now carefully, summons and etcjump by multiplying it by 2! ## 0x604 + (0x25E * 2) = 0xAC0 Therefore: a0stg006. x Camera animation 7 in camera collection 0 is at 0xAC0
== Geometry ==
|-
| 6 + (Number of vertices *6)
| (AbsolutePosition MOD 4) + 24
| Padding
|-
| V of second texture coordinate
|-
| OPCLUT_ID| ByteUInt16 (BIT operated)*| Opacity / Alpha channel blend|-| Unknown| Byte| PSOne GPU related?Index to CLUT_ID operated by BIT (see below)
|-
| U3
| V of first texture coordinate
|-
| OPCLUT_ID| ByteUInt16 (BIT operated)*| Opacity / Alpha channel blend|-| Unknown| Byte| PSOne GPU related?Index to CLUT_ID operated by BIT (see below)
|-
| U2
0x0E
0x05
{| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"
! style="background:rgb(204,204,204)" | Description
|-
| First char/ 6 4 BIT's
| B2 --> 0B
| Unknown
|-
| Second char / 2 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): {| border="1" cellspacing="1" cellpadding="3" align="center" style="border: 1px solid black; border-collapse: collapse;"! style="background:rgb(204,204,204)" | CLUT ID! style="background:rgb(204,204,204)" | BIT! style="background:rgb(204,204,204)" | 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 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.
=== Face order / Translation/ triangulation ===
ABCD ---> ABDC
example:
f 1 2 4 3
ABDC > ABD
ACD (same for VT)
f 1 2 4
f 1 3 4
A/T1 > A/T2
B/T2 > B/T3
C/T3 > C/T1
(Also with quad triangulation face order!)
f 1/1 2/2 7/4 6/3