[FF8] Engine reverse engineering

  • Thread starter Thread starter Halfer
  • Start date Start date
Status
Not open for further replies.
H

Halfer

Guest
Hello!

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.

Code: [Select]
Code:
Game version: FF8 PC - 2000Address: 00B6D970    Functionality: Frame limiter?    Data type: Boolean?    Size: 4 Bytes?...See information...    Default value: 01 00 00 00    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.

Code: [Select]
Code:
Game version: FF8 PC - 2000Address: 01CE4760    Functionality/Description: Module index to be transported    Data type: Short?    Size: 1 Byte, most likely 2 bytes but second one never used.    Default value: 00 00    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.
 
Last edited:
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 might be better off using github pages or something instead. Has more downtime resistance and is distributed.

Quite a few useful bits of info here: https://github.com/rcxrdx/FF8Modding/blob/master/src/Hooks.cpp
You are right! Will take a look on this later on.

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.
 
Okay. I have my notes:
FF8 2000 PC - Game memory start as:
Code: [Select]
Code:
FF8.exe = 0x400000
General Kernel.dll LoadFile:
Code: [Select]
Code:
FF8.exe + 0x15d323 [0x55d323] //Used for opening data files (battle.fs, field.fs etc.)FF8.exe + 0x15d27b [0x55d27b] //As above
You can test function return addresses to track the process of given file initialization.


Real Sound ID for sound.dat ("The second uint32 in FMT file"):
Code: [Select]
Code:
Sound ID: FF8.exe+69D9D - lea ebp,[edi+edx] //Check EAX or ECX  or ESI register for sound ID (I don't remember, but normally EAX is used for math, so probably ECX or ESI)Audio.dat register: FF8.EXE+69DDB [00469DDB] //Check ECX register for Sound ID

Code: [Select]
Code:
FF8.exe+69E26 - E8 B5450000           - call FF8.exe+6E3E0 - // play sound call [already loaded] ?


Unknown notes [Those I'm unsure if are correct] I have saved:
*STEAM!
Code: [Select]
Code:
FF8_EN.exe+10DB40(50DB40) - mov ecx,[esp+04] = GENERIC LEVEL loader. HOLDS ALREADY CAMERA POINTER! [The relative address where the battle stage file starts, as the BS file has no pointers in header]00482610  - z array Battle load file //Whatever I meant writing this?

How to know which battle file FF8 wants to load? Engine uses "Battle stage file list" which is array of 1117 elements. Full content is available here:
http://wiki.qhimm.com/view/FF8/Engine_const/BattleFiles


I though I had the buffer location, but it turns out I did not save it.

EDIT: GameShark codes to real memory location in ePSXe 1.9.2 calculation guide: http://wiki.qhimm.com/view/User:MaKiPL#GameShark_codes_to_PC_version_-_memory_calculation


EDIT2:
Just checked with IDA. For Sound initialization, there's a function at .TEXT:00469990
It 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.

Here is the function to play sound in C (cleared and one named function):
http://pastebin.com/m1AzrVpb

46FED0 takes one parameter only.


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...
 
Last edited:
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 research
wouldn't be limited by my time entirely.
 
I'm quite confused on what's there. I found IDA database more comfortable to play with than what rcxrdx made there.


Quick note before I forget:
?? >> sub_46B270 (Preconfigured system sound settings) >> sub_469990 (Play Sound) >> ??

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:

Code: [Select]
Code:
signed int __cdecl PlaySystemSound(int Sound_ID) //sub_4B90F0{  return PlaySystemSound(SoundID[Sound_ID]);}
- Just pass any uint number that is not higher than array in .data

FF8.exe.Data.SoundID[]:
Code: [Select]
Code:
db 0                   .data:00B87D28.data:00B87D29                 db    1 ; Cursor change menu.data:00B87D2A                 db    1.data:00B87D2B                 db    9.data:00B87D2C                 db  0Fh.data:00B87D2D                 db  10h.data:00B87D2E                 db    1.data:00B87D2F                 db    1  ; Back sound in menu.data:00B87D30                 db  12h.data:00B87D31                 db  29h ; ).data:00B87D32                 db  1Bh.data:00B87D33                 db  1Ch.data:00B87D34                 db  1Dh.data:00B87D35                 db  26h ; &.data:00B87D36                 db  27h ; '.data:00B87D37                 db  28h ; (.data:00B87D38                 db  24h ; $.data:00B87D39                 db  20h.data:00B87D3A                 db  23h ; #.data:00B87D3B                 db  25h ; %.data:00B87D3C                 db  41h ; A.data:00B87D3D                 db  5Fh ; _.data:00B87D3E                 db  5Bh ; [.data:00B87D3F                 db  5Ch ; \.data:00B87D40                 db  5Dh ; ].data:00B87D41                 db  6Ch ; l.data:00B87D42                 db  6Dh ; m.data:00B87D43                 db  6Eh ; n.data:00B87D44                 db  6Fh ; o.data:00B87D45                 db  71h ; q.data:00B87D46                 db    0.data:00B87D47                 db    0.data:00B87D48                 db    0.data:00B87D49                 db    0.data:00B87D4A                 db    0.data:00B87D4B                 db    0.data:00B87D4C                 db    0.data:00B87D4D                 db    0.data:00B87D4E                 db    0.data:00B87D4F                 db    0.data:00B87D50                 db    0.data:00B87D51                 db    0.data:00B87D52                 db    0.data:00B87D53                 db    0.data:00B87D54                 db    0.data:00B87D55                 db    0.data:00B87D56                 db    0.data:00B87D57                 db    0
PlaySystemSound:
Code: [Select]
Code:
signed int __cdecl PlaySystemSound(unsigned int Sound_ID){  return PlaySound(0, Sound_ID, 0x7Fu, 0x40u, 100); //Unknown, Sound_ID, Volume, Unknown, Unknown}
PlaySound pseudo is in my post above.


How to call any sound you want?

1. Inject:
Code: [Select]
Code:
mov ecx, [SoundID]; change SoundIDpush ecx ; make sure ecx is not used by other function when injectingcall 0046B270pop ecxret
2. Change EIP to your injected code
3. Return;
 
Last edited:
Reserving space for writing out a little tutorial how to open SSIGPU - VRAM display while in world map.

These addresses apply for FF8 PC - 2000 version.

By my suspections, sub_53EF40 function is responsible for things that happens during one frame ON world map. It takes one parameter.

Active window detection during frame?
Code: [Select]
Code:
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 :cry:.
Code: [Select]
Code:
 sub_53EF40       ...     - 0053EFD8    cmp    [02036AE4], edi     // Changing the value at memory address 02036AE4 to anything else than EDI (00000000) will trigger the if statement |---  0053EFDE    jne    0053EFEA                // If not equal |   - 0053EFE0    call   sub_45C320            // Creates the SSIGPU window |   - 0053EFE5    call   sub_45C590            // Updates the window? |->  ...

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.
 
Last edited:
MaKiPL what is confusing?

Surely this is easier:

signed int(*PlaySystemSound)(unsigned int Sound_ID) = ((signed int(*)(unsigned int))0x0046B270);

PlaySystemSound(whatever_sound_you_want);

Than:

1. Inject:
Code: [Select]
mov ecx, [SoundID]; change SoundID
push ecx ; make sure ecx is not used by other function when injecting
call 0046B270
pop ecx
ret
2. Change EIP to your injected code
3. Return;

?
 
Hmm... Still unsure, just give me some time, Paul. :3

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.


EDIT: Okay. I made it. Just change:
ff8.exe+69625 to:
Code: [Select]
Code:
call dword ptr [00B6908C]
- call dword ptr [FF8.exe+76908C]

Works now. The engine now display debug strings like:
Code: [Select]
Code:
MIDI stopmidi_play...Stopping PerformancePlaying Segmentmidi_play successful
*There are much more hidden debug data. ;)

Okay Paul, thanks! Just pulled the Hooks and made a custom function to play every sound in-game by loop. Works! I'm so happy. :3
Yep, that's a lot easier than assembly stuff. Now I get it.
 
Last edited:
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.

For example HWND as far as I can read from disassembly is at [.data:01A79D88]+92+ff8.exe
or for example:
dword_1A77654 = window width;
   In memory: 01A78BCC (read two bytes)
dword_1A77650 = window height;
   In memory: As above+2
or paths are at:
1A77658 + (1 or 4 or 5 or 6 or 7 * 260) in memory
   Example of memory address:
01A7775C = "\ff8\data\eng\                                                                      " [260]
In code: 260 * 1 + 0x1A77658     260d+1A77658h == 01A7775C

sub_401000:
Code: [Select]
Code:
// Again calculation of static memory buffer for holding variables in memory. int __cdecl sub_401000(int a1){  return 4 * a1 + 0x1A77238; }
sub_004011C0:
Code: [Select]
Code:
// This functions copies input *String to 1A77658 + (BlockID * 260). It's mostly paths like "FF8\ENG\Battle".unsigned int __cdecl CopyPathString(int BlockID, const char *String){  unsigned int StringLength; // eax@1  StringLength = strlen(String) + 1;            // Length of bytes to cpy                                                //   qmemcpy((void *)(260 * BlockID + 0x1A77658), String, StringLength);// Block size in memory of hardcoded paths are 260 bytes.   return StringLength;}
sub_00401ED0:
Code: [Select]
Code:
// Creates FF8 window and copies hardcoded paths to core FF8 data directories. Keep note of fixed variables. (Is it windowed mode only?)char InitializeFF8Paths_and_Window(){  int v0; // eax@1  int v1; // esi@1  int v2; // edx@2  sub_40A94D(0);  sub_406F5A(0);  v0 = sub_409E88();  v1 = v0;  if ( v0 )                                     // is not null?  {                                             // Copies hardcoded paths to static memory location    sub_4010E0(RootPath);    CopyPathString(1, DataPath);    CopyPathString(4, MenuPath);    CopyPathString(5, BattlePath);    CopyPathString(6, FieldPath);    CopyPathString(7, WorldPath);    *(_DWORD *)(v1 + 2804) = FinalFantasyVIII;    sub_4016A0();    *(_DWORD *)(v1 + 2728) = dword_1A78BC8 == 0;    v2 = (dword_1A78BB8 & 0xF0000000) == 0;    *(_DWORD *)(v1 + 2716) = 16;                // Colors?     *(_DWORD *)(v1 + 2740) = 0;    *(_DWORD *)(v1 + 2780) = 0x42340000;        // DataType: SINGLE! (No change...)    *(_DWORD *)(v1 + 2784) = 0x43480000;        // DataType: SINGLE! (No change...)    *(_DWORD *)(v1 + 2788) = 0x477A0000;        // DataType: SINGLE! (No change...)    *(_DWORD *)(v1 + 2708) = 640;               // Width    *(_DWORD *)(v1 + 2712) = 480;               // Height     *(_DWORD *)(v1 + 2760) = 0;    *(_DWORD *)(v1 + 2732) = 0;    *(_DWORD *)(v1 + 2724) = 1;                 // crash if not ==1 ?     *(_DWORD *)(v1 + 2696) = 0;    *(_DWORD *)(v1 + 2700) = 1;    *(_DWORD *)(v1 + 2756) = 1;    *(_DWORD *)(v1 + 2984) = v2;    *(_DWORD *)(v1 + 2328) = 0;    *(_DWORD *)(v1 + 2324) = 0;    *(_DWORD *)(v1 + 2928) = 0;    *(_DWORD *)(v1 + 3052) = 0;    *(_DWORD *)(v1 + 3056) = 0;    *(_DWORD *)(v1 + 3060) = 0;    dword_1A7764C = 0;    dword_1A77648 = 0;    _width = 640;                               // Copies from above v1+2708 [holds in ECX]    _height = 480;    *(_DWORD *)(v1 + 2864) = sub_401890;    *(_DWORD *)(v1 + 2868) = sub_401A00;    *(_DWORD *)(v1 + 2872) = sub_470300;    *(_DWORD *)(v1 + 2876) = sub_470330;    *(_DWORD *)(v1 + 2880) = sub_4703A0;    *(_DWORD *)(v1 + 2884) = 0;    *(_DWORD *)(v1 + 2888) = 0;    Set_Pointer_MemoryPaths(v1);                // Sets shared variable to point to section with hardcoded paths    if ( dword_1A78BB8 & 0x10 )      sub_45B5C0();    else      sub_45B5A0();    LOBYTE(v0) = dword_1A78BB8 & 4;    byte_1F9DC3C = dword_1A78BB8 & 2;    byte_1F9DC3D = dword_1A78BB8 & 8;    byte_1F9DC3E = dword_1A78BB8 & 4;  }  return v0;}
and many more subs for registry checking/creating like:
sub_00402870:
Code: [Select]
Code:
signed int CheckRegistry_Graphics(){  signed int v0; // esi@3  signed int result; // eax@3  HKEY phkResult; // [sp+4h] [bp-10h]@1  DWORD cbData; // [sp+8h] [bp-Ch]@2  BYTE Data[4]; // [sp+Ch] [bp-8h]@2  DWORD Type; // [sp+10h] [bp-4h]@2  if ( RegOpenKeyExA(         HKEY_LOCAL_MACHINE,         "Software\\Square Soft, Inc\\Final Fantasy VIII\\1.00",         0,         0x20019u,                              // KEY_READ - reads value                                                //          &phkResult) )  {    OutputDebugStringA(aCannotFindGr_0);    result = -1;  }  else  {    cbData = 4;    if ( RegQueryValueExA(phkResult, "InstallOptions", 0, &Type, Data, &cbData) )    {      OutputDebugStringA(OutputString);      RegCloseKey(phkResult);      result = -1;    }    else    {      v0 = *(_DWORD *)Data;      RegCloseKey(phkResult);      result = v0;    }  }  return result;}
Finding the shared variable of let's call it coreBuffer makes a lot, I mean A LOT things easier, as extreme amount of functions are working with this value.

Code: [Select]
Code:
int sub_40A04A(){  return Pointer_MemoryStaticPaths;}
and is set by:
Code: [Select]
Code:
.data:01A79D88 Pointer_MemoryStaticPaths dd ?          ; DATA XREF: Set_Pointer_MemoryPaths+6w
The Set_Pointer_MemoryPaths is called by sub_00401ED0 (see higher for pseudo)

BTW> Putting FF8 into higher resolutions via assembly hotfixing still renders game at 640x480(?) but only creates bigger window.
snap.png
 
Last edited:
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.
 
You betcha I be aiming to upscale it to full HD, Shunsq. :) It's just a matter of time. (I think :o)


So far I tested Steam and retail version, and it looks like it's only 10 bytes (sic!) difference between functions, example:


sub_46B270 [ PlaySystemSound(unsigned int SoundID) ]: //fixed Volume and channel ID
FF8.EXE = 0x0046B270; (2000)
FF8_EN.EXE = 0x0046B280; (Steam 2013)

I think there is some bigger difference later in code, but what's the most important about engine is at 0x00400000-0x004F0000.

EDIT:
FF8 is capable of playing 16 sounds at once.
Music: (I'm still searching for fix, as those two doesn't really play any music when I call it externally)
Code: [Select]
Code:
*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? :o MAX VOLUME 7F (127!!)
Debug done, rerouted switch to OutputDebugMessageA. Here's the sample output of first seconds:
http://pastebin.com/5n3PUX84

To reroute debug display, just set:
Code: [Select]
Code:
.text:00403DA9 jz      short loc_403DEB.text:00403DB5 jz      short loc_403DEB
NOTE: It's only OutputDebugString_1 rerouted to ds:OutputDebugStringA lpString, there's more! :3
Also, a lot unused MessageBoxes
It's cool, because when music changes you see:
sd_music_play (number=0, song_id=42, volume=127) ;)
 
Last edited:
I don't really know wtf is going on in this thread... but wow.  It looks like there's some amazing progress being made!!!  Way to go, people!  8-)
 
0x00559F70: PlayMovie(unsigned __int8 PAKfile, unsigned __int8 MovieID) - Plays Movie. PAKfiles are:Code: [Select]
Code:
[0] DISC1.pak[1] DISC2.pak[2] DISC3.pak[3] DISC4.pak[4] PUBLISH.pak
For 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)Code: [Select]
Code:
FF8.exe+15A085 - mov [esp+48],00000500 FF8.exe+15A08D - mov [esp+44],000002D0FF8.exe+15A073 - mov [esp+48],00000500 FF8.exe+15A07B - mov [esp+44],000002D0 FF8.exe+1F52 - mov ecx,00000500 - Window for 720pFF8.exe+1F6C - mov eax,000002D0 - Window for 720p
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:
Code: [Select]
Code:
.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]. Code: [Select]
Code:
[0]'A'; [1]'K'; [2]'A'; [3]'O'; [4]Byte SongID;
Sample loop:
Code: [Select]
Code:
 _declspec(dllexport) void PlayMusic() {  signed int(*MusicPlaying)(char *AKAO) = ((signed int(*)(char *AKAO))0x0046B500);  char AK[] = "AKAO ";  AK[4] = 0x3C; //Set number  for (int i = 0x20; i != 0x64; i++) //Sample range  {   AK[4] = i;   MusicPlaying(AK);   Sleep(10000);  } }
If MIDI fails to play a Debug is displayed:  midi_play FAILED!:  returning 0
The game doesn't crash compared to SFX play... -.-
Many programmers, many different ways of solving problems.
Nope. It crashed on song 99(dec)
 
Last edited:
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:
snap.png

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_403DEB
should 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:

Code: [Select]
Code:
Debugged application message: f:\DISK1Debugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\world\dat\wmsetus.obj                     //Displays even world.fs! Debugged application message: smPcRead::%sDebugged application message: SdMusicPlay ()Debugged application message: sd_music_play (number=0, song_id=41, volume=0)Debugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successfulDebugged application message: f:\DISK1Debugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\maplistDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.mim   //HOW COOL IS THAT? :3Debugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pmpDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pvpDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.idDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.mapDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.caDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.infDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.ratDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.mrtDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.msdDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.sfxDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pmdDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.jsmDebugged application message: smPcRead::%sDebugged application message: SdMusicPlay ()Debugged application message: sd_music_play (number=0, song_id=42, volume=127)Debugged application message: MIDI stopDebugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successfulDebugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pcb       // SEE?? Shows even field thingsDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\world\dat\wmsetus.objDebugged application message: smPcRead::%sDebugged application message: MIDI stopDebugged application message: SdMusicPlay ()Debugged application message: sd_music_play (number=0, song_id=41, volume=0)Debugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successfulDebugged application message: SdMusicPlay ()Debugged application message: sd_music_play (number=0, song_id=5, volume=127)Debugged application message: MIDI stopDebugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successfulDebugged application message: \ff8\data\eng\battle\A0STG101.XDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\B0WAVE.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\C0M028.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\D2C006.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\D2W016.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\D0C000.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\D0W006.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\D3C007.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\D3W020.DATDebugged application message: smPcRead::%sDebugged application message: \ff8\data\eng\battle\R0WIN.DATDebugged application message: smPcRead::%sDebugged application message: MIDI stopDebugged application message: SdMusicPlay ()Debugged application message: sd_music_play (number=0, song_id=1, volume=0)Debugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successful73F63400: thread has started (tid=8888)Debugged application message: \ff8\data\eng\world\dat\wmsetus.objDebugged application message: smPcRead::%sDebugged application message: MIDI stopDebugged application message: sd_music_play (number=0, song_id=41, volume=127)Debugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successful
@Update:
Okay, found a spot where I can change the code. Look:
FF8.exe+12D2DE - jne FF8.exe+12D2F5 - Fakes the error to display filename
FF8.exe+12D2FA - push FF8.exe+79152C - Changes debug template to not show error [ fake ]

That's the result:
Code: [Select]
Code:
Debugged application message: smPcRead::\ff8\data\eng\harata.cnfDebugged application message: smPcRead::\ff8\data\eng\kernel.binDebugged application message: smPcRead::\ff8\data\eng\sysfnt.tdwDebugged application message: smPcRead::\ff8\data\eng\icon.timDebugged application message: smPcRead::\ff8\data\eng\namedic.binDebugged application message: smPcRead::\ff8\data\eng\wm2field.tblDebugged application message: smPcRead::\ff8\data\eng\menu\mngrphd.bin5F2A0000: loaded C:\WINDOWS\SysWOW64\msadp32.acmDebugged application message: smPcRead::\ff8\data\eng\credits.timDebugged application message: midi_play...Debugged application message: Stopping PerformanceDebugged application message: Playing SegmentDebugged application message: midi_play successfulDebugged application message: MIDI stopDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\maplistDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.mimDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pmpDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pvpDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.idDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.mapDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.caDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.infDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.ratDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.mrtDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.msdDebugged application message: Can't open file: \ff8\data\eng\field\mapdata\te\test1\test1.sfx  // :DDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pmdDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.jsmDebugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pcb
 
Last edited:
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.

the logic is something like: (note that this isn't 100% correct)
Code: [Select]
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 memory uint8_t unk[124]; uint8_t unkbyte1D27B90; //unknown flag byte uint8_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)Character *ptr1D27B10 = (Character*)0x1D27B10; //208 byte structure - appears to be array of 6 itemsbool *init_done = (bool*)0x1D28E09; //1 byte - seems to be 0 at first turn in battle for init then 1 otherwiseunk1D28E89 *ptr1D28E89 = (unk1D28E89*)0x1D28E89; //71 byte structure - appears to be array of 6 itemsuint32_t my_sub_487DF0(uint32_t monster_id, uint8_t *AICode, uint8_t *arg3, uint8_t *arg4) //monster id seems to be 3,4 or 5... I'm assuming 0, 1 and 2 are reserved for your party{ uint32_t local0; uint32_t local1; uint32_t local2; uint32_t local3; uint8_t next_ai_byte_1; //local.4_0 uint8_t next_ai_byte_2; //local.4_1 uint8_t next_ai_byte_3; //local.4_2 uint32_t local5; uint8_t current_ai_byte; //local.6_0 uint8_t local7_0; uint8_t *monster_info; //local.8_0 uint32_t local9; uint32_t local10; uint32_t local11; uint32_t local12; uint32_t local13; uint8_t local14_0; uint32_t local15; uint32_t local16; uint32_t local17; uint32_t local18; uint32_t local19; uint32_t local20; uint8_t local21_0; uint32_t local22; uint8_t local23_0; uint32_t local24; local22 = (uint32_t)ptr1D28E89[monster_id].unk; local21_0 = 0; local7_0 = 0; local23_0 = 0; monster_info = *ptr1D27B10[monster_id].monster_info; local14_0 = 0; local24 = 0; //esi     //monster_id = 0 - esi if (init_done && ptr1D27B10[monster_id].unkbyte1D27B90 & 0x20 != 0) {  //TO DO  //jumps somewhere - I've not really seen this section of code called  } //loop do {  current_ai_byte = *AICode++;  switch (current_ai_byte) {   //TO DO  } } while (current_ai_byte != 0);}
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 (https://www.ff7catalog.com/threads/7441/) 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.
 
Last edited:
Thanks for the AI interpretor function! It's one of the biggest function I've seen here. :D

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):
Breaker.jpg

I have no screen for 1234567 damage, but it only displays 34567 leaving "12' unrendered. :C
 
Status
Not open for further replies.
Back
Top