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

Maki

  • 0xBAADF00D
  • *
  • Posts: 624
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #50 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.
« Last Edit: 2016-06-02 20:37:30 by Handsome cat in the hat (MaKiPL) »

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #51 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.
« Last Edit: 2016-06-02 23:06:00 by JWP »

Maki

  • 0xBAADF00D
  • *
  • Posts: 624
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #52 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!

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #53 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.

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #54 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
« Last Edit: 2016-06-05 13:52:01 by JWP »

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #55 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.

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.
« Last Edit: 2016-06-21 23:31:10 by JWP »

Maki

  • 0xBAADF00D
  • *
  • Posts: 624
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #56 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...

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #57 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.
« Last Edit: 2016-06-14 22:54:04 by JWP »

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #58 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?

Callisto

  • *
  • Posts: 303
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #59 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)

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #60 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.
« Last Edit: 2016-06-17 22:47:33 by JWP »

Callisto

  • *
  • Posts: 303
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #61 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 :)

JWP

  • *
  • Posts: 194
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #62 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.

Maki

  • 0xBAADF00D
  • *
  • Posts: 624
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #63 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!
« Last Edit: 2017-03-13 17:00:31 by Maki »

Maki

  • 0xBAADF00D
  • *
  • Posts: 624
  • 0xCCCCCCCC
    • View Profile
Re: [FF8] Engine reverse engineering
« Reply #64 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.