Author
Topic: [FF8] Engine reverse engineering (Read 13263 times)

Off-topic stuff first out. I'm not sure if anyone has yet noted a little lack of updates on FF8 world map exporter/imported (named wmx2obj in tools sections). It's still under work mostly because I want it to be as user friendly as possible. This has also lead me to somewhat what this topic tries to achieve.

So this topic will work on engine side and I hope that with this topic we can fill FF8 Engine information on wiki too.

For starters every addresses that straightly or remotely operates engine functionality somehow will be good to share here, also if there are already full functions constructed from disassembly that would be great. When posting addresses, please post your game version. I'm not sure how much difference there are in addresses between different versions, but if anyone has information on that, it would be good to be addressed here.

I've been debugging a bit of FF8 engine side lately, especially engine module switching from field to world and so on, and there are promising results building up. Here's one address that I found really cool to share now.

Information: This seems to be some kind of boolean value for the first byte in given address. When set to 00, the game is sped up. 01 is the default value.

Edit: Found addresses from dynamic memory that the frame functions compare on each frame, I'll put more info from functions later on, but right now these memory variables are good enough to operate from field module.

Information: Value is the index to the module to be called. It is checked every frame and defaults to 00 00 in field. This value is only used in field module, other modules uses values from different memory addresses.

IMPORTANT: Before changing the module index, change the parameters. If parameters are not set correctly before module transfer, the game may crash, freeze or leave the game in a state where it can't yet be recovered without resetting the game.

Values: 01 00 = Call field module with room number in address 01CE4762 U16 (little-endian) (right after this value). There are a lot of parameters yet to be studied, for example where the characters are spawned. 02 00 = Not used. 03 00 = Call battle module with parameters right after this value. 01CE4762 U16 (little-endian) value seems to be encounter code, codes are here: http://wiki.qhimm.com/view/FF8/Encounter_Codes 04 00 = Resets game. Game is restarted. 05 00 = Call in-game menu module. No parameters seems to be needed, however this does not mean it doesn't take any parameters. 06 00 = Yet to be confirmed. Soft freeze. 07 00 = Call world module with parameters right after this value. 01CE476C U8 determines where you are spawned in world map. Value 0x32 spawns you in ragnarok where you left it and value 0x30 spawns you in balamb garden where you left it. More values to be examined later.

You found battle calling! Amazing work!I have addresses and functions for kernel32 level loading, character/enemy battle model loading, Sound load, Sound ID handling and stuff related, will share as soon as I get to my notes on PC. The problem is of course this version. I found working on Steam version the most comfortable because it's faster and somewhat more stable. There are some changes in .data section of the exe, bit majority of game code assembly is the same. Oh right! I also have the buffer location, I'll get all the values, addresses, pseudocodes and assembly ASAP.

You found battle calling! Amazing work!I have addresses and functions for kernel32 level loading, character/enemy battle model loading, Sound load, Sound ID handling and stuff related, will share as soon as I get to my notes on PC. The problem is off course this version. I found working on Steam version the most comfortable because it's faster and somewhat more stable. There are some changes in .data section of the exe, bit majority of game code assembly is the same. Oh right! I also have the buffer location, I'll get all the values, addresses, pseudocodes and assembly ASAP.

That's cool! Also a little more precision to the battle calling: Address 0046FED0 or FF8+6FEDO (which ever feels more comfortable to use) is the function for updating the whole frame or at least part of it. The value I documented above is compared to byte 03 which seems to trigger the battle with parameters.

EDIT2:Just checked with IDA. For Sound initialization, there's a function at .TEXT:00469990It takes five parameters, four unsigned and one signed. Probably IDs and etc. Needs more testing in game and maybe I'm close to force sound play whenever I want to.

BTW> Just saying, you can force game to load other file by changing the register to load modified ID of battle list array. Though it's problematic.

I wish we could someday create full IDA database containing named variables and function names, so instead of sub_ABCDEF you will see PlaySound or something...

I hope this too. I'm learning to write things out properly just like the link Paul posted from rcxrdx. Once I've learned to do that I'll write out some things out for everyone, of course also before that so the researchwouldn't be limited by my time entirely.

4th push parameter (so third parameter) [assembly takes parameters from the end to beginning] is Volume, also every other is predefined, so it's System sound play. Changing other parameters doesn't seem to do anything. Still looking for direct playing any desired sound at any time. I'm digging deeper, I'm closer. :3

Also, there's an extreme amount of lpOutputString, OutputDebugStringA and string write, but majority is disabled (thanks to rcxrdx for basic file I/O debug logging display). Some are pushing lpOutputString function, but are not calling it (?). Things like SFX error, or even displaying detailed AKAO debug info is hidden in the EXE (Sound play, ID: Volume: etc...). There is some shared variable for debug and I'm going to find it, as rcxrdx way is to reverse jump mnemonic to not jump if bool is negative.

Okay, I made it! IDA is almighty! I can play whatever sound I want by changing MOVSX instruction of parameter to sub_4B90F0, to MOV fixed number. This is array index, so:

sub_53EF40 ... - 0053EF46 call sub_45B2E0 - 0053EF48 test eax, eax // sets zero flag to 0 if game is not active |--- 0053EF4D jne 0053F14F // If you change this this OPCODE to "je" you can run the game while in background and in world map. If you want to run the game again when window is active, change this back |->...Call for SSIGPU window that shows VRAM buffer. The window doesn't seem to come back again if closed once, need to restart the game if you want to start it again .

sub_45B2E0 (call from sub_53EF40, line 0045B2E6 ) is a function that checks if the game window is active or not. The function itself calls system functions including USER32.GetActiveWindow. Return value seems to be -1 if not and 0 if active, returned to EAX register.

sub_45C320 (call from sub_53EF40, line 0053EFE0) is the function that creates the SSIGPU window.

I have a question. Many cool info is Outputed by 0x469625, It's OutputDebugString of lpOutputString. This string is outputed in fact, but not as debug string (?). The only working debug string is OutputDebugStringA.Furthermore:FF8.exe+69625 is calling "OutputDebugString". This event is "EXCEPTION_DEBUG_EVENT" and EAX register holds address of debug string. The debug string IS NOT displayed to debugger. The debug string that is in fact displayed is "OUTPUT_DEBUG_STRING_EVENT" and is calling OutputDebugStringA.

I made a big research of startup. Researched among others WindowCreate, Registry queries, buffer handling, errormessage handling...Around 1A77238 you can see static memory portion containing some core stuff like paths, errorcodes, even HWND.

MakiPL: This is really interesting for me. I want to increase all the game resolution by at least 4. By resolution i don't only mean the window but also the size of the objects, characters, worldmap,etc... My problem is i can replace characters by higher polycount ones, but the vertices are merged when read by the game BECAUSE the resolution is low. I found a way to prevent the vertices from merging by increasing the size of the models in the character file. BUT when the game reads the file, the characters are oversized.So i thought if i can increase the size of the screen AND the models, then there wouldn't be any oversized character or merged vertices.

*EDIT: Those addresses below are all wrong. I don't know what the heck have I done lol46C850 - Music Play (Unsigned int MusicID) >>46C2A0 - MusicPlay(unsigned int MusicID, signed int Volume, signed int 0, signed int 0) <-- this function doesn't exist. Where did I get it from? :oMAX VOLUME 7F (127!!)Debug done, rerouted switch to OutputDebugMessageA. Here's the sample output of first seconds:http://pastebin.com/5n3PUX84

[0] DISC1.pak[1] DISC2.pak[2] DISC3.pak[3] DISC4.pak[4] PUBLISH.pakFor forcing play in 720p (though doesn't seem to work on Vanilla. Chances are this should work with Aali's drivers and window initialized at 1280x720, below code for all)

What else....text:0047CCB0 - checks for Battle status. If EAX >= 5 then after battle transition you're jumping right back to world map, if 4 the chara is stuck after jumping back to world map, if 3 the battle is normal. .text:004A1C80 - Menu related [thanks to rcxcrdx source]. .text:00500720 - Performs majority of memory function calls for things after to-battle transition

So far so good. Happy Easter! :3

EDIT: Back to music- I don't know what they've done, but the MusicID is held in EBX, however the thing I missed is here:

.text:0046B578 xor ebx, ebx - the EBX is flushed INSIDE the function, not from outside!.text:0046B57A mov bl, [esi+4] - The real SongID is held by esi+4 value, not by EBX made earlier from some other function...

Okay, I have functions responsible for playing music if battle and if entered town, also probably logic for "KeepBattleMusic after winning battle" script interpretor. Loaded music is at: 0x01CE0934 (Memory) [for WORLD]and: 0x01CDC928 for Balamb Garden (don't know if it's the same for other fields)

UPDATE2: I just tested. The function was right, but the loading process is a bit stupid. To call any MusicPlay, you have to call:0046B500 with parameter: (char[5] AKAO) [char *AKAO].

If MIDI fails to play a Debug is displayed: midi_play FAILED!: returning 0The game doesn't crash compared to SFX play... -.-Many programmers, many different ways of solving problems. Nope. It crashed on song 99(dec)

Just unlocked debug info that rcxrdx found out way before (For I/O debugging). Though, I'm reversing it function by function so there's shitload more of this. In big amount of cases it uses things like this:This means, that there's NOT any shared variable that declares debugging. The only way is to change it to JNZ, because xor eax, eax is always zero. Also, as you can see it calls OutputDebugString_1 which ALSO is locked, and jumps out of function inside instead of using routine to ds:DisplayDebugInfoA. That's why this patch: .text:00403DB5 jz short loc_403DEBshould display majority of data and unlocks debug handed from functions like this.

That's all. :3

@UPDATE:Sorry for jumping ahead, but by using code injection and custom built debug script I made it to display currently loaded SmPcRead(some function for file system interpretation) file. Normally this lands somewhere in memory and is not used, however with the custom assembly the debug info is like:

There's an interesting sub at 0x00487DF0 (Steam version) which controls the AI code for monster turns (i.e. it parses section 8 of the .dat files).Most of the function is essentially a giant switch statement on the current AI byte and then it goes to the next one until it finds 0x00.The thing I like about this function is that if you replace it, you could make your own AI scripting system (e.g. using Lua), which would make for more interesting battle AI without faffing about with byte code.

//0x1D27B10 - note: actual start address is earlier and some of the later items in the struct probably need to be moved to the start#pragma pack(push, 1)struct Character{uint8_t **monster_info; //pointer to section 7 of a loaded dat file in memoryuint8_t unk[124];uint8_t unkbyte1D27B90; //unknown flag byteuint8_t unk1[79];};#pragma pack(pop)

//0x1D28E89 - note: actual start address is earlier and some of the later items in the struct probably need to be moved to the start#pragma pack(push, 1)struct unk1D28E89{uint8_t unk_byte;uint8_t unk[70];};#pragma pack(pop)

I'm currently working on reversing this function and the 60 cases of the switch statement (there's also nested switches for cases 0x02 and 0x04 =/) with some help from discoveries by random_npc (http://forums.qhimm.com/index.php?topic=11137.0) and the debug room.EDIT: Slowly getting there, the structures are starting to make sense and it's helping with the AI opcodes... e.g. opcode 0x3C looks it adds the next word to it's own HP and 0x16 sets the current HP to the max HP.

Thanks for the AI interpretor function! It's one of the biggest function I've seen here.

I though about damage limit breaking. I know someone here made it to break 9999 limit, but does anybody broke 65535 limit? I'm on it. Using a bit of code caves and assembler hacking I made it working to be able to hit with 32 bit int! There's A LOT of work. The damage dealt is in 32 bits, but the damage displayed is in 16 bit. So far I forced the engine to show full number, but there are a lot of problems:*It supports drawing only FIVE numbers. I'm currently on it to force it drawing more numbers. [probably hard]*I'm forcing 32 bit registers, so I have to find a safe place to store it, as I'm currently touching the status byte.(though it's working) [Easy, but needs some custom assembly to copy memory outside subroutine (they are all storing to registers or i.e. ebp+08)]

Proof with number exceeding 16 bits (damage dealt was 98765):I have no screen for 1234567 damage, but it only displays 34567 leaving "12' unrendered. :C

Update to the Monster structure:Note, the offsets listed are for the first struct in the array, they are usually referenced with address + (208*monster_id) - the unknown padding is mostly in sizes of 8 so it's easy to replace it when I find out what some values are.

I'd imagine that the struct above is referenced all over the battle code, so I'm trying to decipher as much of it as I can so that understanding what other battle functions do will be a lot easier.I've found that generally the ID of the monsters is 3, 4 and 5 (no idea what happens with 8 monster battles yet), so the first monster you fight will have its parameters at 0x1D27D80 rather than 0x1D27B10. It looks like it stores most of the parameters for the monsters that you're fighting, all I need now is to finish decoding it .There's a bunch of interesting things I didn't know about the AI code, you have to be careful with some opcodes because you can override bits of memory that you shouldn't - which could lead to some random crashes.opcode 0x0E is used to set a variable as mentioned by random_npc, there's a special case where the immediate is 0xCB which causes the variable to be assigned the value of last attacker instead of the immediate - it appears that 0xCB is treated as a special case in quite a few opcodes.After further investigation, it seems there are 2 sets of variables DC-E3 (which are stored in the struct above) and 60-67 (which are stored separately - I assume these are global for the battle).It looks like bad things would happen if you used opcode 0x0E to write outside the range DC-E3 but there's no constraint checking!

The 71 byte structure I mentioned before contains a series of multipliers for stats (str, mag etc.) which default to 10 (1x multiplier) among some other things that I haven't identified.

Notes on opcode 0x04 - Targeting:the final form of this in the function is a bit mask to represent which enemies are targeted, looking at the code I can see the following:0xC8: 0000 0000 0000 1000 //self (it just shifts 1 left by the monster id - this represents and id of 3)0xC9: //random enemy - probably one of the first 3 bits is set0xCA: //??0xCB: //last attacker - does 1 << the value in it's struct (see struct above)0xCC: 1000 0000 0000 0111 //all enemies0xCD: 1000 0000 1111 1000 //all allies0xCE: 1000 0000 1111 1111 //everyone?0xCF: //random ally0xD0: 1010 0000 0000 0111 //something to do with enemies0xD1: //?? does 1 << the value in 0x1D28DFB0xDC-0xE3: //does 1 << the value in the corresponding variable

Okay. This one is cool! :3I documented wm2field.tbl file. http://wiki.qhimm.com/view/FF8/Main_wm2There are 72 warp points on the world map (Sic!). It's not possible, so I tested it a bit and copied Balamb warp to 72 entry in wm2field.tblI was warped to world map screen. It looks like forgotten debug field (it's not accesible from debug room btw). See here:BTW> What's wrong with Quistis hair?

EDIT: You are warped to unavailable places on field. For Example you can run over rescue pod...

Battle stages milestone:I really couldn't believe it at first. Many weeks of thinking over it and I made a theory, that paths to every battle stage are hardcoded in EXE. NOW I CAN PROVE IT!

1. 0050E3C0 is a giant switch with... 163 cases. Guess how many battle stages we have? 163.2. Every switch points to OWN INDEPENDENT function that... guess what? Adds to location of battle stage needed value to point to camera data. This means, that there are >>163<< functions, that every just adds different number. So, we have 163 functions less to reverse. :33. Every independent level function has those +0x5D4 or 0x5D8 ADD and also logic AND to convert variable to uint8.

Having all the BattleStage parsing functions I'll be able to move on with the re-parser software to convert 3D models into FF8 Battle stages (I was stuck because of those hardcoded pointers).I still don't know if it's possible to put more detailed battle stages without touching the engine. I found also TIM texture pointer, but haven't inspected if it also have hardcoded pointers yet.

OMG... It also have additional pointers...EDIT3: WHAT THE ACTUAL F... It has MAJORITY of predefined data for EVERY stage... Even some sort of freaking hidden TIM files for battle stage 03 and 06: b00300.tim ...

There's 95% probability, that the data before camera is indeed unused. Dear Square, what's the purpouse of using pointers inside camera data, if you just jump to geometry section via hardcoded numbers? :O

What is "BDlinkTask"? The only result for this is my ASCII rip on Pastebin. xD

EDIT4: If they did it all in own functions, then my theory that every GF has it's own function is true, just needs to be proven.

EDIT5: Renzokuken magic ID is 336; Witch spawn magic ID is 208

EDIT6: Stage 137 (Witch stage at time compression) has a check for 01D98B64 (I called this BS_EffectState). If it's equal 8, then anything goes normal. If not, then the code for stage deformation is called. (The function double checks if the state is not normal and to be sure if it's exact 2. Then deformation applies. If you change it to 8, then deformation is stopped. You can toogle it. Spawning and changing levels is called by magic (as stated by some user here, forgot who... ;c)

so there are a couple of interesting things...There appear to be 40 bytes used for status resistance stuff but only 19 are actually loaded from dat files, the rest are just set to 100 during loading... so the rest are a mystery - could be The End, Vit0, unused...The first status byte seems to contain timed statuses, so I wonder where the timers for these are.Also I believe opcode 0x0F is used to set global vars (60-67) and 0x0E to set local ones (DC-E3) you can write outside those variable ranges but:a) you'll probably be writing into areas of memory that you probably shouldn't (overriding stats etc.)b) the test opcode (0x02) will not test values outside of those rangesopcode 0x12 is for adding to local vars.opcode 0x13 is for adding to global vars.opcode 0x15 also do addition of some sort but I haven't discovered what to yet.opcode 0x24 fills the ATB gauge.opcode 0x2D is for changing elemental resistances (e.g. 2D 00 F4 01 sets fire resistance to 500).opcode 0x36 disables Odin and enables Gilgamesh

Expanding on what random_npc said about 02 02 being probability:The next byte is used as a modulus and the comparison is done as normal e.g.02 02 03 01 02 00 XX XXis basically:if (rand() % 3 < 2) { //should be 2/3 probability do stuff;}

EDIT: realized that I screwed the padding up at some point and all the addresses were out of alignment, should be fixed now.EDIT1: this: http://wiki.qhimm.com/view/FF8/GameSaveFormat is located at 0x1CFDC58 in memory, I noticed some checks for stuff in there in the battle code.

BYTE mentalRes[40]; //0x1D27BA0offset 0x20 of that array seems to be checked when using meltdown (i.e. vit0).So the idea behind this post is to implement a way to make some monsters vit0 resistant without getting rid of vit0 entirely.The following code changes in the exe load the unused 20th status byte in the dat file into that position instead of it being set to 100:

!!!--- make a backup if you're going to do this ---!!!Also make sure you check the from bytes, since I'm not sure if this will work with other versions of FF8 (I'm using the English Steam version).

it's basically a bunch of ASM code that bounces around the place.The problem is that the jump table doesn't go anywhere near the value needed and there's no space for code to change one status without having a knock-on effect on the others or overriding code, so I had to resort to doing crazy amounts of jumps and using the NOP space between functions.Anyway, I tested it and it appeared to stop things getting vit0 status - if they had the 20th byte as 0xFF anyway.It'd probably be a lot easier to do something like this in a mod with an injected DLL - since it wouldn't involve so much code jumping and it wouldn't be permanent.You could also test it by adding 0x400000 to those addresses and editing the memory of the program.

For those interested, this is the ASM code - note that register EAX is unused until its next modification (otherwise modifying AL like I have without backing it up would have unintended consequences):

0x48BEE8: JMP 0048BF95 ; jump to unused code section...0x48BF95: MOV AL, BYTE PTR DS:[EDI+17B] ; load 20th byte into AL (EDI points to the start of section 7 of a DAT file)0x48BF9B: JMP SHORT 0048C014 ; jump to unused code section...0x48C014: MOV BYTE PTR DS:[ESI+1D27BC0],AL ; move 20th byte into the monster data in memory at the vit0 resistance point (ESI is 208 * monster_id)0x48C01A: JMP 0048C131 ; jump to unused code section...0x48C131: MOV ECX,0A0A0A0A ; do overwritten instruction0x48C136: JMP 0048BEED ; jump back to where we startedEDIT: was accidentally using the wrong byte, should be fixed now... it looks like that byte isn't usually loaded into memory at all - I screwed my numbering up when converting from hex to dec and fixed it in my previous post.Basically it ended up using the byte before instead (info + 0x17A instead of info + 0x17B).