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

Halfer

  • *
  • Posts: 142
    • View Profile
[FF8] Engine reverse engineering
« on: 2016-03-06 17:17:14 »
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]
Game version: FF8 PC - 2000

Address: 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]
Game version: FF8 PC - 2000

Address: 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 Edit: 2016-03-06 19:40:11 by Halfer »

paul

  • *
  • Posts: 179
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #1 on: 2016-03-06 19:20:47 »
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

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #2 on: 2016-03-06 20:11:45 »
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.

Halfer

  • *
  • Posts: 142
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #3 on: 2016-03-06 21:35:02 »
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.

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #4 on: 2016-03-07 15:47:35 »
Okay. I have my notes:
FF8 2000 PC - Game memory start as:
Code: [Select]
FF8.exe = 0x400000
General Kernel.dll LoadFile:
Code: [Select]
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]
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]
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]
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 Edit: 2016-03-07 16:16:29 by MaKiPL »

paul

  • *
  • Posts: 179
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #5 on: 2016-03-07 17:43:09 »
MaKiPL - why not fork and continue the work in https://github.com/rcxrdx/FF8Modding/blob/master/src/Hooks.cpp? :)

Halfer

  • *
  • Posts: 142
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #6 on: 2016-03-07 18:09:16 »
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.

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #7 on: 2016-03-08 16:15:35 »
MaKiPL - why not fork and continue the work in https://github.com/rcxrdx/FF8Modding/blob/master/src/Hooks.cpp? :)


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]
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]
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]
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]
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;
« Last Edit: 2016-03-08 17:14:41 by MaKiPL »

Halfer

  • *
  • Posts: 142
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #8 on: 2016-03-08 16:54:08 »
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]
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]

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 Edit: 2016-03-08 19:16:55 by Halfer »

paul

  • *
  • Posts: 179
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #9 on: 2016-03-08 18:57:45 »
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;

?

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #10 on: 2016-03-09 15:34:22 »
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]
call dword ptr [00B6908C] - call dword ptr [FF8.exe+76908C]

Works now. The engine now display debug strings like:
Code: [Select]
MIDI stop
midi_play...
Stopping Performance
Playing Segment
midi_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 Edit: 2016-03-16 17:31:18 by MaKiPL »

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #11 on: 2016-03-19 20:29:30 »
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]
// 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]
// 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]
// 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]
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]
int sub_40A04A()
{
  return Pointer_MemoryStaticPaths;
}

and is set by:
Code: [Select]
.data:01A79D88 Pointer_MemoryStaticPaths dd ?          ; DATA XREF: Set_Pointer_MemoryPaths+6wThe 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.
« Last Edit: 2016-03-19 21:48:11 by MaKiPL »

paul

  • *
  • Posts: 179
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #12 on: 2016-03-19 23:27:05 »
Seems it has a huge singleton with tons of data like FF7 has..

Shunsq

  • *
  • Posts: 142
  • 20 years to mod a game, that's insanely long
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #13 on: 2016-03-20 10:48:56 »
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.


Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #14 on: 2016-03-20 19:44:44 »
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]
*EDIT: Those addresses below are all wrong. I don't know what the heck have I done lol
46C850 - 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]
.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 Edit: 2016-03-28 14:03:01 by MaKiPL »

Mcindus

  • *
  • Posts: 929
  • Artist, Modder, Musician.
    • View Profile
    • Lunatic Pandora
Re: [FF8] Engine reverse engineering
« Reply #15 on: 2016-03-23 21:27:55 »
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-)

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #16 on: 2016-03-27 11:52:53 »
0x00559F70: PlayMovie(unsigned __int8 PAKfile, unsigned __int8 MovieID) - Plays Movie. PAKfiles are:
Code: [Select]
[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]
FF8.exe+15A085 - mov [esp+48],00000500
FF8.exe+15A08D - mov [esp+44],000002D0
FF8.exe+15A073 - mov [esp+48],00000500
FF8.exe+15A07B - mov [esp+44],000002D0
FF8.exe+1F52 - mov ecx,00000500 - Window for 720p
FF8.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]
.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]
[0]'A'; [1]'K'; [2]'A'; [3]'O'; [4]Byte SongID;
Sample loop:
Code: [Select]
_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 Edit: 2016-03-28 16:18:13 by MaKiPL »

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #17 on: 2016-04-01 19:08:28 »
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_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]
Debugged application message: f:\DISK1
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\world\dat\wmsetus.obj                     //Displays even world.fs!
Debugged application message: smPcRead::%s

Debugged 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 Performance
Debugged application message: Playing Segment
Debugged application message: midi_play successful

Debugged application message: f:\DISK1
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\maplist
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.mim   //HOW COOL IS THAT? :3
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pmp
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pvp
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.id
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.map
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.ca
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.inf
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.rat
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.mrt
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.msd
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.sfx
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pmd
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.jsm
Debugged application message: smPcRead::%s

Debugged application message: SdMusicPlay ()
Debugged application message: sd_music_play (number=0, song_id=42, volume=127)
Debugged application message: MIDI stop
Debugged application message: midi_play...
Debugged application message: Stopping Performance
Debugged application message: Playing Segment
Debugged application message: midi_play successful

Debugged application message: \ff8\data\eng\field\mapdata\bc\bcgate_1\bcgate_1.pcb       // SEE?? Shows even field things
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\world\dat\wmsetus.obj
Debugged application message: smPcRead::%s

Debugged application message: MIDI stop
Debugged 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 Performance
Debugged application message: Playing Segment
Debugged application message: midi_play successful

Debugged application message: SdMusicPlay ()
Debugged application message: sd_music_play (number=0, song_id=5, volume=127)
Debugged application message: MIDI stop
Debugged application message: midi_play...
Debugged application message: Stopping Performance
Debugged application message: Playing Segment
Debugged application message: midi_play successful

Debugged application message: \ff8\data\eng\battle\A0STG101.X
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\B0WAVE.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\C0M028.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\D2C006.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\D2W016.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\D0C000.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\D0W006.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\D3C007.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\D3W020.DAT
Debugged application message: smPcRead::%s

Debugged application message: \ff8\data\eng\battle\R0WIN.DAT
Debugged application message: smPcRead::%s

Debugged application message: MIDI stop
Debugged 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 Performance
Debugged application message: Playing Segment
Debugged application message: midi_play successful

73F63400: thread has started (tid=8888)
Debugged application message: \ff8\data\eng\world\dat\wmsetus.obj
Debugged application message: smPcRead::%s

Debugged application message: MIDI stop
Debugged application message: sd_music_play (number=0, song_id=41, volume=127)
Debugged application message: midi_play...
Debugged application message: Stopping Performance
Debugged application message: Playing Segment
Debugged 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]
Debugged application message: smPcRead::\ff8\data\eng\harata.cnf
Debugged application message: smPcRead::\ff8\data\eng\kernel.bin
Debugged application message: smPcRead::\ff8\data\eng\sysfnt.tdw
Debugged application message: smPcRead::\ff8\data\eng\icon.tim
Debugged application message: smPcRead::\ff8\data\eng\namedic.bin
Debugged application message: smPcRead::\ff8\data\eng\wm2field.tbl
Debugged application message: smPcRead::\ff8\data\eng\menu\mngrphd.bin

5F2A0000: loaded C:\WINDOWS\SysWOW64\msadp32.acm
Debugged application message: smPcRead::\ff8\data\eng\credits.tim

Debugged application message: midi_play...
Debugged application message: Stopping Performance
Debugged application message: Playing Segment
Debugged application message: midi_play successful
Debugged application message: MIDI stop

Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\maplist
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.mim
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pmp
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pvp
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.id
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.map
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.ca
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.inf
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.rat
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.mrt
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.msd
Debugged application message: Can't open file: \ff8\data\eng\field\mapdata\te\test1\test1.sfx  // :D
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pmd
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.jsm
Debugged application message: smPcRead::\ff8\data\eng\field\mapdata\te\test1\test1.pcb
« Last Edit: 2016-04-02 12:30:56 by MaKiPL »

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #18 on: 2016-04-11 16:40:40 »
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]
//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 items
bool *init_done = (bool*)0x1D28E09; //1 byte - seems to be 0 at first turn in battle for init then 1 otherwise
unk1D28E89 *ptr1D28E89 = (unk1D28E89*)0x1D28E89; //71 byte structure - appears to be array of 6 items

uint32_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 (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.
« Last Edit: 2016-04-13 01:04:49 by JWP »

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #19 on: 2016-04-13 16:43:33 »
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):

I have no screen for 1234567 damage, but it only displays 34567 leaving "12' unrendered. :C

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #20 on: 2016-04-13 18:14:29 »
Hehe, looks nice! Now you can deal all of the damage in the world :D

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.
Code: [Select]
//0x1D27B10
#pragma pack(push, 1)
struct Character
{
BYTE **monster_info; //0x1D27B10 - could just be used for the name but points to section 7 of a loaded dat file
BYTE unk[8]; //0x1D27B14
BYTE status; //0x1D27B1C - MSB - reflect / shell / protect / regen / stop / slow / haste / ? - LSB
BYTE unk1[3]; //0x1D27B1D
DWORD unkdword1D27B20; //0x1D27B20 - used in opcode 0x24
DWORD unkdword1D27B24; //0x1D27B24 - used in opcode 0x24
DWORD currentHP; //0x1D27B28
DWORD maxHP; //0x1D27B2C
BYTE unk2_0[4]; //0x1D27B30
DWORD localVars[8]; //0x1D27B34 - DC to E3 - base address used is 0x1D277C4
WORD unkwordarr1D27B54[6]; //0x1D27B54 at least 6 items, changed by opcode 2D
BYTE unk3_0[4]; //0x1D27B60
WORD unkword1D27B64[16]; //0x1D27B64
DWORD unkdword1D27B84[3]; //0x1D27B84
BYTE status1; //0x1D27B90 MSB - ? / zombie / berserk / ? / ? / ? / ? / ? - LSB
BYTE unk4[7]; //0x1D27B91
BYTE lastAttacker; //0x1D27B98 - used in opcode 0x0E case 0xCB - 0, 1 or 2 for SeeD 3+ for monsters
BYTE unk5_0[7]; //0x1D27B99 - this whole section of unknowns looks to be status resistances
BYTE unk5_1[8]; //0x1D27BA0
BYTE unk5_2[8]; //0x1D27BA8
BYTE unk5_3[8]; //0x1D27BB0
BYTE unk5_4[8]; //0x1D27BB8
BYTE unk5_5[8]; //0x1D27BC0
BYTE unkbyte1D27BC8; //0x1D27BC8 used in case 0x2A
BYTE unk6[3]; //0x1D27BC9
BYTE lvl; //0x1D27BCB
BYTE str; //0x1D27BCC
BYTE vit; //0x1D27BCD
BYTE mag; //0x1D27BCE
BYTE spr; //0x1D27BCF
BYTE spd; //0x1D27BD0
BYTE luck; //0x1D27BD1
BYTE eva; //0x1D27BD2
BYTE unk7[12];
};
#pragma pack(pop)

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 :D.
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 set
0xCA: //??
0xCB: //last attacker - does 1 << the value in it's struct (see struct above)
0xCC: 1000 0000 0000 0111 //all enemies
0xCD: 1000 0000 1111 1000 //all allies
0xCE: 1000 0000 1111 1111 //everyone?
0xCF: //random ally
0xD0: 1010 0000 0000 0111 //something to do with enemies
0xD1: //?? does 1 << the value in 0x1D28DFB
0xDC-0xE3: //does 1 << the value in the corresponding variable
« Last Edit: 2016-04-15 11:27:13 by JWP »

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #21 on: 2016-04-20 16:55:07 »
Thanks JWP for your contribution! <3

Okay. This one is cool! :3
I documented wm2field.tbl file.
http://wiki.qhimm.com/view/FF8/Main_wm2
There 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.tbl
I 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...

UPDATE2: http://wiki.qhimm.com/view/FF8/FileFormat_b0wave is now fully documented
« Last Edit: 2016-04-21 10:57:52 by MaKiPL »

Maki

  • 0xBAADF00D
  • *
  • Posts: 621
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #22 on: 2016-04-21 13:10:07 »
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. :3
3. 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)

01D98B60 operates the speed of sky rotation.
« Last Edit: 2016-04-21 15:49:32 by MaKiPL »

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #23 on: 2016-04-21 19:24:20 »
Nice work! That seems like a crazy amount of functions o_0

Here's my latest information on the battle stuff:
Code: [Select]
struct Character
{
BYTE **infoSection; //0x1D27B10 - points to section 7 of a loaded dat file - NULL for SeeDs
BYTE **aiSection; //0x1D27B14 -  NULL for SeeDs
DWORD status1D27B18; //0x1D27B18 0x27
BYTE status; //0x1D27B1C - MSB - reflect / shell / protect / regen / stop / slow / haste / sleep - LSB
BYTE unk1[3]; //0x1D27B1D
DWORD ATBmax; //0x1D27B20
DWORD ATBcur; //0x1D27B24
DWORD currentHP; //0x1D27B28
DWORD maxHP; //0x1D27B2C
BYTE unk2_0[4]; //0x1D27B30
DWORD localVars[8]; //0x1D27B34 - DC to E3 - base address used is 0x1D277C4
WORD elemRes[8]; //0x1D27B54 - 500 = very weak, 800 = normal, 1000 = fully absorb - 0. Fire, 1. Ice, 2. Thunder, 3. Earth, 4. Poison, 5. Wind, 6. Water, 7. Holy
WORD timers[16]; //0x1D27B64 - status timers
DWORD unkdword1D27B84; //0x1D27B84
DWORD unkdword1D27B88; //0x1D27B88
DWORD unkdword1D27B8C; //0x1D27B8C - edited by function 0x48C5C0
WORD status1; //0x1D27B90 MSB - ? / zombie / berserk / silence / blind / petrify / ? / death - LSB
WORD unkword1D27B92; //0x1D27B92
WORD unkword1D27B94; //0x1D27B94 ????? 0x2E, 0x29, 0x06,
WORD unkword1D27B96; //0x1D27B96 ?????
BYTE lastAttacker; //0x1D27B98 - used in opcode 0x0E case 0xCB
BYTE unk5_0[7]; //0x1D27B99
BYTE mentalRes[40]; //0x1D27BA0 - Mental resistances - 0. Death, 1. Poison, 2. Petrify, 3. Darkness, 4. Silence, 5. Berserk, 6. Zombie, 8. Sleep, 9. Haste, 10. Slow, 11. Stop, 12. Regen, 15. Reflect, 18. Doom, 20. Petrifying, 21. Float, 22. Confuse, 23. Drain, 24. Expulsion, rest are always 100 for monsters
BYTE unkbyte1D27BC8; //0x1D27BC8 used in case 0x2A  - edited by function 0x48C5C0
BYTE unkbyte1D27BC9; //0x1D27BC9 - edited by function 0x48C5C0
BYTE unkbyte1D27BCA; //0x1D27BCA
BYTE unkbyte1D27BCB; //0x1D27BCB - edited by function 0x48C5C0
BYTE lvl; //0x1D27BCC
BYTE str; //0x1D27BCD
BYTE vit; //0x1D27BCE
BYTE mag; //0x1D27BCF
BYTE spr; //0x1D27BD0
BYTE spd; //0x1D27BD1
BYTE luck; //0x1D27BD2
BYTE eva; //0x1D27BD3
BYTE hit; //0x1D27BD4
BYTE unk7[5]; //0x1D27BD5
BYTE crisisLevel; //0x1D27BDA
BYTE unkbyte1D27BDB; //0x1D27BDB
WORD unkword1D27BDC; //0x1D27BDC
BYTE unk8[2]; //0x1D27BDE
};

vars 60-67:
Code: [Select]
DWORD * const vars = (DWORD*)0x1D28D98; //- 60 to 67 - base address used is 0x1D28C18 - array of size 8

and a few functions (note that parameter signs/sizes might not be accurate):

Code: [Select]
int(*count_valid_targets)() = (int(*)())0x4860A0; //0xFF if no valid targets - counts SeeDs that are not petrified or dead
int(*getStat)(int level, BYTE *datfile, int stat) = (int(*)(int, BYTE*, int))0x48C3F0; //gets str, vit, mag, spr, spd, eva from dat file
BYTE(*get_random_number)() = (BYTE(*)())0x48F020;
EDIT: see section 2 of  http://www.gamefaqs.com/ps/197343-final-fantasy-viii/faqs/58936 for how the RNG works

the start of the AI function mentioned before goes something like this:
Code: [Select]
if (init_done && (character_info[id].status1 & 0x20) != 0) { //if berserk
if (count_valid_targets() == 0xFF) {
target = 0; //set no target
next_ai_byte_1 = 0; //make sure to skip AI code on next loop
//00487E6B | .BE 1DA2D201   MOV ESI, OFFSET 01D2A21D
goto lbl489804; //jump to attack code in switch?
}
BYTE randomTarget;
do {
randomTarget = get_random_number() % 3;
} while (character_info[randomTarget].status1 & 0x01 != 0); //check target isn't dead
next_ai_byte_1 = 0; //make sure to skip AI code on next loop
target = 1 << randomTarget;
//00487E6B | .BE 1DA2D201   MOV ESI, OFFSET 01D2A21D
goto lbl489804; //jump to attack code in switch?
}

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 ranges
opcode 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 XX
is 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.
« Last Edit: 2016-05-06 18:34:13 by JWP »

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #24 on: 2016-05-01 22:43:05 »
I'll post roughly what I said to Callisto in a PM since I can't actually read what I sent him.
in this part:
Code: [Select]
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).

FF8_EN.exe:
Code: [Select]
from: 0x8BEE8: B90A 0A0A 0A
to:   0x8BEE8: E9A8 0000 00

from: 0x8BF95: 9090 9090 9090 9090
to:   0x8BF95: 8A87 7B01 0000 EB77

from: 0x8C014: 9090 9090 9090 9090 9090 90
to:   0x8C014: 8886 C07B D201 E912 0100 00

from: 0x8C131: 9090 9090 9090 9090 9090
to:   0x8C131: B90A 0A0A 0AE9 B2FD FFFF

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):
Code: [Select]
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 instruction
0x48C136: JMP 0048BEED ; jump back to where we started
EDIT: 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).
« Last Edit: 2016-05-03 12:17:20 by JWP »