Author Topic: FF7.exe battle routine questions  (Read 4933 times)

quantumpencil

  • *
  • Posts: 72
    • View Profile
FF7.exe battle routine questions
« on: 2018-12-07 04:01:10 »
I've been going through the exe battle routines and have a few questions/observations

1: The routine at 0x5D173F chooses the damage formula to call from a pointer table located at 0x8FF1F8. This same routine calls a sub 0x5DC880 which is quite daunting, but appears to be being passed a "0" and a "1" via the stack (as is standard with cdecl calls like this) and is called both before and after the damage formula pulled from the ability data in this routine is called via function pointer. Interestingly enough I can't see where this argument is used within the sub at 0x5dC880. Due to it's size I'm not totally sure what this sub does,  does but it seems to have some roll in preparing the battle context object pointed to in all damage formulas. Anyone made any substantial progress understanding this big guy?

2: The large data structure which is referenced throughout damage calculation and formula selection (and is pointed to by the pointer at 0x99CE0C) seems to contain a lot of information from my inspection, it contains at the very least attacker/target stats, ability data, and some attack flags like whether or not something crit. Has this data structure pointed to by 0x99CE0C been mapped by anyone in the modding community? I've got myself a little loader/dll injector for redirecting routines, but in order to make the redirected routines (and hopefully expose a C api for redirecting exe routines at runtime) I need a better understanding of this data structure -- it looks rather amorphous, I don't see evidence of a clear ability data struct or anything like that at the offsets which seem to correspond to ability flags (like context_ptr+ 48h, which is definitely ability power, or context_ptr + 6Ch which appears to some flags, one of which is "auto-critical")"

Thank you for your help =)

« Last Edit: 2018-12-07 18:03:40 by quantumpencil »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7.exe battle routine questions
« Reply #1 on: 2018-12-07 17:58:03 »
I can answer both questions for you in excruciating detail, but I can only answer the first one right now since I don't have access to my notes right now.

That method you're seeing before and after the damage routine call is the additional effects handler. It has to run before the damage calculation and after to be able to influence pre damage and post damage calculation manipulation when applicable. The 0 and 1 are handled in the individual effects and will only apply if the action has an effect assigned. I had some detailed notes on the wiki if those still exist on one of the new ones.

quantumpencil

  • *
  • Posts: 72
    • View Profile
Re: FF7.exe battle routine questions
« Reply #2 on: 2018-12-07 18:19:37 »
Fantastic! So that method corresponds to the "effects" that can be assigned to an ability (Multiple Hits, Fills allied limit guages, etc)? Or do you mean additional effects more broadly.

As for the data structure referenced for calculation, I've noticed that there also appears to be a separate array of actor data structs of size 68h stored around 0x9AB070 (I think it starts here, not sure because instead of offsetting from the beginning of the struct, things are offset from the particular thing being fetched in the first actors struct), which seems to have some stats that aren't accessible from what I'm calling the "context" pointer. Target Level, Actor Status Masks, etc are often gotten by taking target ID from the context ptr and doing 68*target ID byte offset math to fetch the relevant actor data.

Now these structs are only 0x68h in size as opposed to the AI script accessible actor blocks of 0x3ff (masked addresses of 4000 - 43ff) so they clearly aren't the AI accessible actor blocks, but they also seem to be affected by changes made to the AI blocks. Do you/anyone else happen to know where information from the AI script accessible actor blocks gets gathered up into these smaller engine accessible blocks?

Depending on how that consolidation happens, I think it may be possible to implement *actual* new status effects within the engine with a nice C API. If the unused actor data (4300 - 433f) is not already being saved (via an object copy) to these blocks, they can be resized (by just relocating the data at runtime) to include new masks for new status effects, that can then be handled by damage calc, or elemental modifer routines, and possibly display visual effects =p

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ASIDE: NFTC1 btw if you want to sync IDA files (In case you have a lot of stuff labeled in it), I've got a plugin for exporting/importing the IDA db to json format for version control purposes. I'm planning to host this json file on github for modders along side this routine replacement toolkit so it's easy for any information about the .exe currently known to be accessed by anyone.

UPDATE: I've successfully replaced the physical damage routine with a routine which always deals 1 damage, and the game runs fine with it. I'd like to have some #includes (read: big structs) for all known data structures used in battle so they can be referenced in C++ in a human readable way and not as offsets. As long as the function signatures match (well, the argument counts match) so that the stack is not corrupted, we can in theory replace any function without needing to do rewrites on the exe, or worry about space constraints.

In particular, I think it will be very easy to add new formulas, once I get passed the data protection issues, we can relocate the function ptr table somewhere else in VRAM with more space and then register new formulas by function_ptr.  I'll try this next while waiting for more info on data.
« Last Edit: 2018-12-07 20:27:13 by quantumpencil »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7.exe battle routine questions
« Reply #3 on: 2018-12-07 22:05:16 »
the struct you speak of that's only 68h in size...is actually 260h in size. There's a monstrous 512 alignment after that 68h that picks back up at 200h. That's where target stats exist and it can change per target. The first 68h stays consistent for the duration of the calculation.

I love the idea of an ida to json exporter. I'd happily apply it to my database. I've marked thousands of lines and don't even remember all the changes I've made. I've annotated nearly every routine used in accuracy/damage calculation and still have to hunt through them to find details.

quantumpencil

  • *
  • Posts: 72
    • View Profile
Re: FF7.exe battle routine questions
« Reply #4 on: 2018-12-08 18:48:04 »
PM'd you  about IDA-Sync =)

I'm confused:

1: Are you saying the total struct has size 0x260h or that each actor has a struct of size 0x260h? Also, as I often express myself poorly, a clarification: I'm not talking about the struct pointed to by the battle pointer in my last post (I was in the first post tho), I think that is a different struct (and that one is 260h, I think) but a separate array that is sometimes used to access target/actor data that isn't retreived from the battle pointer. Example here:

From Magic Hit formula (Intel syntax):
mov     edx, BattleContextPtr
mov     eax, [edx+208h]
imul    eax, 68h
xor     ecx, ecx
mov     cl, byte_9AB0E5[eax]

This is only getting the "target id" (an integer) from the battle context_ptr and then referencing an array of contiguous structs starting somewhere around 9AB0E5 (but that's not the real start, that's the "level" of the first actor) and then getting the level of the target by offsetting 0x9AB0E5 by target_id*68h

That is in fact a contiguous array of structs of size 68h right?
« Last Edit: 2018-12-08 18:53:51 by quantumpencil »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7.exe battle routine questions
« Reply #5 on: 2018-12-09 23:24:55 »
I see the confusion. I wasn't looking at my notes so I wasn't clear on what's going on. :P

The structure you are referring to is an array of 68h in size. The one I'm referring to is a pointer to an array that is 264h bytes in size. It does have a weird gap in its structure between index FF and 200. I'm not sure why, but it doesn't seem like those 256 byte are used.

What YOU are referring to translates to the battle variables 4000 - 42E0 for each actor (there are as many as 10, but only 9 are functionally used). Here's my notes on the structure. Some of the bytes (particularly toward the end) are unused. Some are so rarely used (possibly just once in the entirety of the game) I can't tell what they're for.

Code: [Select]
00000000 BattleVar_Actor struc ; (sizeof=0x68)   
00000000                                       
00000000 Statuses        dd ?
00000004 ActorFlags      dd ?                    ; base 2
00000008 Index           db ?
00000009 Level           db ?                    ; base 10
0000000A Unused_0        db ?
0000000B ElementDamage   db ?                    ; base 2
0000000C CharacterID     db ?
0000000D AtkPower        db ?                    ; base 10
0000000E MagPower        db ?                    ; base 10
0000000F PhysEvade       db ?
00000010 IdleAnimID      db ?
00000011 DamagedAnimID   db ?
00000012 BackDamage      db ?                    ; base 2
00000013 SizeScale       db ?
00000014 Dexterity       db ?
00000015 Luck            db ?
00000016 IdleAnimHolder  db ?                    ; base 2
00000017 LastCovered     db ?
00000018 LastTargets     dw ?                    ; base 2
0000001A PreviousAttacker dw ?
0000001C PreviousPhysAttacker dw ?
0000001E PreviousMagAttacker dw ?
00000020 PDef            dw ?
00000022 MDef            dw ?                    ; base 10
00000024 MyIndex         dw ?
00000026 AbsorbedElements dw ?
00000028 CurrentMP       dw ?
0000002A CurrentMMP      dw ?
0000002C CurrentHP       dd ?
00000030 CurrentMHP      dd ?
00000034 anonymous_1     dd ?
00000038 anonymous_2     dd ?
0000003C anonymous_3     dd ?
00000040 anonymous_4     dd ?
00000044 InitialStatuses dd ?
00000048 anonymous_5     dd ?
0000004C anonymous_6     db ?
0000004D MagEvade        db ?
0000004E Row             db ?
0000004F Camera?         db ?
00000050 GilStolen       dw ?
00000052 ItemStolen      dw ?
00000054 Unknown         dw ?
00000056 APValue         dw ?
00000058 GilValue        dd ?
0000005C EXPValue        dd ?
00000060 anonymous_8     db ?
00000061 anonymous_9     db ?
00000062 anonymous_10    db ?
00000063 anonymous_11    db ?
00000064 anonymous_12    db ?
00000065 anonymous_13    db ?
00000066 anonymous_14    db ?
00000067 anonymous_15    db ?
00000068 BattleVar_Actor ends

ergonomy_joe

  • *
  • Posts: 20
    • View Profile
Re: FF7.exe battle routine questions
« Reply #6 on: 2018-12-10 07:41:41 »
That is in fact a contiguous array of structs of size 68h right?

I think you're refering to an array of structs starting at 0x009AB0DC;
here is how I reverse-engineered it:
Code: [Select]
struct t_battle_local_68 {//size 0x68
int f_00;//[0x4000]//009AB0DC
int f_04;//[0x4020]//009AB0E0
char f_08;//[0x4040]//009AB0E4
unsigned char f_09;//[0x4048]//009AB0E5
char __0a[1];//009AB0E6
unsigned char f_0b;//[0x4058]//009AB0E7
unsigned char f_0c;//[0x4060]//009AB0E8
unsigned char f_0d;//[0x4068]//009AB0E9
unsigned char f_0e;//[0x4070]//009AB0EA
unsigned char f_0f;//[0x4078]//009AB0EB
char f_10;//[0x4080]//009AB0EC
unsigned char f_11;//[0x4088]//009AB0ED
unsigned char f_12;//[0x4090]//009AB0EE
unsigned char f_13;//[0x4098]//009AB0EF
unsigned char f_14;//[0x40a0]//009AB0F0
unsigned char f_15;//[0x40a8]//009AB0F1
unsigned char f_16;//[0x40b0]//009AB0F2
unsigned char f_17;//[0x40b8]//009AB0F3
unsigned short f_18;//[0x40c0]//009AB0F4
unsigned short f_1a;//[0x40d0]//009AB0F6
unsigned short f_1c;//[0x40e0]//009AB0F8
unsigned short f_1e;//[0x40f0]//009AB0FA
unsigned short f_20;//[0x4100]//009AB0FC
unsigned short f_22;//[0x4110]//009AB0FE
unsigned short f_24;//[0x4120]//009AB100
unsigned short f_26;//[0x4130]//009AB102
unsigned short f_28,f_2a;//MP//[0x4140][0x4150]//009AB104,009AB106
unsigned f_2c,f_30;//HP//[0x4160][0x4180]009AB108,009AB10C
char __34[0x10];//009AB110
int f_44;//[0x4220]//009AB120
char __48[4];//009AB124
unsigned char f_4c;//[0x4260]//009AB128
unsigned char f_4d;//[0x4268]//009AB129
unsigned char f_4e;//[0x4270]//009AB12A
unsigned char f_4f;//[0x4278]//009AB12B
unsigned short f_50;//[0x4280]//009AB12C
unsigned short f_52;//[0x4290]//009AB12E
unsigned short f_54;//[0x42a0]//009AB130
unsigned char f_56;//[0x42b0]//009AB132
char __57[1];//009AB133
int f_58;//[0x42c0]//009AB134
int f_5c;//[0x42e0]//009AB138
char __60[8];//009AB13C
};

The array is part of another structure: it starts at offset 0x3c of an object starting at 0x009AB0A0.
As you can see, there are a lot of field that I didn't make sense of yet.
But you can see HP at f_28/f_2a and MP at f_2c,f_30 for instance.

As for the function at 0x005DC880 here what it looks like transposed back to C:
Code: [Select]
const unsigned short D_007B7748[] = {
/*007B7748*/0x04,0x3C,0x04,0x20,
/*007B7750*/0x01,0x24,0x10,0x10,
/*007B7758*/0x04,0x02,0x02,0x02,
/*007B7760*/0x02,0x01,0x20,0x04,
/*007B7768*/0x24,0x10,0x10,0x04,
/*007B7770*/0x20,0x10,0x10,0x10,
/*007B7778*/0x30,0x10,0x20,0x10,
/*007B7780*/0x10,0x14,0x01,0x01,
/*007B7788*/0x01,0x01,0x01,0x18
};

int C_005DC880(int bp08) {
struct {
int bp_5c;
int bp_58;
int bp_54;
int bp_50;
int bp_4c;
int bp_48;
int bp_44;
int bp_40;
int bp_3c;
int bp_38;
int bp_34;
int bp_30;
int bp_2c;
int bp_28;
int bp_24;
int bp_20;
unsigned short bp_1c; char _p_1c[2];
int bp_18;
int bp_14;
struct t_temp_034 *bp_10;
int bp_0c;
int bp_08;
int bp_04;
}lolo;

lolo.bp_04 = 0;
if(D_0099CE0C->f_0bc == -1)
return lolo.bp_04;
if(D_007B7748[D_0099CE0C->f_0bc] & BIT(bp08)) {//else 005DD653
lolo.bp_14 = D_0099CE0C->f_000;
lolo.bp_10 = &(D_009A8B10.f_02a8[lolo.bp_14]);
switch(D_0099CE0C->f_0bc) {
case 0:
D_0099CE0C->f_0b0 = D_0099CE0C->f_0c0;
break;
case 1:
switch(bp08) {
case 2:
D_0099CE0C->f_090 |= 0x40000;
break;
case 3:
if(D_0099CE0C->f_230 & 0x80)
D_0099CE0C->f_218 |= 1;
break;
case 4:
if(D_0099CE0C->f_208 >= 4)
D_009A8B10.f_2228 |= BIT(D_0099CE0C->f_208);
break;
case 5:
if(D_0099CE0C->f_0e0 == 0) {
D_0099CE0C->f_0b4 ++;
D_0099CE0C->f_098 = 0x61;
C_00436C4B();//restore indexes D_009AEA9C & D_009AEAA0
C_005CAC07(D_0099CE0C->f_098);
}
break;
}//end switch
break;
case 2:
if(C_005C8BA1() < D_0099CE0C->f_0c0) {//Battle.random:get unsigned char?
D_0099CE0C->f_02c = 0x60;
D_0099CE0C->f_098 = D_0099CE0C->f_02c;
C_005CA807();//calls "CMD script_0C"
}
break;
case 3:
C_005DF95A();
break;
case 4:
lolo.bp_18 = (D_009AB0A0.f_03c[D_0099CE0C->f_208].f_04 & 0x80)?1:0;
lolo.bp_18 ^= D_0099CE0C->f_0c0;
D_0099CE0C->f_234 &= ~1;
D_0099CE0C->f_220 &= ~2;
if(lolo.bp_18 == 0)
D_0099CE0C->f_234 |= 1;
break;
case 5:
switch(bp08) {
case 2:
if((D_009A8748.f_008.f_10 & 4) == 0)
D_0099CE0C->f_0dc = 0x36;
break;
case 5:
D_009AB0A0.f_022 |= 8;
break;
}//end switch
break;
case 6:
if(D_0099CE0C->f_208 < 3) {//else 005DCC01
lolo.bp_1c = D_0099CE0C->f_004 * 0x14;
if(lolo.bp_1c > D_00DBFD38.dwGIL)
lolo.bp_1c = D_00DBFD38.dwGIL;
if(lolo.bp_1c && lolo.bp_1c + D_009AB0A0.f_03c[lolo.bp_14].f_50 < 0xffff) {//else 005DCC01
D_009AB0A0.f_03c[lolo.bp_14].f_50 += lolo.bp_1c;
D_00DBFD38.dwGIL -= lolo.bp_1c;
C_005C7D59(lolo.bp_14, 0x55, 1, &lolo.bp_1c);
}
}
if(D_0099CE0C->f_048 == 0) {
D_0099CE0C->f_218 |= 2;
D_0099CE0C->f_224 = D_009AB0A0.f_03c[D_0099CE0C->f_208].f_11;
}
break;
case 7:
if(D_009AB0A0.f_03c[lolo.bp_14].f_52 == (unsigned short)0xffff && D_0099CE0C->f_208 < 3)
C_00435139(0, lolo.bp_14, 0xb, 0);
if(D_0099CE0C->f_048 == 0) {
D_0099CE0C->f_218 |= 2;
D_0099CE0C->f_224 = D_009AB0A0.f_03c[D_0099CE0C->f_208].f_11;
}
break;
case 8:
D_0099CE0C->f_048 = C_005C8BDC(7);//Battle.random:get unsigned char(with max)?
D_0099CE0C->f_024 += D_0099CE0C->f_048;
D_0099CE0C->f_048 += 4;
D_0099CE0C->f_048 *= 8;
break;
case 9:
if(D_0099CE0C->f_004 == D_0099CE0C->f_254)
D_0099CE0C->f_214 *= 8;
break;
case 0xa:
C_005DFB5C();
break;
case 0xb:
C_005DFBEE();
break;
case 0xc:
C_005DFD5B();
break;
case 0xd:
if(1) {
lolo.bp_20 = 0;
lolo.bp_24 = 0;
for(lolo.bp_28 = 4; lolo.bp_28 < 0xa; lolo.bp_28 ++) {
if(D_0099CE0C->f_018 & BIT(lolo.bp_28)) {
lolo.bp_20 += D_009AB0A0.f_03c[lolo.bp_28].f_09;
lolo.bp_24 ++;
}
}
if(lolo.bp_24)
lolo.bp_20 /= lolo.bp_24;
D_0099CE0C->f_048 = lolo.bp_20;
}
break;
case 0xe:
for(lolo.bp_08 = 0; lolo.bp_08 < 3; lolo.bp_08 ++) {
if(D_009AB0A0.f_03c[lolo.bp_08].f_00 & 1) {
D_009AB0A0.f_03c[lolo.bp_08].f_00 &= ~1;
D_009AB0A0.f_03c[lolo.bp_08].f_2c = D_009AB0A0.f_03c[lolo.bp_08].f_30;
}
}
break;
case 0xf:
C_005DF460();
break;
case 0x10:
switch(bp08) {
case 2:
if(1) {
lolo.bp_2c = 0;
for(lolo.bp_08 = 0; lolo.bp_08 < 3; lolo.bp_08 ++) {
if(D_009AB0A0.f_03c[lolo.bp_08].f_08 != -1)
lolo.bp_2c ++;
}
if(lolo.bp_2c < 2)
D_0099CE0C->f_0dc = 0x50;
}
break;
case 5:
C_005DF5D7();
break;
}//end switch
break;
case 0x11:
if(D_0099CE0C->f_208 < 3) {
lolo.bp_30 = BIT(D_0099CE0C->f_208);
C_00435139(2, D_0099CE0C->f_208, 0x13, lolo.bp_30);
}
D_0099CE0C->f_224 = C_005CABBA();
break;
case 0x12:
if(1) {
lolo.bp_34 = BIT(D_0099CE0C->f_208);
if(D_0099CE0C->f_208 < 3)
C_00435139(2, D_0099CE0C->f_208, 0x13, lolo.bp_34);
D_009A8B10.f_2214 |= lolo.bp_34;
D_0099CE0C->f_224 = C_005CABBA();
}
break;
case 0x13:
if(1) {
D_0099CE0C->f_018 = 0;
D_0099CE0C->f_0b4 = 0;
lolo.bp_40 = D_009A8748.f_0ac[lolo.bp_14].f_00;
lolo.bp_38 = D_009A8B10.f_02a8[lolo.bp_14].f_00->f_22;
lolo.bp_3c = C_005C8684(lolo.bp_40, 0);
for(lolo.bp_08 = 0; lolo.bp_08 < 7; lolo.bp_08 ++) {
lolo.bp_44 = D_009A8748.f_16c[lolo.bp_08];
lolo.bp_48 = 0x7f;
for(lolo.bp_0c = 0; lolo.bp_0c < 0xc; lolo.bp_0c ++) {
if(lolo.bp_38 & BIT(lolo.bp_0c)) {
lolo.bp_48 = C_005C8684(lolo.bp_40, lolo.bp_0c);
lolo.bp_38 &= ~BIT(lolo.bp_0c);
break;
}
}
if(lolo.bp_44 && lolo.bp_48 - lolo.bp_3c < 7) {//else 005DD0FB
lolo.bp_48 -= lolo.bp_3c;
lolo.bp_48 += 0x62;
if(lolo.bp_44 > 1)
lolo.bp_48 |= 0x80;
C_005CAC07(lolo.bp_48);
D_0099CE0C->f_0b4 ++;
}
}
D_0099CE0C->f_090 |= 0x10000;
}
break;
case 0x14:
for(lolo.bp_08 = 0; lolo.bp_08 < 3; lolo.bp_08 ++) {
if(lolo.bp_14 != lolo.bp_08)
D_009A8B10.f_02a8[lolo.bp_08].f_08 = 0xff;
}//end for
break;
case 0x15:
C_005C7EE5(D_0099CE0C->f_208, D_0099CE0C->f_0c0 - 0x64, 0xf);
break;
case 0x16:
C_005C7EE5(lolo.bp_14, D_0099CE0C->f_0c0 - 0x64, 0x10);
break;
case 0x17:
C_005C7EE5(lolo.bp_14, D_0099CE0C->f_0c0 - 0x64, 1);
break;
case 0x18:
switch(bp08) {
case 4:
if(D_0099CE0C->f_208 >= 4)
D_009A8B10.f_2228 |= BIT(D_0099CE0C->f_208);
break;
case 5:
D_0099CE0C->f_0b4 ++;
D_0099CE0C->f_098 = D_0099CE0C->f_0c0;
D_0099CE0C->f_0ec = D_0099CE0C->f_018;
C_005CAC07(D_0099CE0C->f_098);
D_0099CE0C->f_090 |= 0x81000;
break;
}//end switch
break;
case 0x19:
if(C_005C7F60() && D_0099CE0C->f_208 < 3) {
D_0099CE0C->f_224 = (D_009AB0A0.f_03c[D_0099CE0C->f_208].f_04 & 0x40)?0x32:0x31;
D_009AB0A0.f_03c[D_0099CE0C->f_208].f_04 ^= 0x40;
}
break;
case 0x1a:
if(1) {
lolo.bp_4c = D_0099CE0C->f_018;
lolo.bp_50 = (lolo.bp_4c & 0xf)?0xf:0x3f0;
lolo.bp_54 = D_009A8748.f_14c & ~D_009A8748.f_15a;
lolo.bp_54 &= lolo.bp_50;
lolo.bp_54 &= ~lolo.bp_4c;
D_0099CE0C->f_0ec = lolo.bp_54;
D_0099CE0C->f_0b4 ++;
D_0099CE0C->f_098 = D_0099CE0C->f_0c0;
C_005CAC07(D_0099CE0C->f_098);
D_0099CE0C->f_090 |= 0x281000;
}
break;
case 0x1b:
if(D_0099CE0C->f_208 >= 4) {
lolo.bp_58 = BIT(D_0099CE0C->f_208);
D_009A8B10.f_2214 |= lolo.bp_58;
}
break;
case 0x1c:
C_005C7EE5(D_0099CE0C->f_208, D_0099CE0C->f_0c0 - 0x64, 0xc);
break;
case 0x1d:
switch(bp08) {
case 2:
D_0099CE0C->f_090 |= 0x1000000;
break;
case 4:
D_0099CE0C->f_224 = 0x33;
break;
}//end switch
break;
case 0x1e:
D_0099CE0C->f_048 = (D_0099CE0C->f_0c4 * 3 * D_009A8B10.f_0000[lolo.bp_14].f_3c) / D_009AB0A0.f_03c[lolo.bp_14].f_30 + 1;
break;
case 0x1f:
D_0099CE0C->f_048 = (D_0099CE0C->f_0c4 * 3 * D_009A8B10.f_0000[lolo.bp_14].f_3e) / D_009AB0A0.f_03c[lolo.bp_14].f_2a + 1;
break;
case 0x20:
C_005DFC52();
break;
case 0x21:
D_0099CE0C->f_048 = (lolo.bp_10->f_00->f_24 / 0x80) * D_0099CE0C->f_0c4 / 0x10 + 0xa;
break;
case 0x22:
D_0099CE0C->f_048 = ((lolo.bp_10->f_0a * (lolo.bp_10->f_05 + 1)) >> 4) * D_0099CE0C->f_0c4 / 0x10 + 1;
break;
case 0x23:
switch(bp08) {
case 3:
if(D_0099CE0C->f_230 & 0x80)
D_0099CE0C->f_218 |= 1;
break;
case 4:
if(D_0099CE0C->f_208 >= 4) {
lolo.bp_5c = BIT(D_0099CE0C->f_208);
D_009A8B10.f_2228 |= lolo.bp_5c;
D_009A8B10.f_0000[D_0099CE0C->f_208].f_29 |= 1;
D_009AB0A0.f_03c[D_0099CE0C->f_208].f_58 = 0;
}
break;
}//end switch
break;
}//end switch
}

return lolo.bp_04;
}

well... it needs a lot of work before making sense of it   :-p

quantumpencil

  • *
  • Posts: 72
    • View Profile
Re: FF7.exe battle routine questions
« Reply #7 on: 2018-12-10 14:48:47 »
Thanks for the help guys. I'm gonna see about resizing the 68h actor array/relocating it somewhere else in memory. As many size-locked references to this struct as there are in the codebase, I think it may end up being simpler to just make a new array and get the AI engine to write new status data (I'm using battle variable 4310 as a new status mask in my mod) or locate exactly where that byte is being accessed by the AI scripts and have the engine access it from there (I suspect that might actually be the end of this actor struct array).  Either way hopefully once I get it figured out I can add an AddNewStatus() function utility to the dll injector.

@NFITC1 or anyone else who knows, you said the battle local_var correspondence correspond to battle vars 4000-42E0; It looks to me like these structs *are* what the scripting engine accesses/modifies when an AI script writes to actor data (They seem to be updated as expected by AI scripts when I've run them in debug mode and watched the structs), but I want to make sure there is no rawer form that is copied into this struct somewhere.  (As it seems 4300 - 433F, which  I've used extensively in my mod, are actually included in this struct as well, that's probably not the case).

Also wow,  that additional effects method is as almost as intimidating in C as the x86 version =p.
« Last Edit: 2018-12-10 15:39:10 by quantumpencil »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7.exe battle routine questions
« Reply #8 on: 2018-12-10 20:13:19 »
That struct IS the "definitive" values for all things battle. It holds the considered HP values which get written to the save block at the end of each battle as well as which status each actor has, etc. To save on memory, redundancy is reduced as much as possible so these are the only "stable" values for each actor. When damage is calculated it will occasionally temporarily copy values to smaller parts, but the 4000+ battle addresses are what gets kept.

quantumpencil

  • *
  • Posts: 72
    • View Profile
Re: FF7.exe battle routine questions
« Reply #9 on: 2018-12-11 05:38:53 »
Alright, so it looks like referencing the unused variables at the end of those actor battlevariable blocks (the 68h array) in memory and applying extra damage modifiers based on their bit-state accomplishes the true elemental interaction modifiers I wanted. Current action elements are 0x44 in the "battle context struct" (the one with the large gap in between that has current ability data) and so that plus appropriate left/write shifts (which I stuck into the routine at 0x5DB593, which already handles elemental weakness/strength multiplication based on a different intermediate "current element interactions" variable set a previous sub (somewhere in 0x5DB593, though I haven't figured out exactly where) was successful.

Now I'm interested in the duration of  various expiring status effects and how the game handles that. I tried to do this through some truly ridiculous AI script (I used an invisible actor as a clock and kept localvars for every actor, and then checked the 'clock' actor every pre-action script, expiring the status after so many 'ticks' had passed.

Now that I've got routine replacement as an option, I'd prefer to see if these various effects could be made to expire the same way poison currently expires. I should be able to just malloc a new array of length 10 and the required size at runtime and use it as a place to hold any data needed (per actor) to handle the expiration. I'd like to see how the game currently expires its timed statuses, but I haven't had any luck finding the routines that manage/increment the clock or expire status effects (I think as long as I know where the game stores the in game clock, it should be possible to implement this logic). Anyone know where any of these (especially timed status effect expiration) live in the exe?
« Last Edit: 2018-12-11 05:48:14 by quantumpencil »

ergonomy_joe

  • *
  • Posts: 20
    • View Profile
Re: FF7.exe battle routine questions
« Reply #10 on: 2018-12-11 07:58:31 »
ok, so there is this function at 0x0043526A:
Code: [Select]
//counters:refresh(1)?
void C_0043526A() {
struct {
int dwDecr;//bp_40
int dwCurCounter;//bp_3c
int dwNewHP;//bp_38
int dwHPMax;//bp_34
int bp_30;
int dwHPIncr;//bp_2c
int dwCountIncr_1;//bp_28
int dwDecr_2_3;//bp_24
int dwCountIncr_0;//bp_20
int dwDecr_def;//bp_1c
int j;//bp_18
int i;//bp_14//local_5
int bp_10;//local_4
int *bp_0c;//local_3
int *bp_08;//local_2
int dwDecr_0;//bp_04//local_1
}lolo;

//-- --
lolo.bp_10 = D_009AE8A0;
D_009AE8A0 ^= 1;
lolo.bp_0c = D_009AE928[lolo.bp_10];
lolo.bp_08 = D_009AE978[lolo.bp_10];
//-- --
lolo.dwDecr_0 = D_009AE8D0[lolo.bp_10] >> 13;
D_009AE8D0[lolo.bp_10] &= 0x1fff;
//-- --
for(lolo.i = 0; lolo.i < 0xa; lolo.i ++) {
lolo.bp_30 = D_009A8B10.f_0000[lolo.i].f_06;
lolo.dwCountIncr_1 = lolo.bp_0c[lolo.i];
lolo.dwCountIncr_0 = lolo.bp_08[lolo.i];
//-- some counter --
D_009AE900[lolo.i] += lolo.dwCountIncr_1;
lolo.dwDecr_def = D_009AE900[lolo.i] >> 13;
D_009AE900[lolo.i] &= 0x1fff;
//-- some counter --
D_009AE8D8[lolo.i] += lolo.dwCountIncr_0;
lolo.dwDecr_2_3 = D_009AE8D8[lolo.i] >> 12;
D_009AE8D8[lolo.i] &= 0xfff;
//-- counter for HP recovery? --
D_009AE8A8[lolo.i] += lolo.dwCountIncr_1 * lolo.bp_30;
lolo.dwHPIncr = D_009AE8A8[lolo.i] >> 15;
D_009AE8A8[lolo.i] &= 0x7fff;
//-- --
lolo.bp_0c[lolo.i] = 0;
lolo.bp_08[lolo.i] = 0;
//-- --
if((D_009AB0A0.f_03c[lolo.i].f_00 & 1) == 0) {//else 00435485
lolo.dwNewHP = D_009AB0A0.f_03c[lolo.i].dwHP + lolo.dwHPIncr;
lolo.dwHPMax = D_009AB0A0.f_03c[lolo.i].dwHPMax;
if(lolo.dwNewHP < 0) {//else 00435468
D_009AB0A0.f_03c[lolo.i].f_00 |= 1;
C_005C89E2(lolo.i, lolo.i, 1);
C_005C8931();//execute "other" scripts
lolo.dwNewHP = 0;
//goto 00435476
} else if(lolo.dwNewHP > lolo.dwHPMax) {
lolo.dwNewHP = lolo.dwHPMax;
}
D_009AB0A0.f_03c[lolo.i].dwHP = lolo.dwNewHP;
}
//-- some other states(poison, ...) --
for(lolo.j = 0; lolo.j < 0x10; lolo.j ++) {
lolo.dwCurCounter = D_009A8B10.f_0000[lolo.i].f_10[lolo.j];
if(lolo.dwCurCounter) {//else 00435531
switch(lolo.j) {
case 0:
lolo.dwDecr = lolo.dwDecr_0;
break;
case 2:
case 3:
lolo.dwDecr = lolo.dwDecr_2_3;
break;
default:
lolo.dwDecr = lolo.dwDecr_def;
}//end switch
lolo.dwCurCounter -= lolo.dwDecr;
if(lolo.dwCurCounter < 0)
lolo.dwCurCounter = 0;
if(lolo.dwCurCounter == 0)
C_00435139(2, lolo.i, lolo.j, lolo.j);//...:add event?
D_009A8B10.f_0000[lolo.i].f_10[lolo.j] = lolo.dwCurCounter;
}
}//end for
//-- --
}//end for
}

From what I understand, it refreshes some counters and take their upper bits to refresh some more counters. But near the end of the function, we have "dwCurCounter", and if it reaches 0, a function at 0x00435139 is called with such parameters as: the actor id, and the event type, respectively "i" and "j" in our case (a value of 6 means poison). Function at 0x00435139 manages a list of events which are called by the function at 0x004351BD:
Code: [Select]
//...:add event?
void C_00435139(int bp08, int bp0c, int bp10, int bp14) {
int local_2;
struct t_battle_inner_4 *local_1;

local_2 = D_009AE890[bp08];
local_1 = &(D_009AE280[bp08][local_2]);
if(local_1->f_00 == (unsigned char)0xff) {
local_1->f_02 = bp14;
local_1->f_01 = bp10;
local_1->f_00 = bp0c;
D_009AE890[bp08] = C_004351A3(local_2);//make next id?
}
}

//make next id?
int C_004351A3(int bp08) {
bp08 ++;
bp08 &= 0x7f;

return bp08;
}

//...:flush events?
void C_004351BD(int bp08) {
struct {
void (**local_3)(int, int);
int local_2;
struct t_battle_inner_4 *local_1;
}lolo;

C_004351C3:
lolo.local_2 = D_009AE880[bp08];
lolo.local_1 = &(D_009AE280[bp08][lolo.local_2]);
if(lolo.local_1->f_00 != (unsigned char)0xff) {
lolo.local_3 = D_007C2AC0[bp08];
lolo.local_3[lolo.local_1->f_01](lolo.local_1->f_00, lolo.local_1->f_02);
lolo.local_1->f_00 = 0xff;
D_009AE880[bp08] = C_004351A3(lolo.local_2);//make next id?
goto C_004351C3;
}
}

In the case of poison, the callback is at 0x00434DB0:
Code: [Select]
//(callback)2_06 "poison hit"
void C_00434DB0(int bp08, int _p0c) {
if(D_009AB0A0.f_03c[bp08].f_00 & 8) {
D_009A8B10.f_0000[bp08].f_10[6] = 0xa;
C_00432687(bp08, 3, 0x23, 0, 0);//add battle command?
}
}

What does it do ?
It reloads a poison-specific counter D_009A8B10.f_0000[bp08].f_10[6], and creates an attack command that will take HP from the character actually poisonned.

A little complex I would say  :-/

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7.exe battle routine questions
« Reply #11 on: 2018-12-11 13:21:56 »
@ergonomy_joe

That's how poison works. It takes a delta of one of the actors' timers (I forget which one) and when it reaches a certain value it queues an action which inflicts poison damage to the poisoned. There are a lot of battle "events" that are mostly used by animations, but it can be used for almost anything.

quantumpencil

  • *
  • Posts: 72
    • View Profile
Re: FF7.exe battle routine questions
« Reply #12 on: 2018-12-13 01:01:08 »
Ok, so I got some time look into this. calling the sub (enqueue action?) called within the poison routine with the same arguments called there can be used to, unsurprisingly, re-create the poison effect in random situations.

enqueue_event(attacker_id, 3, 23, 0, 0) are pushed onto the stack prior to the call. The parameters are (as far as I'm able to tell):

attacker_id, some kind of queue priority, command index (poison has index 23h, which is capable of dealing damage, but I'm not sure where the data for these indexes are) attack index, and another 0 (I don't know what this one is for yet)

I have major need for this mechanic, as it is a much better way to implement, for instance, an overheated status (create an attack that does massive fire damage and enqueue it when the physical formula is used by an overheated actor).

So does anyone know what the last argument in the enqueue function is, and...
does anyone know how where the game is looking for the action attack metadata of poison (i.e, the fact that it uses formula 3, it's poison elemental, etc).

Thanks again guys =)

« Last Edit: 2018-12-13 19:33:47 by quantumpencil »

ergonomy_joe

  • *
  • Posts: 20
    • View Profile
Re: FF7.exe battle routine questions
« Reply #13 on: 2018-12-14 01:55:39 »
here is the detail of the function at 0x00432687:
Code: [Select]
//add battle command?
void C_00432687(int bp08, int bp0c, int bp10, int bp14, int bp18) {
struct t_battle_local_08_HHHH local_2;

local_2.f_00 = bp0c;
local_2.f_02 = bp08;
local_2.f_03 = bp10;
local_2.f_04 = bp14;
local_2.f_06 = bp18;
C_0043258A(&local_2);//queue battle command?
}

the details about "struct t_battle_local_08_HHHH" (sorry about the name) is already given in the wiki, at "Queued Actions"(although there seems to be a confusion with offsets 0,1 and 2). According to it, the last two parameters are respectively "target mask" and "action index". Not sure that helps though  :-\

quantumpencil

  • *
  • Posts: 72
    • View Profile
Re: FF7.exe battle routine questions
« Reply #14 on: 2018-12-14 06:33:45 »
Update: I've finished reversing most of the EventList and BattleActionQueue enqueue/pop logic (thanks ergonomy_joe =)), and while I still haven't totally figured out how the game is managing "time" --  which is the last piece to get dots working, I have thus far successfully:

Created a new mechanic for cloud where he has a chance to use double cut or combo an attack with death-blow (like added cut if the added cut were deathblow) after every physical attack, without materia (the first done by re-writing the attack index prior to the main command handling routine, and the second done by generating an event corresponding to "Deathblow on the current target" of priority 0 immediately after a "attack" from an actor identified as Cloud is issued.

Found the data that needs to be edited with to enable a class mod (There's a giant struct of player character data that holds enabled commands. By modifying it in BattleMain (after it is populated), you can pretty much give commands to characters by default without materia, i.e Yuffie can be given innate skills.  Similarly, you can disable access to certain commands, magic, summons or E-Skills in battle). Proved by giving Yuffie innate steal and no access to Deathblow, even if the materia is equipped. 

Replaced Sense with Provoke (Could probably do this without replacing Sense too, but it'd be harder): Enemies record the "provoker" mask in their unused AIVariables data block. If the provoke bit is set, then before any action, change the target mask to that AI stored value. If Sense is used, don't call the damage from the pointer table, but instead just set the bit associated with provoke  in the target, and store the actor mask in the targets AI block.

Once I figure out how the  game does all this clock magic, DoT affects will be next =D
« Last Edit: 2018-12-16 20:51:39 by quantumpencil »