Qhimm.com Forums

Miscellaneous Forums => Scripting and Reverse Engineering => Topic started by: Halfer on 2016-03-06 17:17:14

Title: [FF8] Engine reverse engineering
Post by: Halfer 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.
Title: Re: [FF8] Engine reverse engineering
Post by: paul 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
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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.
Title: Re: [FF8] Engine reverse engineering
Post by: Halfer 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.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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...
Title: Re: [FF8] Engine reverse engineering
Post by: paul 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? :)
Title: Re: [FF8] Engine reverse engineering
Post by: Halfer 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.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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;
Title: Re: [FF8] Engine reverse engineering
Post by: Halfer 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.
Title: Re: [FF8] Engine reverse engineering
Post by: paul 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;

?
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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.
(http://picload.org/image/wppclrg/snap.png)
Title: Re: [FF8] Engine reverse engineering
Post by: paul on 2016-03-19 23:27:05
Seems it has a huge singleton with tons of data like FF7 has..
Title: Re: [FF8] Engine reverse engineering
Post by: Shunsq 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.

Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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) ;)
Title: Re: [FF8] Engine reverse engineering
Post by: Mcindus 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-)
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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)
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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:
(http://s13.postimg.org/pjmqvb9qv/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]
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
Title: Re: [FF8] Engine reverse engineering
Post by: JWP 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 (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.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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):
(https://s18.postimg.org/jxegjlexl/Breaker.jpg)
I have no screen for 1234567 damage, but it only displays 34567 leaving "12' unrendered. :C
Title: Re: [FF8] Engine reverse engineering
Post by: JWP 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
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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:
(http://s31.postimg.org/kpo0y04gq/debug.jpg)
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
Title: Re: [FF8] Engine reverse engineering
Post by: Maki 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.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP 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 (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 (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.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP 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).
Title: Re: [FF8] Engine reverse engineering
Post by: Callisto on 2016-05-02 15:12:05
Amazing work JWP! Just tried this myself and it seems to work perfectly. By making these changes, it is very simple to adjust Vit 0 resistance for each enemy individually, especially in conjunction with Ifrit enemy editor. Almost like a dream becoming true.. Can finally get back to work now. Thanks a ton! :-D
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-03 18:43:10
No problem :). Btw, here's all the stuff you were after in http://forums.qhimm.com/index.php?topic=16679.0 (http://forums.qhimm.com/index.php?topic=16679.0):

Quote
2. Damage formulas for Kamikaze and Darkside abilities

Kamikaze:
Code: [Select]
CPU Disasm
Address   Hex dump               Command                                      Comments
00492D6C  |.  8B81 2C7BD201      MOV EAX,DWORD PTR DS:[ECX+1D27B2C]           ; EAX = caster max HP
00492D72  |.  8D0480             LEA EAX,[EAX*4+EAX]                          ; EAX = EAX*5
Some sites say that the damage multiplier is 6 for kamikaze, so either it's been changed or the code for critting is elsewhere.

Darkside:
Code: [Select]
CPU Disasm
Address   Hex dump               Command                                      Comments
00491069  |> \8D3476             LEA ESI,[ESI*2+ESI]                          ; ESI = ESI * 3 - darkside multiplier

Quote
3. Angel Wing damage multiplier

Code: [Select]
CPU Disasm
Address   Hex dump               Command                                      Comments
00491085  |.  8D34B6             LEA ESI,[ESI*4+ESI]                          ; ESI = ESI * 5 - angel wing multiplier

Quote
6. Changing character base stats and starting level, also giving Squall, Zell and Quistis some default spells at the beginning of the game

Most of the stuff looks like it's loaded from init.out in main.fi, I'd imagine it's a similar format to sections of: http://wiki.qhimm.com/view/FF8/GameSaveFormat (http://wiki.qhimm.com/view/FF8/GameSaveFormat)

Looks like init.out starts at the GF data and the character data starts at 0x440 of init.out - the level looks like it's determined by the XP (level = 1+(XP/1000)) and the magic + stats you can change too - note the stats listed in the save file are bonuses and are added to the usual level calculated stat.
The HP stat for each of the characters is set to 9999 and then capped to the max after loading - the max being calculated from the level-calculated HP + the HP bonus (I think it's HP bonus rather than the max HP that the wiki says).
The parameters for the formula that controls the level curve for each character are located in section 7 of kernel.bin, see http://forums.qhimm.com/index.php?topic=16923.msg240609#msg240609 (http://forums.qhimm.com/index.php?topic=16923.msg240609#msg240609)
EDIT: does anyone know where the calculation of crisis level for limit breaks is? - it'd be pretty useful for some of my investigation stuff
EDIT1: Callisto helped me find the function, it's at 0x4941F0
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-05-05 20:40:04
So much data... Nice! <3 I need to update database and wiki with this. JWP, do you have wiki account?

@topic:
I investigated the harata.cnf file. I came up with another theory.

Battle module has rich debug data code written, but it's destroyed/unusable

From the beginning: I traced all the way harata.cnf is loaded (the myserious file that looks like debug loader for battle programmer). Keep note battle is one module that has no DebugOutput strings. I didn't really find anything, it's loaded and that's kinda all. It's loaded by SmPcRead function before main menu shows along with font and namedic.bin and two hardcoded paths to Y:/work/battle/batwave.dat (Programmer loaded file independently to jumpover archive process). I tried to find the file in memory and see if there are any refferences, however I found the words that were made to recognize harata.cnf file like: 'start' 'manual' 'bdprint' 'pinch' and extreme amount of others that even harata.cnf consist. They ARE used, bad thing is their code is never used (I mean no function calls it- only jump, but it's impossible case due to battle state system overwriting update function for checking states).

Example for displaying/outputing/writing to log file (most probably) for showing current EncounterID from Scene.out:
(http://s32.postimg.org/fehh1nq11/indeks.jpg)
47FCEE pushes this mysterious "SCENE " (Many of them further have the %s when pushing)
47FCF8 This is missing from final game. Did the programmer delete the "StreamToLogFile" like function? This looks like the programmer commented out the function before final release
47FD11 Puts EncounterID (01CFF8B8 ; unsigned __int16 EncounterID if you want :) )
I don't understand this though:
push    1F800000h (and I think we all won't understand, since the function is null now.... )

The scheme for above example is like this:
mov     edx, dword ptr EncounterID                -Get Encounter ID to edx (Encounter ID is uint16 !!! )
and     edx, 0FFFFh - Force EDX to be 16 bit by doing the bitwise operation
push    edx - Push EncounterID
push    1F800000h - Push mysterious something??
call    nullsub_7 - deleted output function

It's complete mess.
Any other data (that is used for writing log file) is obtained from dword_1CFFA28, the only instruction that writes it is 0047F0F9.
There's whole function for this. The function that consists jump to this unused section is: 0047EEF0
Well... the problem is it's fired, when battle state is == 4 (the only case when it's equal to 4 is BattleResultScreen). Simple? Nope. When the game goes into BattleState==4 it's automatically stopped, and the parent function reroutes the state to BattleResult screen, therefore the code for state 4 is never fired (it's no more called by update function). That way the code remain 'unused'. There's a way to force battleState = 4 by memory hacking, but the game crashes (After unlocking user from endless loop at 0047EF09) (the script also loads btitle.ovl file [this file doesn't exist])

Getting out of the loop and stepping over (to avoid instant crash, but to see all the logic) we find those variables are beign written for the first time (It's unknown size byte array):
unk_1CFFB98; unk_1D13C10; unk_1CFFBF4; unk_1D13C6C
We again get into endless loop at 0047F073 (the conditional jump to beginning of function without again testing variables makes it impossible case, till something bad happens). Let's break out of it. Finally we get into core unused sub-routine that directly manages the unused battle debug data.
The software crashes at 'mov     ds:1F800000h, edx' because 1F800000h is unallocated.

Sad...

I found this function in FFVIII Demo version. It's decompilable there. There's pseudocode fragment:
Code: [Select]
v1F800000 = dword_CD2868 + 16496;
  v1F800004 = 17;
  v1F800006 = EncounterID;
  v1F800008 = 1;
  v1F80000C = &off_5FD158;
  nullsub_7(528482304, (unsigned __int16)EncounterID_0, 0, 0);
  v1F800000 = dword_CD2868 + 16496;
  v1F800004 = 3;
  v1F800006 = 00CFAACA + 1;
  v1F800008 = v45;
  v1F80000C = aTime;
  nullsub_7(528482304, 0, 0, 0);
  v1F800000 = dword_CD2868 + 16496;
  v1F800004 = 17;
  v1F800006 = 00CFAACA + 1;
  v1F800008 = 1;
  v1F80000C = &off_5FD164;
  nullsub_7(528482304, dword_CD1944, 0, 0);
  v1F800000 = dword_CD2868 + 16496;
  v1F800004 = 3;
  v1F800006 = 00CFAACA + 2;
  v1F800008 = v46;
  v1F80000C = aRate100;
  nullsub_7(528482304, 0, 0, 0);

Looks now like it outputs Encounter data (only for this fragment)
Does anyone understand the 1F800000 phenomenon?
Title: Re: [FF8] Engine reverse engineering
Post by: paul on 2016-05-06 05:55:03
This is all artifacts of lack of whole program optimization by old (<=MSVC 6.0 C/C++) compilers. They would have used a #define to control the debug stuff, but because the compiler couldn't prove if some functions where used or not indirectly it had to leave them in.

The 1F800000 stuff is writing directly to a PSX memory address, probably another function that should have been compiled out.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-06 22:00:23
JWP, do you have wiki account?
I'm afraid not. I'm not really sure how I would go about getting one.
I've updated the list of magic IDs (http://forums.qhimm.com/index.php?topic=16923.msg240615#msg240615) to include all the function pointers (690 of them!), I'll probably add the texture names at some point if I get around to it.

EDIT: They don't all load .TIM files as I had originally thought, for example:
Code: [Select]
0x0006--0x00B58050--0x00B58080--Leviathan Summon (Tsunami)loads "mag005_b.00" and "mag005_b.01"

Code: [Select]
0x008D--0x0061DE50--0x0061DE70--Renzokuken - 5 Hitsloads "mag140-158-159-258-331-332.tim"

Code: [Select]
0x0074--0x006C3560--0x006C3550--Quezacotl Summon (Thunder Storm)loads "mag115.tim"

it seems that to find any associated files, you subtract 1 from the magic ID, convert it to decimal and any files with that number in it tend to get loaded.
It looks like those 2 functions are all that are needed, since I made magic ID 0xE0 turn into the cure/fire animations and assigned that ID to the fire spell.
Title: Re: [FF8] Engine reverse engineering
Post by: paul on 2016-05-07 09:55:05
I wish you guys would use github pages to document stuff, then anyone can download a copy or fork it and edit it.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-05-07 10:14:41
I'm not very familiar with GitHub. How could github help us collect research? As a big notepad or? So far I know github is used to store source code and as source control. Paul, please lend some tips. :)
Title: Re: [FF8] Engine reverse engineering
Post by: paul on 2016-05-07 11:01:44
Github pages uses a git repo for displaying a website, and since its a git repo you can fork/edit/push back changes. Instead of source code text its wiki page text :P
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-05-07 11:36:15
"
One... Two... Three...
Teach me how to GitHub" - kid to Tidus, Zanarkand 1000 years ago

A notes page that everyone can edit sounds terrific. I'll make sure to create one today.
:)
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-08 11:17:18
Let me know if you do and I'll add a bunch of stuff to it :). I've just finished documenting all of the character section of kernel.bin in this post (http://forums.qhimm.com/index.php?topic=16923.msg240609#msg240609)
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-05-08 12:29:21
Looks like this:
https://github.com/MaKiPL/FF8_Reverse

I'm confused what now. xD
I'll copy all my notes of files there.

EDIT: I'm really... really confused...
EDIT2: Ahh... The wiki page: https://github.com/MaKiPL/FF8_Reverse/wiki
EDIT3: No, really. Maybe I'm just stupid, but I don't understand how github is better than this thread and wiki here.
Title: Re: [FF8] Engine reverse engineering
Post by: halkun on 2016-05-08 16:45:22
Because I pretty much control the wiki, and there hasn't been a lit of demand for people to gain access. If you PM me your username, email address, and password. I can add you to the qhimm wiki
Title: Re: [FF8] Engine reverse engineering
Post by: paul on 2016-05-09 07:03:28
Looks like this:
https://github.com/MaKiPL/FF8_Reverse

I'm confused what now. xD
I'll copy all my notes of files there.

EDIT: I'm really... really confused...
EDIT2: Ahh... The wiki page: https://github.com/MaKiPL/FF8_Reverse/wiki
EDIT3: No, really. Maybe I'm just stupid, but I don't understand how github is better than this thread and wiki here.

You need to make a branch called gh-pages and set it as the default branch name. For example https://github.com/munificent/wren/tree/gh-pages

Then your URL is something like username.repo.github.io

Edit: The github wiki isn't the same as github pages, its a normal wiki that sucks since you have to manually add people like on qhimm wiki and no one and easily grab a whole backup of the wiki.

gh-pages docs: https://help.github.com/articles/what-are-github-pages/
Title: Re: [FF8] Engine reverse engineering
Post by: volvania on 2016-05-20 10:53:20
i dont really understand what i just read but i think you could increase the damage cap and also change music lets say for example make seifer battle music same as edea battle music that would be epic we already know how to change damage and levels and abilities of enemy and gfs and magic damage and affect on enemies and junction ,we only need to change the stupid easy enemy ai (just to add more twist and strategy in battle -seymour and two guado guards- seifer and two elite guards taking damage from seifer and healing him would be nice we know that biggs heal wedge in the communication tower but the condition is hard to met its easly done if we know how to read enemy ai, lastly if you guys can put all this info into a working program like ifrit or mystre save editor and make modifing easier . all the power to you one day we can make the perfect ff8 remake just like ff7 .   :lol:
Title: Re: [FF8] Engine reverse engineering
Post by: Kefka on 2016-05-23 16:50:03
Amazing work JWP! Just tried this myself and it seems to work perfectly. By making these changes, it is very simple to adjust Vit 0 resistance for each enemy individually, especially in conjunction with Ifrit enemy editor. Almost like a dream becoming true.. Can finally get back to work now. Thanks a ton! :-D

Wait, how are you changing Vit0 resistance with Ifrit? I just looked again, and Ifrit doesn't have the Vit0 status byte to edit (the last one available is the byte for the unused Percent spell). So even by using JWPs code changes in the .exe, you'd still have to edit the Vit0 byte in each enemy file with a hex editor, or am I missing something obvious here? Sorry if this is a dumb question.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-23 19:20:38
The patch I made makes the unused byte in the .dat files (which is marked as percent in ifrit) change vit0, there is no vit0 byte normally.
He's just changing percent byte in Ifrit after applying the patch I made, which edits vit0.
Title: Re: [FF8] Engine reverse engineering
Post by: Callisto on 2016-05-23 19:30:16
No, there is no need for manually changing that byte with a hex editor. After applying JWP's code changes to the .exe, you can use Ifrit and adjust that byte to your liking for Vit 0 resistance. For example, choose 155 for complete immunity, then start up the game and try inflicting Vit 0 on that enemy. It should no longer work.
Title: Re: [FF8] Engine reverse engineering
Post by: Kefka on 2016-05-24 14:27:42
Ah, ok, I got it now. Thanks to the both of you.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-24 22:04:26
So I had another look at the GFs today... it seems that there are a bunch more functions than I had anticipated =/.

Take for example Shiva...
the main function that I've put in this post (http://forums.qhimm.com/index.php?topic=16923.msg240615#msg240615) is 0x005C0D50.
a bit later in that function, function pointers are pushed as arg2 to the function 0x508360 (it's called twice) - the function pointers that are pushed are 0x5C7F50 and 0x5C0F30 - I'm assuming the function 0x508360 stages those 2 functions to be called every frame.
both of these functions appear to be called every frame (I guess it controls the animation) since there's a switch at 0x5C0FC5 that switches on a number that increases every time the function is called.
This switch appears to control things that happen during the animation - I'm assuming that it's switching on the frame number (e.g. loading extra files - the function 0x508480 loads files from this array (http://wiki.qhimm.com/view/FF8/Engine_const/BattleFiles))
The arg to both of these functions is a pointer to some sort of struct, of which the DWORD at offset 0x0C is the current frame number.

Since the constants in the function 0x5C0F30 appear to be for Shiva - (for example the file loaded at 0x5C0FFE uses the constant 0x21F for MAG184_D.DAT), I'm assuming that there are functions like this for all the GFs and possibly magic too.
Title: Re: [FF8] Engine reverse engineering
Post by: Kefka on 2016-05-27 14:29:56
@JWP: one more question about your Vit0 patch, I tried it with the german version but it didn't work, and I realised that the offsets in the German FF8.exe are all exactly 48 bytes further down than in the English version that you posted earlier.

While your offsets are:

0x8BEE8:
0x8BF95:
0x8C014:
0x8C131:

Mine are:

0x8BF18:
0x8BFC5:
0x8C044:
0x8C161:

I suppose due to this the patch can't be applied 1:1 because certain jumps in your patch might lead to wrong places. Do you by any chance know what I would need to change in order to make it work for my version?
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-27 18:18:13
Should work with the new addresses as long as they are offset by the same amount and the code around the area is actually the same because all the jumps are encoded with relative addresses.
Although they appear to be absolute addresses in the assembly code, at the machine code level they are encoded as relative jumps.
EB and E9 opcodes (which are used for the jumps in the patch) both use relative addresses, see here (http://x86.renejeschke.de/html/file_module_x86_id_147.html).

If patching the new addresses doesn't work, could you give me a disassembly of the area in question?
Title: Re: [FF8] Engine reverse engineering
Post by: Kefka on 2016-05-27 20:52:54
If patching the new addresses doesn't work, could you give me a disassembly of the area in question?

Sure, I've sent you a PM. Thanks.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-05-27 21:18:21
Ah thanks, I think I see what's wrong now.
The offset of the battle data in memory is different so the offset 0x1D27BC0 in the code no longer works. It looks like the battle data is offset 0x1A80 higher in memory in the German version.
I think the full patch for the German version should be:

Code: [Select]
from: 0x8BF18: B90A 0A0A 0A
to:   0x8BF18: E9A8 0000 00

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

from: 0x8C044: 9090 9090 9090 9090 9090 90
to:   0x8C044: 8886 4096 D201 E912 0100 00

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

let me know if it works :)
Title: Re: [FF8] Engine reverse engineering
Post by: Kefka on 2016-05-28 06:52:36
Thanks a ton, it worked now! I just tried it in the Cerberus battle and casted about 20-30 Meltdowns on him without inflicting the status once!
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-02 19:02:09
I'm guessing this is known but I can't find a list anywhere.
I found an array of function pointers starting at 0xB8DE94 which appear to be related to all the field opcodes.

Here's a full dump of addresses corresponding to these opcodes (http://wiki.qhimm.com/view/FF8/Field/Script/Opcodes):
Code: [Select]
000 0x0051C160
001 0x0051C4B0
002 0x0051C4D0
003 0x0051C4F0
004 0x0051C530
005 0x0051C570
006 0x0051C5C0
007 0x0051C990
008 0x0051CAB0
009 0x0051CC70
00A 0x0051CAF0
00B 0x0051CCA0
00C 0x0051CB30
00D 0x0051CCD0
00E 0x0051CB70
00F 0x0051CD00
010 0x0051CBB0
011 0x0051CBF0
012 0x0051CC30
013 0x0051CD30
014 0x0051CD60
015 0x0051CED0
016 0x0051D060
017 0x0051D1F0
018 0x0051D360
019 0x0051D530
01A 0x0051DD80
01B 0x0051D700
01C 0x0051D710
01D 0x0051D720
01E 0x0051D780
01F 0x0051D7F0
020 0x0051D830
021 0x0051FF00
022 0x00520460
023 0x005256A0
024 0x00525740
025 0x00525900
026 0x00525A30
027 0x00525B60
028 0x00525CA0
029 0x00521A20
02A 0x00521AC0
02B 0x0051D8F0
02C 0x0051D9B0
02D 0x00526570
02E 0x00526620
02F 0x005266D0
030 0x00526810
031 0x00526890
032 0x00526910
033 0x00526990
034 0x005269E0
035 0x00526A30
036 0x00526AB0
037 0x0051DA00
038 0x00521B70
039 0x0051DC30
03A 0x0051DCE0
03B 0x0051DD00
03C 0x0051D870
03D 0x005233E0
03E 0x00523410
03F 0x00523640
040 0x00523880
041 0x00523AD0
042 0x00523C20
043 0x00525800
044 0x005264D0
045 0x005264F0
046 0x00528E40
047 0x00528F20
048 0x00529900
049 0x00528D40
04A 0x00529520
04B 0x00529A20
04C 0x00529B60
04D 0x0051DDA0
04E 0x0051DE90
04F 0x0051F390
050 0x0051F4F0
051 0x0051E0E0
052 0x00526E60
053 0x00526E90
054 0x00526F30
055 0x00526F70
056 0x0051F520
057 0x0051EBB0
058 0x0051EBD0
059 0x0051EBF0
05A 0x0051EC10
05B 0x0051ED80
05C 0x00521C30
05D 0x00521CB0
05E 0x00521CC0
05F 0x00528E10
060 0x0051EAD0
061 0x0051EB40
062 0x0051EDD0
063 0x0051EE00
064 0x00529020
065 0x005291E0
066 0x0051EE30
067 0x0051ED40
068 0x0051ED60
069 0x00523270
06A 0x005232E0
06B 0x00523300
06C 0x00523330
06D 0x0051DA50
06E 0x0051DAA0
06F 0x005296C0
070 0x0051EEB0
071 0x00520C40
072 0x00520C90
073 0x00520CF0
074 0x00520D50
075 0x00520D90
076 0x00520DF0
077 0x00520F50
078 0x00524030
079 0x005241A0
07A 0x00524310
07B 0x00524490
07C 0x00524550
07D 0x00524600
07E 0x0051D8B0
07F 0x00520E50
080 0x00520E90
081 0x00520EF0
082 0x00527250
083 0x00527320
084 0x005273F0
085 0x005274C0
086 0x0051E160
087 0x0051E270
088 0x0051E350
089 0x0051E5D0
08A 0x0051E400
08B 0x0051E5E0
08C 0x0051E670
08D 0x0051E710
08E 0x0051E7D0
08F 0x00527590
090 0x00527690
091 0x00527790
092 0x005278A0
093 0x00524BA0
094 0x005299F0
095 0x00520570
096 0x00520640
097 0x005206E0
098 0x00520780
099 0x005207A0
09A 0x00520800
09B 0x00520850
09C 0x005216E0
09D 0x00521730
09E 0x005217F0
09F 0x00526C30
0A0 0x00526C80
0A1 0x0051F620
0A2 0x0051F670
0A3 0x0051F2C0
0A4 0x00521710
0A5 0x005283B0
0A6 0x00528490
0A7 0x00528CA0
0A8 0x00520BA0
0A9 0x00520C20
0AA 0x00528D10
0AB 0x0051EC30
0AC 0x0051ECF0
0AD 0x00523370
0AE 0x00523380
0AF 0x00526E30
0B0 0x00526D30
0B1 0x00523D70
0B2 0x00523ED0
0B3 0x00526CD0
0B4 0x0051F7F0
0B5 0x0051F730
0B6 0x00528CE0
0B7 0x00526B00
0B8 0x00526B80
0B9 0x005217B0
0BA 0x0051FA50
0BB 0x0051FB00
0BC 0x0051FEA0
0BD 0x0051FDE0
0BE 0x0051F680
0BF 0x0051FBD0
0C0 0x0051FC70
0C1 0x0051FCD0
0C2 0x0051FD40
0C3 0x0051FFA0
0C4 0x0051FFD0
0C5 0x00520010
0C6 0x00520040
0C7 0x00520080
0C8 0x005200C0
0C9 0x00520110
0CA 0x00520150
0CB 0x0051F700
0CC 0x00523230
0CD 0x005201A0
0CE 0x00520880
0CF 0x0051F690
0D0 0x005208B0
0D1 0x00520AD0
0D2 0x005209A0
0D3 0x00520FA0
0D4 0x00521010
0D5 0x00521090
0D6 0x00521110
0D7 0x00521170
0D8 0x005211F0
0D9 0x00521270
0DA 0x005212D0
0DB 0x00521350
0DC 0x00520F60
0DD 0x005213D0
0DE 0x00521D00
0DF 0x00521CF0
0E0 0x005204E0
0E1 0x00520500
0E2 0x00520520
0E3 0x00520550
0E4 0x00521960
0E5 0x0051DD60
0E6 0x00525090
0E7 0x005264A0
0E8 0x0051D8D0
0E9 0x00528570
0EA 0x00528670
0EB 0x00528770
0EC 0x005288A0
0ED 0x005289D0
0EE 0x00528B20
0EF 0x00528C70
0F0 0x00525DE0
0F1 0x00525E70
0F2 0x00525F30
0F3 0x00525FF0
0F4 0x005260A0
0F5 0x00526150
0F6 0x00526180
0F7 0x00526170
0F8 0x00521CD0
0F9 0x00521CE0
0FA 0x00526190
0FB 0x0051D910
0FC 0x0051D960
0FD 0x00527B90
0FE 0x00527C30
0FF 0x00527D30
100 0x00528300
101 0x00527E40
102 0x0051DB90
103 0x0051DBC0
104 0x0051DBF0
105 0x0051DC10
106 0x00528D80
107 0x00528350
108 0x00527AF0
109 0x0051EFB0
10A 0x005218E0
10B 0x00523350
10C 0x00520490
10D 0x00521820
10E 0x00527F10
10F 0x00527FA0
110 0x00528030
111 0x00528130
112 0x00528240
113 0x00527AD0
114 0x0051F0C0
115 0x0051F140
116 0x00529380
117 0x00520B00
118 0x005261C0
119 0x005261A0
11A 0x00521D10
11B 0x00521D40
11C 0x005216B0
11D 0x0051E640
11E 0x00521D60
11F 0x00523390
120 0x005214C0
121 0x00521530
122 0x005215F0
123 0x00524840
124 0x00524810
125 0x00522380
126 0x00523180
127 0x005231C0
128 0x0051EA10
129 0x00521DA0
12A 0x005220A0
12B 0x00524620
12C 0x005246F0
12D 0x0051F1F0
12E 0x00522190
12F 0x005221B0
130 0x00522280
131 0x0051EA50
132 0x0051F600
133 0x00523200
134 0x00521930
135 0x0051F9A0
136 0x005224D0
137 0x00522770
138 0x00522230
139 0x0051E0B0
13A 0x005225A0
13B 0x00529BF0
13C 0x00529C40
13D 0x00529E70
13E 0x00521460
13F 0x0051EF70
140 0x0051FC20
141 0x0051F880
142 0x0051DD40
143 0x0051DD20
144 0x0051F900
145 0x005220B0
146 0x00522110
147 0x00522150
148 0x005247E0
149 0x0051FDC0
14A 0x00526390
14B 0x005263F0
14C 0x0051DAF0
14D 0x0051DB40
14E 0x005230E0
14F 0x00523110
150 0x0051FBA0
151 0x005223C0
152 0x00522420
153 0x00522480
154 0x00523140
155 0x00523030
156 0x00522070
157 0x0051E910
158 0x0051E990
159 0x0051E9C0
15A 0x00522010
15B 0x00526240
15C 0x00526280
15D 0x005262C0
15E 0x00522500
15F 0x00522540
160 0x00522570
161 0x005222D0
162 0x0051E8A0
163 0x0051E860
164 0x0051F5B0
165 0x00529D40
166 0x00529EB0
167 0x00529EF0
168 0x00529F70
169 0x00526210
16A 0x00522350
16B 0x00526FD0
16C 0x00527070
16D 0x00527110
16E 0x005271B0
16F 0x0051FC40
170 0x005279B0
171 0x00527AB0
172 0x00520200
173 0x00520230
174 0x005258D0
175 0x005230A0
176 0x005219F0
177 0x00522030
178 0x00320000
179 0x00960064

I've not actually tested them yet though :P
Not sure if they're numbered correctly since there are some jumps in the opcode numbers.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-06-02 20:27:01
I'm guessing this is known but I can't find a list anywhere.
I found an array of function pointers starting at 0xB8DE94 which appear to be related to all the field opcodes.

Oh, this is gold! Thank you so much JWP! This will speed up reversing process extremely, because we could be able to locate functions and variables via scripting!

They are not numbered correctly. I could test it only on three examples, but:
53 entry is mine SFX_sub_51FDB0 (related to sound effect) and on list 0x35 is 035 RANIMELOOP
299 entry is mine WorldMap_To_Balamb_Music (Related to AKAO frame and is called when from WM to town) and on list 0x12B is 12B MOVECANCEL
329 entry is mine Draw_Interaction_GetMagic (In fact it's DRAWPOINT opcode) and on list 0x149 is 149 MUSICVOLSYNC, but should be     137 DRAWPOINT

@UPDATE: No, it's kind of okay I think.
The first 16 offset pointers are for 01 CAL operations:
http://wiki.qhimm.com/view/FF8/Field/Script/Opcodes/001_CAL

Now it works:
33 entry (0x21) is opcode:     021 EFFECTPLAY2 (OKAY!)
180 entry (0xB4) is opcode: 0B4 MUSICCHANGE (Yes, therefore my WorldMap_To_Balamb_Music is correct then. It's only for music)
and last one to check for sure: 311 (0x137)     137 DRAWPOINT. Correct!
They are correctly numbered then.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-02 20:36:47
Odd, I tested the following:
Code: [Select]
137 0x00522770 DRAWPOINT
and
Code: [Select]
155 0x00523030 SETDRAWPOINT
and they both seemed correct to me, what version of the game are you using? - it might be offset slightly in your version.
Easiest way to find the function pointer address list is to put a breakpoint on the DRAWPOINT opcode function, then exec until return and look at the line that called it, should be something like CALL DWORD PTR DS:[EAX*4+some_address]
some_address is where the function pointer list starts.

The only time the opcodes get weird is near all the unknown stuff but I think that might be the wiki since they're all called with opcode*4+function_pointer_base.
For instance, UNKNOWN16 in Deling (0x175) is labelled UNKNOWN10 on the wiki.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-06-02 20:39:56
The 2000 version, but it's all right now. I counted the offsets including 16 CAL offsets, that's why there were some errors.
Cool, I'm going to update my database with correct names. The game uses those scripts even from own code function, so again JWP, great work!
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-02 20:53:14
Ah yeah, there are a bunch of function pointers for other stuff before the offset I listed, I was wondering what those were for.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-04 15:55:56
I've been looking through and documenting some of the field stuff.
It seems like all of the field functions are called with 2 arguments:
arg1 = pointer to current entity data
arg2 = function argument

Background entities are 436 bytes each.
Door entities are 396 bytes each.
line entities are 416 bytes each.
Other entities are 612 bytes each.

the entity data looks something like the following (note there are gaps between the entries since I'm still filling the data in and there are probably some differences between entities):
There is probably a base class with all the stack and instruction stuff that the entities have in common and then it's extended differently depending on the entity type.
Code: [Select]
struct entity {
DWORD stack[];
DWORD templist[8]; //+0x140
WORD instruction_pointer; //+0x176
BYTE stack_count; //+0x184
DWORD x_pos; //+0x190
DWORD y_pos; //+0x194
DWORD z_pos; //+0x198
WORD push_radius; //+0x1F6
WORD talk_radius; //+0x1F8
WORD triangle_id; //+0x1FA
WORD move_speed; //+0x1FE
WORD move_speed; //+0x200
WORD anime_speed; //+0x208
WORD model; //+0x218
BYTE angle; //+0x241
BYTE push_disabled; //+0x249
BYTE talk_disabled; //+0x24B
BYTE through_enabled; //+0x24C
BYTE base_anim_id; //+24F
BYTE base_anim_first; //+250
BYTE base_anim_last; //+251
BYTE ladder_anim_id; //+252
BYTE ladder_anim_first; //+253
BYTE ladder_anim_last; //+254
}

for init, the scripts are executed in this order (called in func 0x52C650):
1. background
2. door
3. line
4. other

during each frame, they are executed in this order (called in func 0x529FF0):
1. other
2. line
3. door
4. background

notable memory variables (offsets are for English Steam version):
Code: [Select]
BYTE 0x1D9CDF1 current_entity
DWORD 0x1D9CF88 entity_ptr_other
DWORD 0x1D9CF8C entity_ptr_background
DWORD 0x1D9CF90 entity_ptr_door
BYTE 0x1D9D019 entity_count_other
BYTE 0x1D9D0E0 entity_count_line
BYTE 0x1D9D0E1 entity_count_door
BYTE 0x1D9D0E8 entity_count_background
DWORD 0x1D9D0F0 entity_ptr_line
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-14 18:48:18
I've been working quite a lot on Doomtrain but I also decided to take another look at the battle AI and coded a quick extension to Ifrit to help me view the AI data.
(http://extapathy.co.uk/pics/ifritai.png)
Here's a list of opcodes - the names are just ones that I'm using for the pseudo-code:
Code: [Select]
0x01 - say(arg1)
0x02 - if
0x03 - setmagic(arg1) - sets a magic spell to use - not used but this can be used with 0x06 to cast the magic
0x04 - target(arg1)
0x05 - unknown05(arg1) - Used by Elvoret
0x06 - domove() - not used in the game
0x07 - setenemyattack(arg1) - similar to 0x03 but uses 0x08 as the type flag
0x08 - die() - used a lot in death code to actually kill the entity (e.g. creeps, NORG Pod, Biggs, Wedge) - monsters that use this cannot be killed normally
0x09 - unknown09(arg1) - I think this sets a flag byte to the byte specified - used by Seifer, Raijin, Edea, Left/Right Orb, Fake President, Adel
0x0B - choose(arg1, arg2, arg3) - pick randomly between 3 moves
0x0C - domoveid(arg1) - does the specified move from the move list
0x0D - unused and appears to do nothing
0x0E - set self variable (vars are DC - E3)
0x0F - set global battle variable (vars are 60 - 67)
0x11 - set save variable (vars are 50-57 - these are stored in the save game file - tonberry count etc.)
0x12 - add to self variable
0x13 - add to battle variable
0x15 - add to save variable
0x16 - fillhp()
0x17 - cannotescape(arg1)
0x18 - unknown18(arg1) - appears to be another text print function - used by Tiamat, Wedge
0x19 - all this does is skip a byte, it's used in the init code for Biggs and Wedge but does nothing useful
0x1A - movesay(arg1)
0x1B - unknown1B(arg1, arg2) - used by Biggs
0x1C - unknown1C(arg1) - used by Fake President, BGH251F2, Mobile Type 8, Slapper, Biggs
0x1D - remove(arg1)
0x1E - magicid(arg1) - play specified magic id - used by Biggs, Wedge, Elvoret, G-soldier (for BGH251F2 battle)
0x1F - launch(arg1)
0x20 - similar to 0x1C but unused
0x22 - unknown and unused
0x23 - jump X bytes
0x24 - fillatb()
0x25 - scanadd(arg1)
0x26 - targetadv(arg1, arg2, arg3, arg4)
0x27 - autostatus(arg1, arg2)
0x28 - changestat(arg1, arg2)
0x29 - stealmagic() - steal random magic
0x2A - castmagic() - cast stolen magic
0x2B - targetposition(arg1)
0x2C - uknown2C() - kills the entity and does something? - used by Seifer, Elvoret?
0x2D - changeresistance(arg1, arg2)
0x2E - killmagic() - blow away random magic
0x2F - targetable() - not used
0x30 - untargetable() - makes current entity untargetable
0x31 - givegf(arg1)
0x32 - unknown32() - used by Sphinxaur, Griever, Trauma
0x33 - unknown33() - used by Sphinxara, Edea, Trauma, "Sorceress", Ultimecia
0x34 - unknown34(arg1) - used by Trauma
0x35 - unknown35(arg1) - used by Griever, Trauma
0x36 - gilgamesh() - disables Odin and enabled Gilgamesh
0x37 - givecard(arg1)
0x38 - giveitem(arg1)
0x39 - gameover()
0x3A - targetableid(arg1) - makes entity in slot X targetable
0x3B - unknown3B(arg1, arg2) - used by Ultimecia when spawning helixes and final form
0x3C - addhp(arg1)
0x3D - proofofomega()
note: opcodes 0x0A, 0x10, 0x14 and 0x21 don't exist.
Quite a few of the unknown codes might be related to spawning enemies, changing music or changing scene.

I might go into detail about some of the opcodes at another point.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2016-06-14 20:00:40
DAMN!
This is amazing!

If you could, show the witch script please, either the first or second type witch. I'm looking for a starting point of level switch in witches battle (probably some magic or maybe other opcode). Still can't replace the stages even by using the original stage warp function, I'm missing something...
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-14 20:26:30
Sure :)

Sorc1 init:
Code: [Select]
if (var60 != 0){
    var60 += 1
    if (var62 == 0){
        var62 = 1
    }
    else {
        var62 = 2
    }
    target(200)
    domove(1)
    if (var60 == 2){
        self.varDC = 2
    }
    if (var60 == 3){
        self.varDC = 3
    }
    if (var60 == 4){
        self.varDC = 4
    }
    if (var60 == 5){
        self.varDC = 5
    }
    if (var60 == 6){
        self.varDC = 6
    }
    if (var61 == 3){
        var61 = 0
        self.varDD = 3
    }
    else {
        var61 = 0
        self.varDD = 6
    }
}
else {
    var60 += 1
    var62 = 1
    var63 = 0
    self.varDC = 1
    self.varDD = 3
    target(200)
    domove(0)
}

Sorc1 turn:
Code: [Select]
if (var60 >= 10){
}
else {
    if (rand() % 2 == 0){
        if (var62 == 1){
            if (self.varDD == 3){
                if (var60 >= 6){
                    var61 = 4
                    unknown33()
                    launch(1)
                    return
                }
                else {
                    var61 = 6
                    unknown33()
                    launch(3)
                    return
                }
            }
            else {
                if (self.varDD == 6){
                    if (var60 >= 6){
                        var61 = 5
                        unknown33()
                        launch(2)
                        return
                    }
                    else {
                        var61 = 3
                        unknown33()
                        launch(0)
                        return
                    }
                }
            }
        }
    }
}
if (rand() % 3 == 0){
    return
}
if (rand() % 3 == 0){
    target(201)
    domove(3)
    return
}
if (self.varDC == 1){
    if (unknown(0x14) == 23){
        if (enemy.status == 23){
            targetadv(0, 201, 0, 23)
            domove(2)
            return
        }
        else {
            target(202)
            domove(5)
            return
        }
    }
    else {
        targetadv(0, 200, 3, 23)
        domove(2)
        return
    }
}
if (self.varDC == 2){
    if (enemies.alive == 1){
        if (unknown(0x14) == 23){
            target(201)
            domove(3)
            return
        }
    }
    else {
        target(201)
        domove(4)
        return
    }
}
if (self.varDC == 3){
    if (enemies.alive == 1){
        if (unknown(0x14) == 23){
            target(201)
            domove(3)
            return
        }
    }
    else {
        target(201)
        domove(8)
        return
    }
}
if (self.varDC == 4){
    if (unknown(0x14) == 23){
        if (enemy.status == 23){
            targetadv(0, 201, 0, 23)
            domove(9)
            return
        }
        else {
            target(202)
            domove(5)
            return
        }
    }
    else {
        target(201)
        domove(9)
        return
    }
}
if (self.varDC == 5){
    if (enemy.status == 4){
        targetadv(0, 201, 0, 4)
        domove(11)
        return
    }
    else {
        if (unknown(0x14) == 23){
            if (enemies.alive == 1){
                target(201)
                domove(3)
                return
            }
            else {
                target(201)
                domove(7)
                return
            }
        }
        else {
            target(201)
            domove(10)
            return
        }
    }
}
if (self.varDC == 6){
    if (unknown(0x14) == 23){
        if (enemies.alive == 1){
            target(201)
            domove(3)
            return
        }
        else {
            target(201)
            domove(7)
            return
        }
    }
    else {
        target(201)
        domove(15)
        return
    }
}

Sorc1 death:
Code: [Select]
if (var63 >= 9){
    die()
    unknown33()
    launch(4)
}
else {
    if (var62 == 1){
        var62 = 0
        var63 += 1
        die()
        if (self.varDD == 3){
            if (var60 >= 6){
                var61 = 4
                unknown33()
                launch(1)
            }
            else {
                var61 = 6
                unknown33()
                launch(3)
            }
        }
        else {
            if (self.varDD == 6){
                if (var60 >= 6){
                    var61 = 5
                    unknown33()
                    launch(2)
                }
                else {
                    var61 = 3
                    unknown33()
                    launch(0)
                }
            }
        }
    }
    else {
        var62 = 1
        var63 += 1
        die()
    }
}

-----------------------------------------------------------------------------------

Sorc2 init:
Code: [Select]
var60 += 1
if (var62 == 0){
    var62 = 1
}
else {
    var62 = 2
}
target(200)
domove(1)
if (var60 == 7){
    self.varDC = 7
}
if (var60 == 8){
    self.varDC = 8
}
if (var60 == 9){
    self.varDC = 9
}
if (var60 == 10){
    self.varDC = 10
}
if (var61 == 4){
    var61 = 0
    self.varDD = 4
}
else {
    var61 = 0
    self.varDD = 5
}

Sorc2 turn:
Code: [Select]
if (var60 >= 10){
}
else {
    if (rand() % 2 == 0){
        if (var62 == 1){
            if (self.varDD == 4){
                var61 = 5
                unknown33()
                launch(2)
                return
            }
            else {
                if (self.varDD == 5){
                    var61 = 4
                    unknown33()
                    launch(1)
                    return
                }
            }
        }
    }
}
if (rand() % 3 == 0){
    return
}
if (enemies.alive == 1){
    if (rand() % 3 == 0){
        target(201)
        choose(4, 3, 15)
        return
    }
    else {
        return
    }
}
if (rand() % 3 == 0){
    target(201)
    domove(4)
    return
}
if (self.varDC == 7){
    if (self.status != 23){
        if (self.status != 33){
            if (rand() % 2 == 0){
                target(200)
                domove(11)
                return
            }
        }
    }
    if (self.status == 33){
        target(201)
        domove(3)
        if (enemies.alive != 0){
            target(201)
            domove(3)
        }
    }
    else {
        target(201)
        domove(3)
    }
    return
}
if (self.varDC == 8){
    if (rand() % 2 == 0){
        target(204)
        domove(13)
        return
    }
    else {
        target(201)
        domove(12)
        return
    }
}
if (self.varDC == 9){
    target(208)
    domove(8)
    return
}
if (self.varDC == 10){
    if (self.status != 23){
        if (self.status != 33){
            if (rand() % 2 == 0){
                target(200)
                domove(11)
                return
            }
        }
    }
    if (self.status == 33){
        target(201)
        domove(7)
        if (enemies.alive != 0){
            target(201)
            domove(7)
        }
    }
    else {
        target(201)
        domove(7)
    }
    return
}

Sorc2 death:
Code: [Select]
if (var63 >= 9){
    die()
    unknown33()
    launch(4)
}
else {
    if (var62 == 1){
        var62 = 0
        var63 += 1
        die()
        if (self.varDD == 4){
            var61 = 5
            unknown33()
            launch(2)
            return
        }
        else {
            if (self.varDD == 5){
                var61 = 4
                unknown33()
                launch(1)
                return
            }
        }
    }
    else {
        var62 = 1
        var63 += 1
        die()
    }
}

-----------------------------------------------------------------------------------

Sorc3 init:
Code: [Select]
self.varDC = 0
self.varDD = 0
target(200)
domove(1)
target(200)
domove(3)

Sorc3 turn:
Code: [Select]
if (self.varDC != 0){
    return
}
self.varDD += 1
if (self.varDD == 3){
    unknown18(0)
    self.varDE = 1
    target(200)
    domove(4)
}
else {
    if (self.varDD == 4){
        unknown18(1)
    }
    else {
        if (self.varDD == 5){
            unknown18(2)
            self.varDE = 1
            target(200)
            domove(5)
        }
        else {
            if (self.varDD == 6){
                unknown18(3)
            }
            else {
                if (self.varDD == 7){
                    unknown18(4)
                    self.varDE = 1
                    target(200)
                    domove(6)
                }
                else {
                    if (self.varDD >= 8){
                        self.varDD = 0
                        target(204)
                        domove(2)
                        self.varDE = 1
                        target(200)
                        domove(3)
                    }
                }
            }
        }
    }
}

Sorc 3 counter:
Code: [Select]
if (self.varDE == 1){
    self.varDE = 0
}
else {
    if (self.varDD <= 2){
        target(203)
        domove(0)
    }
}

Sorc3 death:
Code: [Select]
if (self.varDC == 0){
    self.varDC = 1
    target(200)
    domove(7)
    remove(200)
}

Sorc3 unknown:
Code: [Select]
if (self.varDC == 0){
    if (self.status == 0){
        unknown09(3)
    }
}

I make no claims as to the accuracy of the above code :P - I know some of the if statements aren't very clear on their meaning and I haven't bothered fixing it yet.
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-17 20:21:05
I've been toying around with the AI code some more and I've added a working compiler for my pseudocode :D.
I can now make bytecode but I still haven't injected it into the dat files yet as I'd need to patch some offsets in.
In addition to this I've added if/else parsing to turn code like this:

Code: [Select]
self.varDC = 0
self.varDD = 0
self.varDE = 0
if (character.alive == 3) {
    movesay(1)
    if (character.alive == 0) {
        movesay(6)
    }
    else {
        if (character.alive == 1) {
            movesay(8)
        }
        else {
            if (character.alive == 5) {
                movesay(9)
            }
            else {
                if (character.alive == 2) {
                    movesay(10)
                }
                else {
                    if (character.alive == 4) {
                        movesay(11)
                    }
                }
            }
        }
    }
}
else {
    if (character.alive == 1) {
        movesay(2)
        if (character.alive == 0) {
            movesay(6)
        }
        else {
            if (character.alive == 3) {
                movesay(7)
            }
            else {
                if (character.alive == 5) {
                    movesay(9)
                }
                else {
                    if (character.alive == 2) {
                        movesay(10)
                    }
                    else {
                        if (character.alive == 4) {
                            movesay(11)
                        }
                    }
                }
            }
        }
    }
    else {
        if (character.alive == 5) {
            movesay(3)
            if (character.alive == 0) {
                movesay(6)
            }
            else {
                if (character.alive == 3) {
                    movesay(7)
                }
                else {
                    if (character.alive == 1) {
                        movesay(8)
                    }
                    else {
                        if (character.alive == 2) {
                            movesay(10)
                        }
                        else {
                            if (character.alive == 4) {
                                movesay(11)
                            }
                        }
                    }
                }
            }
        }
        else {
            if (character.alive == 2) {
                movesay(4)
                if (character.alive == 0) {
                    movesay(6)
                }
                else {
                    if (character.alive == 3) {
                        movesay(7)
                    }
                    else {
                        if (character.alive == 1) {
                            movesay(8)
                        }
                        else {
                            if (character.alive == 5) {
                                movesay(9)
                            }
                            else {
                                if (character.alive == 4) {
                                    movesay(11)
                                }
                            }
                        }
                    }
                }
            }
            else {
                if (character.alive == 0) {
                    movesay(0)
                    if (character.alive == 3) {
                        movesay(7)
                    }
                    else {
                        if (character.alive == 1) {
                            movesay(8)
                        }
                        else {
                            if (character.alive == 5) {
                                movesay(9)
                            }
                            else {
                                if (character.alive == 2) {
                                    movesay(10)
                                }
                                else {
                                    if (character.alive == 4) {
                                        movesay(11)
                                    }
                                }
                            }
                        }
                    }
                }
                else {
                    movesay(5)
                    if (character.alive == 0) {
                        movesay(6)
                    }
                    else {
                        if (character.alive == 3) {
                            movesay(7)
                        }
                        else {
                            if (character.alive == 1) {
                                movesay(8)
                            }
                            else {
                                if (character.alive == 5) {
                                    movesay(9)
                                }
                                else {
                                    if (character.alive == 2) {
                                        movesay(10)
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
movesay(12)
movesay(13)
unknown1C(4)

into this:
Code: [Select]
self.varDC = 0
self.varDD = 0
self.varDE = 0
if (character.alive == 3) {
    movesay(1)
    if (character.alive == 0) {
        movesay(6)
    }
    else if (character.alive == 1) {
        movesay(8)
    }
    else if (character.alive == 5) {
        movesay(9)
    }
    else if (character.alive == 2) {
        movesay(10)
    }
    else if (character.alive == 4) {
        movesay(11)
    }
}
else if (character.alive == 1) {
    movesay(2)
    if (character.alive == 0) {
        movesay(6)
    }
    else if (character.alive == 3) {
        movesay(7)
    }
    else if (character.alive == 5) {
        movesay(9)
    }
    else if (character.alive == 2) {
        movesay(10)
    }
    else if (character.alive == 4) {
        movesay(11)
    }
}
else if (character.alive == 5) {
    movesay(3)
    if (character.alive == 0) {
        movesay(6)
    }
    else if (character.alive == 3) {
        movesay(7)
    }
    else if (character.alive == 1) {
        movesay(8)
    }
    else if (character.alive == 2) {
        movesay(10)
    }
    else if (character.alive == 4) {
        movesay(11)
    }
}
else if (character.alive == 2) {
    movesay(4)
    if (character.alive == 0) {
        movesay(6)
    }
    else if (character.alive == 3) {
        movesay(7)
    }
    else if (character.alive == 1) {
        movesay(8)
    }
    else if (character.alive == 5) {
        movesay(9)
    }
    else if (character.alive == 4) {
        movesay(11)
    }
}
else if (character.alive == 0) {
    movesay(0)
    if (character.alive == 3) {
        movesay(7)
    }
    else if (character.alive == 1) {
        movesay(8)
    }
    else if (character.alive == 5) {
        movesay(9)
    }
    else if (character.alive == 2) {
        movesay(10)
    }
    else if (character.alive == 4) {
        movesay(11)
    }
}
else {
    movesay(5)
    if (character.alive == 0) {
        movesay(6)
    }
    else if (character.alive == 3) {
        movesay(7)
    }
    else if (character.alive == 1) {
        movesay(8)
    }
    else if (character.alive == 5) {
        movesay(9)
    }
    else if (character.alive == 2) {
        movesay(10)
    }
}
movesay(12)
movesay(13)
unknown1C(4)

In addition to this, the editor now has better highlighting and line numbering due to the use of ScintillaNET.

I'm not really sure what to do with the tool though since it's tied into Ifrit at the moment.
I guess there's a few things I could do:
a) pull out the code and put it in a standalone application
b) speak to the person who wrote Ifrit and try to get it put into the main release
c) fork Ifrit (although I don't really want to do this one)

What do you guys think?
Title: Re: [FF8] Engine reverse engineering
Post by: Callisto on 2016-06-17 21:44:10
This sounds very promising.

Will your tool also allow editing the probability of attacks being used? For example, making the two GIM52As in Desert Prison use Ray Bomb more often while Elite Soldier is alive? That would be neat. As well as preventing enemies from mindlessly casting positive status effects over and over, such as Fujin does in LP.

As for what to do with the tool, I would suggest option b)
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-17 21:54:19
Yes, all of the probabilities can be changed, they are currently in the form of something like:
Code: [Select]
...
if (rand() % 3 == 0) {
...

which means random number modulo 3 - this gives a random number between 0 and 2
and the test is == 0, which has a 1/3 chance of being triggered.

You can change both the numbers and the test: e.g.

Code: [Select]
...
if (rand() % 4 < 3) {
...

gives a 3/4 chance of the code being executed.
You could then combine it with other tests to make it more likely in certain circumstances.

Thinking about it, I could change the syntax to something like [0..2] to make it easier to understand.

you can test to see if you have a status too, so you can easily stop enemies from casting the same spell over and over.

I might put in boolean expressions at some point, the main problem being that decompiling boolean or statements back from the byte code seems like it'd be a pain.
Title: Re: [FF8] Engine reverse engineering
Post by: Callisto on 2016-06-17 23:41:24
Hm, I think the current probability form is actually fine as it is. If improving readability causes too much pain, better save it for a later revision. For now, it would be great to just see an initial release soon :)
Title: Re: [FF8] Engine reverse engineering
Post by: JWP on 2016-06-18 11:24:27
Changing the rand syntax is a 2 second job, boolean expressions I'd definitely leave until a later release.
I did realize that I could make it a lot easier by encoding metadata using useless/illegal opcodes though since the game pretty much ignores them.
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2017-03-13 14:22:19
Input cracked!/Input hack/Create your own key input algorithms!

4685E0 - get_key_state (If you can't find it search for xrefs to "INPUT PARAMETER ERROR - get_key_state")
This function returns whenever *(1CD02D8+arg_0) is bigger than 0
Example: To test if user press '1' on keyboard then: bool bpressed = get_key_state(1) (bool because function returns 32bit register)
Now the trick you can use it:
4A2CA0 is the function that is called on every update on field, battle and world. It checks for CTRL+R combination to reset the game, and CTRL+Q combination to exit the game. Now imagine this scenario:

You inject to this function and check for your own specific combination. If the combination passes your code/unknown code section is played (for reversing sake or whatever you want). You can create inside-working trainer. Maybe put changing music on Numpad3 and Numpad4? Super easy to do. This really helps reversing stuff as I can call any script just by clicking desired button in-game.

Bad part is it doesn't work on menu module. Worry not, just xref the call to 01CD02D8 struct and find it there.

If you get_key_state(arg_0) where arg_0 > 255, then the famous "WILLIAM PLEASE CHECK YOUR PARAMETER" will appear. 

Just tested it in-game via assembly injection:
Code: [Select]
push 00000004
CALL ff8.exe+685e0 //get_key_state(4)
ADD esp, 04 //stack
TEST eax,eax
JNE passed //eax is positive

Turns out PUSH 00000004 means '3' instead of '4'
also I tried to write the music swap code, but it crashes after playing SdMusicPlay() function and it looks like the function is not loading desired AKAO or the module corrupts something. I'll do some more tests soon. Anyway, I can claim it working!
Title: Re: [FF8] Engine reverse engineering
Post by: Maki on 2017-03-17 18:21:41
Okay, here's the video example of the hack:
https://www.youtube.com/watch?v=IrvGKKEjVJM&feature=youtu.be

and also assembler source for music player trainer:
http://pastebin.com/YXFhuBGG

The issues I had earlier were caused by not clearing stack (ADD ESP, 04 or POP to EAX) and also remember, that EAX and EBX - NEEDS TO BE ZERO before you proceed!
Not doing XOR EBX, EBX will crash the game.