Qhimm.com Forums
Miscellaneous Forums => Scripting and Reverse Engineering => Topic started by: ff7man on 2022-01-20 22:27:42
-
Does anyone know how the winning chocobos in the gold saucer chocobo betting are calculated or where to look?
Fuzzfingergaming made a video describing his process: https://www.youtube.com/watch?v=jXLHLeT12Ts
I made an html tool to calculate the chocobos using his formula, but it's not perfect.
https://www.youtube.com/watch?v=0EPUW8ckGwg
I know if you use save states on the chocobo betting win pre game screen you can return after the race and re-input the values to get the same result. So the results should be pre calculated based on stats and jockey color.
Would be nice to know the official equation that calculates the winners. I'm not sure where to go looking to find this however.
Anyone able to help?
*Future Me Here* Fuzzfingers method is 63.38% accurate, which really isn't bad. I learned a lot about the game, but there's really just too many random number lookups happening to do better without manipulating RNG. Which I did eventually figure out how to do on both PSX and PC builds https://ff7man.github.io/rng.html
-
Alright so this isn't my first reverse engineering rodeo, so I took a stab at trying to answer this myself, but I still need help.
I setup ff7 steam.
Using black_chocobo I created a save at the chocobo square.
Loading the betting screen there are visual indicators we can see: top speed, stamina, name, chocobo color, jockey color, sprinting, prizes, and gil
I'm guessing the real solution to solving this mystery involves these variables or chocobo stats we can't see.
Using Cheat Engine I searched the game for the value of my gil.
Right click the address -> and the money updates. Neat.
Looking for 1 byte and 2 byte variables of the speed and stamina leads me no where. Not sure why. Those values could be obfuscated somehow...
According to https://finalfantasy.fandom.com/wiki/Chocobo_(Final_Fantasy_VII) someone has reverse engineered the equation for how they calculate speed and stamina.
The displayed value for the speed is derived from Dash/34 and the value for stamina is actual stamina/10.
Searching for stamina*10 didn't retrieve any results in cheat engine and neither did searching for speed*34
I'm hoping if I can find those values in memory and then maybe I can see other data around it for the chocobos stats or a win order.
Taking a step back, and borrowing on the shoulders of giants I found the ff7 gears pdf.
I discovered the data for the minigames are not in the ff7_en.exe, but are rather loaded into memory when you start a minigame.
The data that gets loaded is stored in C:\ff7\data\minigame\chocobo.lgp . Using the lgp tool I can extra the archive and see a bunch of graphical assets and a large 1mb chocobo.wat file.
AFAIK no one has reverse engineered the wat files for minigames... well I at least haven't seen any tools or documentation.
Except maybe @ergonomy_joe who managed to reverse engineer the coaster game. Crazy. That's some next level skill. Kudos.
I found them on this forum and they claim to have done the same for all the minigames except condor - https://forums.qhimm.com/index.php?topic=16507.0
Anyone have a way to reach him? I can't send personal messages yet. Can someone give me privs to do so?
He made an unreleased tool afaik to extract some of the files in the wat back in 2016 https://magnetiktank.blogspot.com/2016/01/ff7s-chocobowat-having-fun-with-3d.html -> the link on geocities is long dead but archive.org has a copy.
https://magnetiktank.blogspot.com/2016/01/ff7-chocobowat.html
I know there are concerns of copyright with his code.
From what I can tell the wat file has a bunch of strings that read a bit like a binary and reference various elements of source code.
If I could just load the code in memory and pause execution and then find where I'm at, I wouldn't need to re the wat file to see the assembly it generates.
Instead of looking for stamina or speed values maybe I can look for the chocobo names. I searched for them in cheat engine, but didn't have any hits.
Then I recalled gears mentioned ff7 doesn't use standard ascii tables for text, but rather uses their own bastardized table.
The text for the chocobo name MIKE is actually 2D 29 2B 25. However searching for an int with those yields nothing... Weird. Prior intuition lead me to believe it might be endianness.
So, I searched for 4 bytes of 25 2B 29 2D and low and behold I found it :) Modifying the characters updates the text on the screen.
Viewing the memory region I can see each chocobo has about 164 bytes between their names.
Most of those bytes change radically once the game starts, so I doubt they are values for the static stamina or top speed. You can zero out most and it doesn't seem to make an impact on the game.
With the address I decided to set a breakpoint on memory write to the chocobo name. However, on loading the betting screen the debugger crashes.
This might be an issue with how minigames are loaded or anticheat, but I found a workaround by using a debugger option in cheat engine to use VEH breakpoints.
This pauses the game the exact instruction a write is called to the chocobo name, landing me exe+373306A.
Dumping the process memory with task manager and searching for the bytes in hxd brings me to the loaded code :) hazzah progress.
Even though we are attached with cheat engine. I can re-attach using IDA. With IDA attached I see the code starts at 401000 from the functions window. Adding the base address plus the position from cheat engine 373306A and we get 772AA3. Jumping to that address and enabling viewing opcodes I can see we are looking at the same piece of memory as cheat engine but now we have more tools. We are in the middle of a long code block in sub_772357, generating psuedocode for the function, and we can see numerous 0->6 for loops showing something is happening for each chocobo. This looks really promising.
I'm not the most familiar with C/asm, but a piece that sticks out to me are a set of ifs.
If a value is <40. Subtract from 40 and divide by 10
If a value is >40 but <60. Subtract it from 60 and divide by 5
If a value is >60 but <80 . Subtract it from 80 and divide it by 5.
If a value is >80. Subtract it from 110 and divide it by 5.
Then divide that value by 8 and save it
Being less than 110 looks oddly similar to the speed ranges.
Using the PSX version of the game and epsxe I can use save states and reload the betting screen quickly.
From that I noticed the stats in class C are:
73-87 -> 14 window
251-278 -> 17 window
and in class B are:
89-105 -> 16 window
300-329 -> 29 window
Using cheat engine I searched 110-speed/5 and 110-speed/5/8, but I didn't get any values that updated the screen...
There are a few /10's in the code perhaps they are operating on the dash value?
I'm not sure really where to go from here. I could use some tips from someone who knows how to RE better than I do.
__int16 sub_772357()
{
int v0; // eax
int v1; // ST88_4
int v2; // ST84_4
__int16 v3; // ST80_2
int v4; // eax
__int16 v5; // si
__int16 v6; // si
int v7; // eax
int v8; // esi
int v9; // eax
int v10; // eax
int v11; // ST8C_4
int v12; // ST78_4
int v13; // ST7C_4
int v14; // ST70_4
int v15; // ST74_4
int v16; // ST94_4
int v17; // ST90_4
char *v19; // [esp+40h] [ebp-134h]
signed int ii; // [esp+50h] [ebp-124h]
signed int jj; // [esp+50h] [ebp-124h]
char *v22; // [esp+5Ch] [ebp-118h]
__int16 i; // [esp+8Ch] [ebp-E8h]
__int16 j; // [esp+8Ch] [ebp-E8h]
__int16 k; // [esp+8Ch] [ebp-E8h]
__int16 n; // [esp+8Ch] [ebp-E8h]
__int16 kk; // [esp+8Ch] [ebp-E8h]
int l; // [esp+90h] [ebp-E4h]
signed int m; // [esp+90h] [ebp-E4h]
__int16 *v30; // [esp+98h] [ebp-DCh]
int v31; // [esp+9Ch] [ebp-D8h]
int v32; // [esp+A0h] [ebp-D4h]
int v33; // [esp+A4h] [ebp-D0h]
int v34; // [esp+ACh] [ebp-C8h]
int v35[45]; // [esp+B0h] [ebp-C4h]
float v36; // [esp+164h] [ebp-10h]
float v37; // [esp+168h] [ebp-Ch]
float v38; // [esp+16Ch] [ebp-8h]
int v39; // [esp+170h] [ebp-4h]
v0 = sub_676578();
v34 = v0;
for ( i = 0; i < 6; ++i )
{
word_E71158[82 * i + 62] = 224 * i / 6 + 16;
LOWORD(v0) = i + 1;
}
for ( j = 0; j < 40; ++j )
{
v1 = sub_7AE9C0() % 6;
v2 = sub_7AE9C0() % 6;
v3 = word_E711D4[82 * v1];
word_E711D4[82 * v1] = word_E711D4[82 * v2];
word_E711D4[82 * v2] = v3;
LOWORD(v0) = j + 1;
}
for ( k = 0; k < 6; ++k )
{
v30 = &word_E71158[82 * k];
if ( !dword_E71128 || k )
{
v5 = word_97A3E0[4 * (unsigned __int8)byte_DC0AF3];
v30[44] = sub_7AE9C0() % word_97A3E2[4 * (unsigned __int8)byte_DC0AF3] + v5;
v6 = word_97A3E4[4 * (unsigned __int8)byte_DC0AF3];
v30[43] = sub_7AE9C0() % word_97A3E6[4 * (unsigned __int8)byte_DC0AF3] + v6;
v30[47] = 50;
v30[49] = 100;
v7 = sub_7AE9C0();
v30[3] = ((v7 >> 31) ^ abs((_BYTE)v7) & 3) - (v7 >> 31);
v8 = word_97A3E0[4 * (unsigned __int8)byte_DC0AF3];
*((_DWORD *)v30 + 27) = sub_7AE9C0() % 300 + v8;
*((_DWORD *)v30 + 26) = *((_DWORD *)v30 + 27);
v30[48] = 1;
v9 = -((sub_7AE9C0() & 7) != 0);
LOBYTE(v9) = v9 & 0xFE;
v30[38] = v9 + 2;
v30[46] = v30[50] / 10;
v30[67] = 3;
v30[50] = 50 * (sub_7AE9C0() & 1) + 50;
}
else
{
v30[44] = ((unsigned __int8)byte_DC0AE7 << 8) + (unsigned __int8)byte_DC0AE6;
v30[43] = ((unsigned __int8)byte_DC0AE9 << 8) + (unsigned __int8)byte_DC0AE8;
v30[47] = (unsigned __int8)byte_DC0AEA;
v30[49] = (unsigned __int8)byte_DC0AEB;
v4 = sub_7AE9C0();
v30[3] = ((v4 >> 31) ^ abs((_BYTE)v4) & 3) - (v4 >> 31);
*((_DWORD *)v30 + 27) = ((unsigned __int8)byte_DC0AF7 << 8) + (unsigned __int8)byte_DC0AF6;
*((_DWORD *)v30 + 26) = *((_DWORD *)v30 + 27);
v30[48] = 1;
v30[38] = (unsigned __int8)byte_DC0AEE;
v30[46] = v30[50] / 10;
switch ( byte_DC0AED )
{
case 1:
v30[67] = 1;
break;
case 2:
v30[67] = 2;
break;
case 3:
case 4:
v30[67] = 0;
break;
default:
v30[67] = 3;
break;
}
v30[50] = (unsigned __int8)byte_DC0AEC;
}
if ( v30[50] >= 40 )
{
if ( v30[50] >= 60 )
{
if ( v30[50] >= 80 )
{
v30[41] = 0;
v10 = (110 - v30[50]) / 5;
}
else
{
v30[41] = 1;
v10 = (80 - v30[50]) / 5;
}
v30[51] = v10;
}
else
{
v30[41] = 2;
v30[51] = (60 - v30[50]) / 5;
}
}
else
{
v30[41] = 3;
v30[51] = (40 - v30[50]) / 10;
}
v30[46] = v30[50] / 8;
v30[76] = *((_WORD *)dword_E7112C + 2);
v30[65] = k;
v30[2] = v30[43] / 2;
v30[39] = v30[43];
*v30 = 10;
v30[1] = 11;
v30[66] = 0;
v30[77] = -1;
v30[80] = 0;
v30[79] = 0;
v30[78] = 0;
v30[81] = 0;
v11 = 256 - v30[62];
v30[75] = v30[62];
v30[8] = (v11 * *(signed __int16 *)(dword_E715FC + 24 * *v30 + 8)
+ v30[62] * *(signed __int16 *)(dword_E715FC + 24 * *v30))
/ 256;
v30[9] = (v11 * *(signed __int16 *)(dword_E715FC + 24 * *v30 + 10)
+ v30[62] * *(signed __int16 *)(dword_E715FC + 24 * *v30 + 2))
/ 256;
v30[10] = (v11 * *(signed __int16 *)(dword_E715FC + 24 * *v30 + 12)
+ v30[62] * *(signed __int16 *)(dword_E715FC + 24 * *v30 + 4))
/ 256;
v30[32] = v30[8];
v30[33] = v30[9];
v30[34] = v30[10];
v30[20] = (v11 * *(signed __int16 *)(dword_E715FC + 24 * v30[1] + 8)
+ v30[62] * *(signed __int16 *)(dword_E715FC + 24 * v30[1]))
/ 256;
v30[21] = (v11 * *(signed __int16 *)(dword_E715FC + 24 * v30[1] + 10)
+ v30[62] * *(signed __int16 *)(dword_E715FC + 24 * v30[1] + 2))
/ 256;
v30[22] = (v11 * *(signed __int16 *)(dword_E715FC + 24 * v30[1] + 12)
+ v30[62] * *(signed __int16 *)(dword_E715FC + 24 * v30[1] + 4))
/ 256;
v30[24] = 0;
v30[25] = 0;
v30[26] = 0;
v30[28] = 0;
v30[29] = 0;
v30[30] = 0;
v30[25] = sub_77CFBF(dword_E715FC + 49152, dword_E715FC + 49176);
v30[56] = 0;
v30[57] = 0;
v30[59] = v30[2] / dword_E7101C;
v30[63] = 0;
v30[64] = 0;
v31 = v30[20] - v30[8];
v32 = v30[21] - v30[9];
v33 = v30[22] - v30[10];
sub_6638B0(&v31, &v31);
v30[29] = sub_77CF40(v31, v33);
v30[25] = v30[29];
LOWORD(v0) = k + 1;
}
if ( byte_DC0AF2 )
{
v12 = dword_E7120C;
v13 = dword_E71210;
v14 = dword_E71224;
v15 = dword_E71228;
qmemcpy(&unk_E711FC, word_E71158, 0xA4u);
dword_E7120C = v12;
dword_E71210 = v13;
dword_E7123C = v12;
dword_E71240 = v13;
dword_E71224 = v14;
dword_E71228 = v15;
dword_E71268 += dword_E71268 / 4;
dword_E71264 += dword_E71264 / 4;
word_E71252 += word_E71252 / 10;
LOWORD(v0) = word_E71254 / 10;
word_E71254 += word_E71254 / 10;
word_E7125A = 100;
word_E71260 = 100;
word_E71202 = 3;
word_E71282 = 0;
word_E7128C = 9;
word_E7128E = 1;
}
for ( l = 0; l < 45; ++l )
{
v35[l] = l;
LOWORD(v0) = l + 1;
}
for ( m = 0; m < 200; ++m )
{
v16 = sub_7AE9C0() % 45;
v0 = sub_7AE9C0();
v17 = v35[v16];
v35[v16] = v35[v0 % 45];
v35[v0 % 45] = v17;
LOWORD(v0) = m + 1;
}
for ( n = 0; n < 6; ++n )
{
if ( byte_DC0AF2 && n == 1 )
{
for ( ii = 0; ii < 8; ++ii )
*((_BYTE *)&dword_E711E0[41 * n] + ii) = byte_97C8A8[ii];
}
else
{
v22 = (char *)&unk_97CC58 + 7 * v35[n];
for ( jj = 0; jj < 6; ++jj )
*((_BYTE *)&dword_E711E0[41 * n] + jj) = *v22++;
*((_BYTE *)&dword_E711E0[41 * n] + jj) = -1;
if ( dword_E71128 && n )
sub_773487(n, 0, 0);
}
LOWORD(v0) = n + 1;
}
if ( dword_E71128 )
{
dword_E711E0[0] = dword_DC0ADC;
word_E711E4 = word_DC0AE0;
byte_E711E6 = -1;
}
for ( kk = 0; kk < 6; ++kk )
{
v19 = (char *)dword_E719E8 + 60 * word_E71158[82 * kk + 72];
if ( (!byte_DC0AF2 || kk != 1) && (!dword_E71128 || kk) )
{
switch ( word_E71158[82 * kk + 3] )
{
case 0:
v19[8] = 0;
v19[7] = 0;
v19[6] = 0;
v38 = (double)(unsigned __int8)v19[6] / 256.0;
v37 = (double)(unsigned __int8)v19[7] / 256.0;
v36 = (double)(unsigned __int8)v19[8] / 256.0;
v39 = 1065353216;
sub_684E20((int)&v36, *(_DWORD *)(*((_DWORD *)v19 + 13) + 180));
break;
case 1:
v19[6] = 64;
v19[7] = 64;
v19[8] = 0;
v38 = (double)(unsigned __int8)v19[6] / 256.0;
v37 = (double)(unsigned __int8)v19[7] / 256.0;
v36 = (double)(unsigned __int8)v19[8] / 256.0;
v39 = 1065353216;
sub_684E20((int)&v36, *(_DWORD *)(*((_DWORD *)v19 + 13) + 180));
break;
case 2:
v19[6] = 64;
v19[7] = 16;
v19[8] = 0;
v38 = (double)(unsigned __int8)v19[6] / 256.0;
v37 = (double)(unsigned __int8)v19[7] / 256.0;
v36 = (double)(unsigned __int8)v19[8] / 256.0;
v39 = 1065353216;
sub_684E20((int)&v36, *(_DWORD *)(*((_DWORD *)v19 + 13) + 180));
break;
case 3:
v19[6] = 0;
v19[7] = 16;
v19[8] = 64;
v38 = (double)(unsigned __int8)v19[6] / 256.0;
v37 = (double)(unsigned __int8)v19[7] / 256.0;
v36 = (double)(unsigned __int8)v19[8] / 256.0;
v39 = 1065353216;
sub_684E20((int)&v36, *(_DWORD *)(*((_DWORD *)v19 + 13) + 180));
break;
default:
break;
}
}
LOWORD(v0) = kk + 1;
}
return v0;
}
Using black chocobo I created a chocobo with set values and that helped a bit.
Taking a look deeper look at the name in memory. The name is actually the trailing piece of data.
Each chocobo has 164 bytes of memory.
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 0A 00 0B 00 BC 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A
00 23 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 40 FA 0A 00 24 DD 00 00 00 00 00 04 00 00 00 00 00 00 00
04 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 78
07 00 00 00 00 00 00 78 07 40 08 00 00 0A 00 51 00 01 00 52
00 53 00 05 00 3D 10 00 00 3D 10 00 00 00 00 00 00 00 00 9F
00 00 00 00 00 A5 00 00 00 00 00 00 00 00 00 03 00 23 48 4F
43 4F 12 FF
Name = Choco2
Top Speed = 62 (-48 bytes /34)
Stamina = 415 (-28 bytes and -32 bytes /10)
Acceleration = 81 (-42 bytes)
Cooperation = 82 (-38 bytes)
Stamina = 4157 (-28 bytes and -32 bytes)
Intelligence = 83 (-36 bytes)
Run speed = x778 = 1912/3323 -> (-50 bytes)
Sprint Speed = x840 = 2112 /3523 (-48 bytes)
With this I am able to modify the speed/stamina values.
The second chocobo:
00 06 00 00 00 1E 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00
00 0A 00 0B 00 61 03 02 00 00 00 00 00 00 00 00 00 E7 F9 0A
00 10 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 41 FA 0A 00 11 DF 00 00 00 00 00 04 00 00 00 00 00 00 00
04 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 C3
06 00 00 02 00 00 00 C3 06 E5 09 00 00 06 00 32 00 01 00 64
00 32 00 02 00 0A 0A 00 00 0A 0A 00 00 00 00 00 00 00 00 90
00 00 00 00 00 35 00 00 00 00 00 01 00 00 00 03 00 39 2F 35
2E 27 FF FF
74,257,YOUNG,bronze
-28 Bytes = Stamina = x0a0a/10=257
-36 Bytes = Intelligence = 32
-38 Bytes = Cooperation = 64
-42 Bytes = Acceleration = 32
-48 Bytes = TS x9e5/34 = 74
-50 Bytes = Run speed = x6c3 = 1731
-40 Bytes = 1 = UNK
-44 Bytes = 6 = UNK
According to the psuedo code and validated with the values intel/coop/acceleration are either 32, or 64 for ai.
Here's a quick script to parse a blob of memory to a csv
def decodeMe(myin):
a =""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
res = ""
smy = myin.split(" ")
for val in smy:
dec = int(val, 16)
if dec >= len(a):
if dec != 255:
print("replacing char: "+val+ " with space")
dec = 0
res += a[dec]
return(res)
def parse(thebytes):
thebytes = thebytes.split()
nameVal = decodeMe(" ".join(thebytes[157:]))
nameVal = nameVal.rstrip()
stamina = thebytes[130]+thebytes[129]
dec = int(stamina, 16)
staminaVal = "0x"+stamina+"/10 = "+str(dec/10)
stamina2 = thebytes[126]+thebytes[125]
dec = int(stamina2, 16)
stamina2Val = "0x"+stamina2+"/10 = "+str(dec/10)
unk1Val = thebytes[123]
intelVal = thebytes[121]
coopVal = thebytes[119]
unk2Val = thebytes[117]
accVal = thebytes[115]
unk3Val = thebytes[113]
speed = thebytes[110]+thebytes[109]
dec = int(speed, 16)
speedVal = "0x"+speed+"/34 = "+str(dec/34)
rs = thebytes[108]+thebytes[107]
dec = int(rs, 16)
runSpeedVal = "0x"+rs+" = "+str(dec)
unk4Val = thebytes[103]
rs = thebytes[100]+thebytes[99]
dec = int(rs, 16)
runSpeed2Val = "0x"+rs+" = "+str(dec)
first = " ".join(thebytes[0:91])
first = first.replace("00 00","")
first = first.replace(" "," ")
otherVal = first
return(",,"+nameVal+","+staminaVal+","+stamina2Val+","+intelVal+","+coopVal+","+accVal+","+speedVal+","+runSpeedVal+","+runSpeed2Val+","+unk1Val+","+unk2Val+","+unk3Val+","+unk4Val+","+otherVal+",")
def main():
allbytes = input("Please paste the space separated bytes, \nstarting with FF FF FF FF 00 00 00 00 FF FF FF FF and ending with FF FF FF FF: ")
print("Parsing: "+str(len(allbytes)))
if len(allbytes) != 3623:
print("Wrong size, expecting 3623 bytes")
input("Press any key to continue")
sbytes = allbytes[105:]
add = "data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other,\n"
for i in [1, 2, 3, 4, 5, 6]:
tbytes = sbytes[0:492]
print("\n"+tbytes)
add += parse(tbytes) +"\n"
sbytes = sbytes[492:]
with open("out.csv","a+") as f:
f.write(add)
print("done -> results appended to out.csv")
main()
I generated some csv collecting data from 10 runs.
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
364512,,Choco2,0x103D/10 = 415.7,0x103D/10 = 415.7,53,52,51,0x0840/34 = 62.11764705882353,0x0778 = 1912,0x0778 = 1912,5,1,0A,0,00 0A 00 0B 00 BC 03 03 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,bronze,YOUNG,0x0A0A/10 = 257.0,0x0A0A/10 = 257.0,32,64,32,0x09E5/34 = 74.5,0x06C3 = 1731,0x06C3 = 1731,2,1,6,2,00 06 00 1E 00 A5 00 20 03 FF FF 0A 00 0B 00 61 03 02 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,plat,SANDY,0x0ABF/10 = 275.1,0x0ABF/10 = 275.1,64,64,32,0x0B00/34 = 82.82352941176471,0x0726 = 1830,0x0726 = 1830,2,1,0C,0,00 0B 00 01 00 35 00 20 03 FF FF 0A 00 0B 00 93 03 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
,plat,JOEL,0x09EC/10 = 254.0,0x09EC/10 = 254.0,32,64,32,0x0A80/34 = 79.05882352941177,0x0736 = 1846,0x0736 = 1846,2,1,6,2,00 0C 00 02 00 10 00 20 03 FF FF 0A 00 0B 00 9B 03 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,gold,JU,0x0A97/10 = 271.1,0x0A97/10 = 271.1,32,64,32,0x0A62/34 = 78.17647058823529,0x073A = 1850,0x073A = 1850,2,1,6,2,00 0D 00 03 00 CA 00 20 03 FF FF 0A 00 0B 00 9D 03 01 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
sprinting,silver,SARA,0x0ACF/10 = 276.7,0x0ACF/10 = 276.7,64,64,32,0x0A1B/34 = 76.08823529411765,0x075F = 1887,0x075F = 1887,2,1,0C,0,00 0E 00 04 00 80 00 20 03 FF FF 0A 00 0B 00 AF 03 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
352164,silver,FOX,0x0AA7/10 = 272.7,0x0AA7/10 = 272.7,32,64,32,0x0A5E/34 = 78.05882352941177,0x073D = 1853,0x073D = 1853,2,1,6,2,00 0A 00 0B 00 9E 03 03 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,plat,JOHN,0x0AC9/10 = 276.1,0x0AC9/10 = 276.1,32,64,32,0x0B34/34 = 84.3529411764706,0x072A = 1834,0x072A = 1834,2,1,6,2,00 0A 00 1E 00 CA 00 20 03 FF FF 0A 00 0B 00 95 03 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,gold,SEAN,0x0A00/10 = 256.0,0x0A00/10 = 256.0,64,64,32,0x0AA9/34 = 80.26470588235294,0x06E7 = 1767,0x06E7 = 1767,2,1,0C,0,00 0B 00 01 00 5A 00 20 03 FF FF 0A 00 0B 00 73 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,bronze,TOM,0x09F9/10 = 255.3,0x09F9/10 = 255.3,32,64,32,0x0A2D/34 = 76.61764705882354,0x0715 = 1813,0x0715 = 1813,2,1,6,2,00 0C 00 02 00 A5 00 20 03 FF FF 0A 00 0B 00 8A 03 02 00 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
sprinting,gold,PAULA,0x0A21/10 = 259.3,0x0A21/10 = 259.3,32,64,32,0x0B38/34 = 84.47058823529412,0x0752 = 1874,0x0752 = 1874,2,1,6,2,00 0D 00 03 00 10 00 20 03 FF FF 0A 00 0B 00 A9 03 01 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,bronze,JAMES,0x09ED/10 = 254.1,0x09ED/10 = 254.1,64,64,32,0x0B0B/34 = 83.1470588235294,0x06D3 = 1747,0x06D3 = 1747,2,1,0C,0,00 0E 00 04 00 80 00 20 03 FF FF 0A 00 0B 00 69 03 02 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
145326,silver,HARVEY,0x0A69/10 = 266.5,0x0A69/10 = 266.5,64,64,32,0x0B17/34 = 83.5,0x0731 = 1841,0x0731 = 1841,2,1,0C,0,00 0A 00 0B 00 98 03 03 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,silver,DARIO,0x09DA/10 = 252.2,0x09DA/10 = 252.2,64,64,32,0x0A8A/34 = 79.3529411764706,0x06B5 = 1717,0x06B5 = 1717,2,1,0C,0,00 0A 00 1E 00 35 00 20 03 FF FF 0A 00 0B 00 5A 03 03 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,silver,JENNY,0x0ADD/10 = 278.1,0x0ADD/10 = 278.1,32,64,32,0x0B2E/34 = 84.17647058823529,0x06BD = 1725,0x06BD = 1725,2,1,6,2,00 0B 00 01 00 A5 00 20 03 FF FF 0A 00 0B 00 5E 03 03 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,plat,SAM,0x0A9C/10 = 271.6,0x0A9C/10 = 271.6,32,64,32,0x0BA4/34 = 87.6470588235294,0x06D2 = 1746,0x06D2 = 1746,2,1,6,2,00 0C 00 02 00 5A 00 20 03 FF FF 0A 00 0B 00 69 03 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,silver,LIA,0x09F5/10 = 254.9,0x09F5/10 = 254.9,64,64,32,0x0A10/34 = 75.76470588235294,0x074E = 1870,0x074E = 1870,2,1,0C,0,00 0D 00 03 00 CA 00 20 03 FF FF 0A 00 0B 00 A7 03 03 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,plat,JOHN,0x09DB/10 = 252.3,0x09DB/10 = 252.3,64,64,32,0x09D9/34 = 74.1470588235294,0x06CF = 1743,0x06CF = 1743,2,1,0C,0,00 0E 00 04 00 80 00 20 03 FF FF 0A 00 0B 00 67 03 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
452613,silver,RICA,0x14A8/10 = 528.8,0x0B3A/10 = 287.4,32,64,32,0x0A11/34 = 75.79411764705883,0x0754 = 1876,0x0754 = 1876,2,1,6,2,00 FE 00 14 01 04 01 F2 00 E1 00 A8 03 A9 03 03 00 09 00 BF F3 09 00 B4 DD 19 07 1D 03 E6 F9 0A 00 C6 DD 28 04 3D 04 E6 F9 0A 00 C6 DD
,plat,AIMEE,0x1552/10 = 545.8,0x0BD4/10 = 302.8,64,64,32,0x0ACE/34 = 81.3529411764706,0x06F8 = 1784,0x06F8 = 1784,2,1,0C,0,00 0A 00 C0 FD 80 00 20 03 FF FF A9 03 AA 03 09 00 22 F4 09 00 46 DD 88 0A 00 80 51 07 00 80 3F F4 0A 00 1F DF 64 06 20 07 E6 F9 0A 00 23 DD
,bronze,JENNY,0x1438/10 = 517.6,0x05C4/10 = 147.6,64,64,32,0x0B0F/34 = 83.26470588235294,0x072B = 1835,0x057F = 1407,2,1,0C,0,00 0B 00 01 00 A5 00 20 03 FF FF A8 03 A9 03 0A 00 02 00 09 00 99 F3 09 00 0E DD 7C 07 07 08 E7 F9 0A 00 B2 DF 6C 04 6F 04 E7 F9 0A 00 B2 DF
,silver,GREY,0x158E/10 = 551.8,0x0B4A/10 = 289.0,64,64,32,0x0B02/34 = 82.88235294117646,0x0744 = 1860,0x0744 = 1860,2,1,0C,0,00 0C 00 02 00 10 00 20 03 FF FF AC 03 AD 03 03 00 09 00 1B F5 09 00 8B DC 01 09 5A 0D 4D F5 0A 00 3D DC A4 02 B0 02 E6 F9 0A 00 81 DC
,silver,SANDY,0x15A2/10 = 553.8,0x0B94/10 = 296.4,32,64,32,0x0B58/34 = 85.41176470588235,0x0738 = 1848,0x0738 = 1848,2,1,6,2,00 0D 00 03 00 CA 00 20 03 FF FF AB 03 AC 03 03 00 09 00 DE F4 09 00 22 DD 74 01 00 80 DD 09 F3 F4 0A 00 3D DC 53 01 4F 01 E7 F9 0A 00 6D DE
sprinting,gold,DAN,0x14FE/10 = 537.4,0x0013/10 = 1.9,32,64,32,0x09CE/34 = 73.82352941176471,0x070C = 1804,0x070C = 1804,2,1,6,2,00 0E 00 04 00 5A 00 20 03 FF FF AA 03 AB 03 01 00 09 00 50 F4 09 00 DE DD C6 03 7E 06 99 F4 0A 00 3D DC 30 01 F6 00 E7 F9 0A 00 10 DF
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
213564,silver,FOX,0x0A15/10 = 258.1,0x0A15/10 = 258.1,32,64,32,0x0A47/34 = 77.38235294117646,0x0729 = 1833,0x0729 = 1833,2,1,6,2,00 0A 00 0B 00 94 03 03 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,silver,MARIE,0x0AC8/10 = 276.0,0x0AC8/10 = 276.0,32,64,32,0x0B45/34 = 84.8529411764706,0x0762 = 1890,0x0762 = 1890,2,1,6,2,00 0A 00 1E 00 35 00 20 03 FF FF 0A 00 0B 00 B1 03 03 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,plat -s,ANDY,0x0A9B/10 = 271.5,0x0A9B/10 = 271.5,64,64,32,0x0B11/34 = 83.32352941176471,0x0723 = 1827,0x0723 = 1827,2,1,0C,0,00 0B 00 01 00 CA 00 20 03 FF FF 0A 00 0B 00 91 03 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
sad face,bronz -s,DAN,0x0A30/10 = 260.8,0x0A30/10 = 260.8,32,64,32,0x0B80/34 = 86.58823529411765,0x0725 = 1829,0x0725 = 1829,2,1,6,2,00 0C 00 02 00 80 00 20 03 FF FF 0A 00 0B 00 92 03 02 00 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
,plat,MIKE,0x0AE0/10 = 278.4,0x0AE0/10 = 278.4,32,64,32,0x0B33/34 = 84.32352941176471,0x06DE = 1758,0x06DE = 1758,2,1,6,2,00 0D 00 03 00 10 00 20 03 FF FF 0A 00 0B 00 6F 03 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,gold,PAULA,0x0ACF/10 = 276.7,0x0ACF/10 = 276.7,64,64,32,0x0A14/34 = 75.88235294117646,0x0708 = 1800,0x0708 = 1800,2,1,0C,0,00 0E 00 04 00 A5 00 20 03 FF FF 0A 00 0B 00 84 03 01 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
142653,gold,JU,0x09F9/10 = 255.3,0x09F9/10 = 255.3,64,64,32,0x0B30/34 = 84.23529411764706,0x06D7 = 1751,0x06D7 = 1751,2,1,0C,0,00 0A 00 0B 00 6B 03 01 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,plat,SARA,0x0A0E/10 = 257.4,0x0A0E/10 = 257.4,64,64,32,0x0B78/34 = 86.3529411764706,0x06DE = 1758,0x06DE = 1758,2,1,0C,0,00 0A 00 1E 00 5A 00 20 03 FF FF 0A 00 0B 00 6F 03 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
,bronze,SAM,0x0A86/10 = 269.4,0x0A86/10 = 269.4,64,64,32,0x0AE2/34 = 81.94117647058823,0x06D1 = 1745,0x06D1 = 1745,2,1,0C,0,00 0B 00 01 00 10 00 20 03 FF FF 0A 00 0B 00 68 03 02 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,plat,SEAN,0x0A17/10 = 258.3,0x0A17/10 = 258.3,64,64,32,0x0B65/34 = 85.79411764705883,0x0712 = 1810,0x0712 = 1810,2,1,0C,0,00 0C 00 02 00 35 00 20 03 FF FF 0A 00 0B 00 89 03 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,silver,ELEN,0x09CB/10 = 250.7,0x09CB/10 = 250.7,64,64,32,0x0A3B/34 = 77.02941176470588,0x06BB = 1723,0x06BB = 1723,2,1,0C,0,00 0D 00 03 00 80 00 20 03 FF FF 0A 00 0B 00 5D 03 03 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,silver,KNIGHT,0x0A02/10 = 256.2,0x0A02/10 = 256.2,32,64,32,0x0A18/34 = 76.0,0x0709 = 1801,0x0709 = 1801,2,1,6,2,00 0E 00 04 00 A5 00 20 03 FF FF 0A 00 0B 00 84 03 03 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
425136,bronze,JENNY,0x0A9D/10 = 271.7,0x0A9D/10 = 271.7,64,64,32,0x0B11/34 = 83.32352941176471,0x06F0 = 1776,0x06F0 = 1776,2,1,0C,0,00 0A 00 0B 00 78 03 02 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,gold,YOUNG,0x0AEF/10 = 279.9,0x0AEF/10 = 279.9,32,64,32,0x0A4C/34 = 77.52941176470588,0x06E1 = 1761,0x06E1 = 1761,2,1,6,2,00 0A 00 1E 00 5A 00 20 03 FF FF 0A 00 0B 00 70 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
sprinting,bronze,JAMES,0x0ADA/10 = 277.8,0x0ADA/10 = 277.8,32,64,32,0x0A3C/34 = 77.05882352941177,0x0769 = 1897,0x0769 = 1897,2,1,6,2,00 0B 00 01 00 A5 00 20 03 FF FF 0A 00 0B 00 B4 03 02 00 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
,gold,KNIGHT,0x0A5B/10 = 265.1,0x0A5B/10 = 265.1,32,64,32,0x0AC4/34 = 81.05882352941177,0x06EF = 1775,0x06EF = 1775,2,1,6,2,00 0C 00 02 00 10 00 20 03 FF FF 0A 00 0B 00 77 03 01 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,bronze,ARL,0x0A2C/10 = 260.4,0x0A2C/10 = 260.4,32,64,32,0x0B0A/34 = 83.11764705882354,0x0769 = 1897,0x0769 = 1897,2,1,6,2,00 0D 00 03 00 CA 00 20 03 FF FF 0A 00 0B 00 B4 03 02 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,bronze,JOEL,0x0A9E/10 = 271.8,0x0A9E/10 = 271.8,32,64,32,0x0AC4/34 = 81.05882352941177,0x06BF = 1727,0x06BF = 1727,2,1,6,2,00 0E 00 04 00 80 00 20 03 FF FF 0A 00 0B 00 5F 03 02 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
352164,gold,LIA,0x09FD/10 = 255.7,0x09FD/10 = 255.7,64,64,32,0x0B11/34 = 83.32352941176471,0x06F9 = 1785,0x06F9 = 1785,2,1,0C,0,00 0A 00 0B 00 7C 03 01 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,silver,ROBER,0x0AD0/10 = 276.8,0x0AD0/10 = 276.8,64,64,32,0x0B05/34 = 82.97058823529412,0x06F9 = 1785,0x06F9 = 1785,2,1,0C,0,00 0A 00 1E 00 35 00 20 03 FF FF 0A 00 0B 00 7C 03 03 00 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
,gold,PAULA,0x0A56/10 = 264.6,0x0A56/10 = 264.6,64,64,32,0x0B31/34 = 84.26470588235294,0x073A = 1850,0x073A = 1850,2,1,0C,0,00 0B 00 01 00 10 00 20 03 FF FF 0A 00 0B 00 9D 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,silver,NANCY,0x0A91/10 = 270.5,0x0A91/10 = 270.5,64,64,32,0x0A21/34 = 76.26470588235294,0x0758 = 1880,0x0758 = 1880,2,1,0C,0,00 0C 00 02 00 A5 00 20 03 FF FF 0A 00 0B 00 AC 03 03 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,plat,JOEL,0x0A49/10 = 263.3,0x0A49/10 = 263.3,64,64,32,0x0B7F/34 = 86.55882352941177,0x0706 = 1798,0x0706 = 1798,2,1,0C,0,00 0D 00 03 00 5A 00 20 03 FF FF 0A 00 0B 00 83 03 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,gold,YOUNG,0x0A9A/10 = 271.4,0x0A9A/10 = 271.4,64,64,32,0x0A2A/34 = 76.52941176470588,0x06C7 = 1735,0x06C7 = 1735,2,1,0C,0,00 0E 00 04 00 CA 00 20 03 FF FF 0A 00 0B 00 63 03 01 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
614235,gold,YOUNG,0x0AD2/10 = 277.0,0x0AD2/10 = 277.0,32,64,32,0x0AC5/34 = 81.08823529411765,0x0734 = 1844,0x0734 = 1844,2,1,6,2,00 0A 00 0B 00 9A 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,silver,JENNY,0x0A5D/10 = 265.3,0x0A5D/10 = 265.3,32,64,32,0x0A2B/34 = 76.55882352941177,0x0742 = 1858,0x0742 = 1858,2,1,6,2,00 0A 00 1E 00 A5 00 20 03 FF FF 0A 00 0B 00 A1 03 03 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,bronze,ELEN,0x0A9D/10 = 271.7,0x0A9D/10 = 271.7,64,64,32,0x0A56/34 = 77.82352941176471,0x06C2 = 1730,0x06C2 = 1730,2,1,0C,0,00 0B 00 01 00 CA 00 20 03 FF FF 0A 00 0B 00 61 03 02 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,gold,DARIO,0x09DF/10 = 252.7,0x09DF/10 = 252.7,32,64,32,0x09E8/34 = 74.58823529411765,0x0711 = 1809,0x0711 = 1809,2,1,6,2,00 0C 00 02 00 35 00 20 03 FF FF 0A 00 0B 00 88 03 01 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,bronze,JOEL,0x09F2/10 = 254.6,0x09F2/10 = 254.6,64,64,32,0x0AF8/34 = 82.58823529411765,0x074D = 1869,0x074D = 1869,2,1,0C,0,00 0D 00 03 00 80 00 20 03 FF FF 0A 00 0B 00 A6 03 02 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,plat,TERRY,0x09DA/10 = 252.2,0x09DA/10 = 252.2,64,64,32,0x0AF5/34 = 82.5,0x0748 = 1864,0x0748 = 1864,2,1,0C,0,00 0E 00 04 00 5A 00 20 03 FF FF 0A 00 0B 00 A4 03 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
625143,plat,JOEL,0x0AA2/10 = 272.2,0x0AA2/10 = 272.2,64,64,32,0x0A39/34 = 76.97058823529412,0x0712 = 1810,0x0712 = 1810,2,1,0C,0,00 0A 00 0B 00 89 03 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,silver,JU,0x0A57/10 = 264.7,0x0A57/10 = 264.7,64,64,32,0x0AE0/34 = 81.88235294117646,0x0759 = 1881,0x0759 = 1881,2,1,0C,0,00 0A 00 1E 00 5A 00 20 03 FF FF 0A 00 0B 00 AC 03 03 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
,bronze,SAMMY,0x0A8E/10 = 270.2,0x0A8E/10 = 270.2,64,64,32,0x0BAA/34 = 87.82352941176471,0x0712 = 1810,0x0712 = 1810,2,1,0C,0,00 0B 00 01 00 35 00 20 03 FF FF 0A 00 0B 00 89 03 02 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
,plat,ROBIN,0x0AA5/10 = 272.5,0x0AA5/10 = 272.5,32,64,32,0x0BB0/34 = 88.0,0x06F4 = 1780,0x06F4 = 1780,2,1,6,2,00 0C 00 02 00 CA 00 20 03 FF FF 0A 00 0B 00 7A 03 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
,gold,ARL,0x0A02/10 = 256.2,0x0A02/10 = 256.2,32,64,32,0x0A59/34 = 77.91176470588235,0x06CF = 1743,0x06CF = 1743,2,1,6,2,00 0D 00 03 00 10 00 20 03 FF FF 0A 00 0B 00 67 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,gold,SAM,0x0AD2/10 = 277.0,0x0AD2/10 = 277.0,64,64,32,0x0B62/34 = 85.70588235294117,0x06B2 = 1714,0x06B2 = 1714,2,1,0C,0,00 0E 00 04 00 A5 00 20 03 FF FF 0A 00 0B 00 59 03 01 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
Chocobo colors are always in the same order. I'm not seeing any patterns...
Acceleration and Cooperation are always the same.
Jockey color definitely has an impact if bronze.
To test if Intelligence matters I set all the values I know to be to the same, using this quick script. The unknown values don't seem change so I'm replacing the last half of the chocobo values. The contents of the first half is still a mystery.
allbytes = ""
print(allbytes[0:105],end='')
sbytes = allbytes[105:]
p2bytes = ""
for i in [1, 2, 3, 4, 5, 6]:
tbytes = sbytes[0:492]
p1bytes = tbytes[0:297]
if not p2bytes:
p2bytes = tbytes[297:-22]
p3bytes = tbytes[470:]
print(p1bytes+p2bytes+p3bytes,end='')
sbytes = sbytes[492:]
print(sbytes,end='')
To be honest I'm kinda surprised this doesn't crash the game, there's a weird artifact where the chocobos group together, but don't overlap at race start.
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
,gold,RICA,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0,00 0A 00 0B 00 8F 03 01 00 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
sprinting,silver,GEORGE,0x0A41/10 = 262.5,0x0A41/10 = 262.5,32,64,32,0x0ACD/34 = 81.32352941176471,0x06FF = 1791,0x06FF = 1791,2,1,6,2,00 0A 00 1E 00 10 00 20 03 FF FF 0A 00 0B 00 7F 03 03 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,gold,JAMES,0x0AED/10 = 279.7,0x0AED/10 = 279.7,32,64,32,0x0B75/34 = 86.26470588235294,0x0739 = 1849,0x0739 = 1849,2,1,6,2,00 0B 00 01 00 80 00 20 03 FF FF 0A 00 0B 00 9C 03 01 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
sprinting,gold,AIMEE,0x0A8A/10 = 269.8,0x0A8A/10 = 269.8,64,64,32,0x0B67/34 = 85.8529411764706,0x06C7 = 1735,0x06C7 = 1735,2,1,0C,0,00 0C 00 02 00 CA 00 20 03 FF FF 0A 00 0B 00 63 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,silver,ROBIN,0x0A98/10 = 271.2,0x0A98/10 = 271.2,64,64,32,0x09E2/34 = 74.41176470588235,0x06FB = 1787,0x06FB = 1787,2,1,0C,0,00 0D 00 03 00 A5 00 20 03 FF FF 0A 00 0B 00 7D 03 03 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,gold,JENNY,0x0A2D/10 = 260.5,0x0A2D/10 = 260.5,32,64,32,0x0ADC/34 = 81.76470588235294,0x0714 = 1812,0x0714 = 1812,2,1,6,2,00 0E 00 04 00 5A 00 20 03 FF FF 0A 00 0B 00 8A 03 01 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
341526,,,,,,,,,,,,,,,
data,color,name,stamina1,stamina2,intelligence,cooperation,acceleration,top speed,run speed,runspeed2,unk1,unk2,unk3,unk4,other
,gold,RICA,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0, 00 0A 00 0B 00 8F 03 01 00 E7 F9 0A 00 B2 DF 41 FA 0A 00 B3 DF 00 04 00 04 E7 F9 0A 00 B2 DF
sprinting,silver,GEORGE,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0,00 0A 00 1E 00 10 00 20 03 FF FF 0A 00 0B 00 7F 03 03 00 E6 F9 0A 00 C6 DD 40 FA 0A 00 C7 DD 00 04 00 04 E6 F9 0A 00 C6 DD
,gold,JAMES,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0,00 0B 00 01 00 80 00 20 03 FF FF 0A 00 0B 00 9C 03 01 00 E6 F9 0A 00 81 DC 40 FA 0A 00 82 DC 00 04 00 04 E6 F9 0A 00 81 DC
sprinting,gold,AIMEE,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0,00 0C 00 02 00 CA 00 20 03 FF FF 0A 00 0B 00 63 03 01 00 E6 F9 0A 00 23 DD 40 FA 0A 00 24 DD 00 04 00 04 E6 F9 0A 00 23 DD
,silver,ROBIN,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0,00 0D 00 03 00 A5 00 20 03 FF FF 0A 00 0B 00 7D 03 03 00 E7 F9 0A 00 6D DE 41 FA 0A 00 6E DE 00 04 00 04 E7 F9 0A 00 6D DE
,gold,JENNY,0x09C4/10 = 250.0,0x09C4/10 = 250.0,64,64,32,0x0A91/34 = 79.55882352941177,0x071E = 1822,0x071E = 1822,2,1,0C,0,00 0E 00 04 00 5A 00 20 03 FF FF 0A 00 0B 00 8A 03 01 00 E7 F9 0A 00 10 DF 41 FA 0A 00 11 DF 00 04 00 04 E7 F9 0A 00 10 DF
461253,,,,,,,,,,,,,,,
I then gave C1 a lower intel of 32 and got the same ordering.
So at least in C class those three values do nothing.
I increased the top speed by 34 (1/kmh) on c1 and it became 164253. So speed matters, a higher speed will give a higher ranking.
Sprinting doesn't appear to matter 4 beat 6 and 1, but 2 lost to 5.
Increasing and decreasing stamina a little and a lot does nothing to the order, it appears stamina does not matter.
Changing the name didn't make a difference.
Altering the run speed did, higher run speeds give higher rankings.
So I need to figure out how run speed is calculated.
Let's see if we can figure out the rest of the 164 chocobo bytes.
I didn't see the max run value I put in black chocobo in memory indicating it's not used.
Byte -60 = 0=normal, 1=normal, 2=sprinting, 3=normal, 4=normal
2 on c2 = 461253
1 on c2 = 564312
0 on c2 = 643125
3 on c2 = 643125
4 on c2 = 643125
So sprinting does change the outcome. I haven't seen this byte set to 1 yet.
-67, -68 = no change
-70,-71,-72 = no change
-77 = Is a weird byte
Setting 4 to 0 on chocobo 2 changes the results from 461253 to 632145
Setting 4 to 3 on chocobo 2 changes the results from 461253 to 123564
Setting 4 to 2 on chocobo 2 changes the results from 461253 to 134526
Setting 4 to 1 on chocobo 2 changes the results from 461253 to 123564
Setting 4 to 5 on chocobo 2 changes the results from 461253 to 413625
Setting 4 to 80 on chocobo 2 changes the result from 461253 to 136254
-85 = Is another weird byte, changing 4 gives different results than changing 4 from -77
-91,-92 = no change
-93,94,54 = no change
-115,116 = messes with camera angle, same order
-118,119,120 = different weird camera angle, same order
-130,134 = camera angle, same order
-145 = when FF/00 -> 461253
-145,146 = random when set to 00 00?, 461253 when 01 01
-146 = when FF/00 -> 461253
-147,148 = when set to 00 00 on c6, that chocobo sprints until exhausted, but no one passes until the very end 432156
-150 = 35-> 0, 613254
-154 = 5-> 4 changes chocobo to color of No.5, but crashes when loading race
-156 = Changes jockey color, but can also change the jockey to be any object in the minigame
15 = gold, 14 = silver, 13 = gold, 12 = gold, 11 = silver, 10 =gold, 9 = hard crash, 1= no rider, 2=no rider, 3= no rider, 4=no rider, 5=graphic crash,6=hard crash, 7 = hard crash, 8=hard crash
16=mog in rainbow pipe animation overlayed
17=clam animation
The numbers change based on the generated value chocobo6's rider is 15 chocobo1's rider is 9. chocobo1 is 0 chocobo 6 is 5. The race crashes when the chocobo gets to the object on the path.
So to summarize I know these variables modify the outcome.
Top Speed, Sprinting, Jockey Color, Run Speed (hidden value), and Sprint Likelihood (Hidden value, not tied to jockey color, same across a class).
Name and stamina do not seem to matter. (stamina only matters when they run out, the chance they have a high top speed and run out of stamina is really low, and they don't just barely run out so the difference in range really doesn't help too much)
-77,85,145,146,147,148,150 can modify results too
77,85 = are always set to 4 though in class c
145-148 = are always 20 03 FF FF in class c
150 is the only byte that differs. However giving two chocobos the same value doesn't put them next to each other with the same other stats.
A higher ranking jockey doesn't mean it has a higher run speed. Perhaps it makes a difference in how run speed is calculated...
-
I'm going to provide some preliminary speculation based on my own observations on this minigame.
Stats are important, but in the end it's all random. Whether the chocobos will sprint or if they will veer left or right and at what time. The chocobo's rank is based on a holistic calculation of all its stats. I can't tell you HOW that calculation is performed. The higher the intelligence, the less they will swerve. The higher the stamina, the longer they can sprint. yadda, yadda.
It also seems like there's some external data at work here. As the race begins, the game just "boosts" some of the positions. It sort of chooses which two it wants to win and gives them some stat-independent advantages at random intervals. These two are more likely to win and it's just completely random whether they do or not. The highest ranks don't always get boosted either.
What I would do: Since you have identified where these are in the PSX memory, what you can do is make a savestate before the chocobos begin running, check the winners vs losers, reload the save, swap the winners' stats with the losers' stats and see how that affects the outcome.
Addendum: I haven't seen ergonomy_joe for a while. Not saying he's not reading the forums. I still do, but I rarely post anymore.
I have looked at the chocobo.wat file, but it doesn't seem independent enough to parse into individual files. This is a "big" hassle in the lgp files too. They don't contain enough metadata on their own to be used separately. It's all contained in the exe file where different fragments start and their size.
However, looking at the bytes inside it looks like it contains several parts that consist of default values for some things. Possibly even proprietary AI scripts which I can't immediately find handlers for. I say that because they LOOK like scripts, but not asm code. It's more like the WM scripts or Field scripts, but they're quite short. It also likely contains the modeling info for the different tracks though the model data and textures for the objects are saved independently in the chocobo.lgp file.
-
I think how often they sprint is just set by the bytes[10-13] class C is 20 03 FF FF.
Hmmm. Why do you think there's a boosting stat for the two it wants to win?
I ran a similar test by replacing all the chocobos with the same stats. I'll take a stab at doing that test though.
I've continued looking at things these last couple days.
There are actually 7.5 chocobos in memory and not 6. Something I didn't notice until I made a better script to rip out data. It's a little confusing. The first chocobo doesn't get fully initialized and has some null data in regards to "willingness to sprint" values. The 7th chocobo is a copy of the chocobo currently in first. At the prerace screen this is the first chocobo fully initialized. The half chocobo is data following the 7th chocobo. It appears to just be a copy of the class/"willingness to sprint" values for the 7th chocobo.
Bytes 152 -> show the current order the chocobos are in
I had an idea that I might be able to bruteforce the run speed with enough data. So I wrote out a script to play the game in super speed in an emulator. It opens the chocobo betting game. Dumps the memory into a nice csv. Patches the memory to make the chocobos invisible (modifies all chocobo pointers to be the next jocker pointer). Takes a screenshot. Uses opencv to identify the jockey color (I apply a filter, crop, get the average color, and report found when a close enough match happens. It's surprisingly hard to do when the jockey is constantly rotating). Patches memory again to make the chocobos visible. Runs the race. Dumps the data again after the race into a nice csv. Resets the game state, waits a random time, and repeats.
I ran it for a few hours and generated data for 683 runs of class c. This data dump has the 7th chocobo and the set includes winning order and jockey color.
I can't attach files, so hopefully this gets mod approval. https://anonfiles.com/XbM0QeD9x3/ff7classc_7z
From Chocobo Stance Modifier -> https://gamehacking.org/game/88853
Byte 150 is the jockeys/chocobo state during a race.
It does not mess with the order, just the animation
From Chocobos Have Infinite Stamina When Racing -> https://gamehacking.org/game/88853
Byte 126/-32 aka Stamina2 is the stamina remaining in a race for that chocobo
Looking closer at the data. Something interesting does happen at the end game screen.
As the data in memory is not in the final order. The last two places swap. I don't account for this in my csv.
Memory would put the order as Terry(3),Rudy(1),Tom(4),Gary(5),Andy(2),Harvey(6)
but on screen it displays Terry(3),Rudy(1),Tom(4),Gary(5),Harvey(6),Andy(2)
I thought maybe it stops updating when a chocobo crosses the finish line, but I recorded a video and I don't see the memory order occurring from the time the fiirst chocobo to the last chocobo cross the finish line.
The 5/6 swapping doesn't happen all the time either...
Sometimes the memory order is just wrong from what is displayed.
(https://i.imgur.com/SL4n62Q.png)
(https://i.imgur.com/2GLXhYa.png)
(https://i.imgur.com/26yT2in.png)
I ran your test but I think the jockeys have something to do with it:
Order: 352614
Young(silver), Tim(silver) = Winners
Fox (gold), Terry(silver) = Losers
Swapped stats on Young <->Fox, Tim<->Terry, Fox was in choco0 so I copied Terry to choco7 as well.
Stats swapped: runspeed2,runspeed1,topspeed,acc,coop,intel,stamina2,stamina1
New Order: 134265
color,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,b156,all
g,0,FOX,81,2761,252,2515,2515,0,1701,32,64,32,0,0,0,0,0A000B005203,E7F90A00B2,41FA0A00B3DF,4,4,E7F90A,B2DF,2,6,1,2,0,10,3,00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A 00 0B 00 52 03 01 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 B3 DF 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 A5 06 00 00 02 00 00 00 A5 06 C9 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 D3 09 00 00 D3 09 00 00 00 00 00 00 00 00 8D 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
s,1,NANCY,83,2834,271,2713,2713,0,1779,32,64,32,2003,0A,0,10,0A000B007903,E6F90A0081,40FA0A0082DC,4,4,E6F90A,81DC,2,6,1,2,0,CA,3,00 0A 00 00 00 1E 00 10 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 79 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 82 DC 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 00 00 00 00 F3 06 00 00 02 00 00 00 F3 06 12 0B 00 00 06 00 32 00 01 00 64 00 32 00 02 00 99 0A 00 00 99 0A 00 00 00 00 00 00 00 00 94 00 00 00 00 00 CA 00 00 00 00 00 01 00 00 00 03 00 2E 21 2E 23 39 FF FF
s,2,YOUNG,85,2888,251,2506,2506,0,1821,64,64,32,2003,0B,1,CA,0A000B008E03,E7F90A006D,41FA0A006EDE,4,4,E7F90A,6DDE,0,0C,1,2,0,5A,3,00 0B 00 01 00 00 00 CA 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 8E 03 03 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 6E DE 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 00 00 00 00 1D 07 00 00 00 00 00 00 1D 07 48 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 CA 09 00 00 CA 09 00 00 00 00 00 00 00 00 97 00 00 00 00 00 5A 00 00 00 00 00 02 00 00 00 03 00 39 2F 35 2E 27 FF FF
s,3,TERRY,75,2566,253,2533,2533,0,1806,32,64,32,2003,0C,2,5A,0A000B008703,E7F90A0010,41FA0A0011DF,4,4,E7F90A,10DF,2,6,1,2,0,35,3,00 0C 00 02 00 00 00 5A 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 87 03 03 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 11 DF 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 0E 07 00 00 02 00 00 00 0E 07 06 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 E5 09 00 00 E5 09 00 00 00 00 00 00 00 00 96 00 00 00 00 00 35 00 00 00 00 00 03 00 00 00 03 00 34 25 32 32 39 FF FF
s,4,TIM,87,2970,270,2701,2701,0,1834,64,64,32,2003,0D,3,35,0A000B009503,E6F90A00C6,40FA0A00C7DD,4,4,E6F90A,C6DD,0,0C,1,2,0,80,3,00 0D 00 03 00 00 00 35 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 95 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 C7 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 00 00 00 2A 07 00 00 00 00 00 00 2A 07 9A 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 8D 0A 00 00 8D 0A 00 00 00 00 00 00 00 00 98 00 00 00 00 00 80 00 00 00 00 00 04 00 00 00 03 00 34 29 2D FF FF FF FF
g,5,JENNY,77,2617,255,2554,2554,0,1756,64,64,32,2003,0E,4,80,0A000B006E03,E6F90A0023,40FA0A0024DD,4,4,E6F90A,23DD,0,0C,1,2,0,A5,3,00 0E 00 04 00 00 00 80 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 6E 03 01 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 24 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 DC 06 00 00 00 00 00 00 DC 06 39 0A 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 FA 09 00 00 FA 09 00 00 00 00 00 00 00 00 92 00 00 00 00 00 A5 00 00 00 00 00 05 00 00 00 03 00 2A 25 2E 2E 39 FF FF
see above,0,FOX,81,2761,252,2515,2515,0,1701,32,64,32,2003,0F,5,A5,1000000,62F60A00C6,BCF60A00C6DD,4,4,E7F90A,B2DF,2,6,1,2,0,7F,3,00 0F 00 05 00 00 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 62 F6 0A 00 C6 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 BC F6 0A 00 C6 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 A5 06 00 00 02 00 00 00 A5 06 C9 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 D3 09 00 00 D3 09 00 00 00 00 00 00 00 00 8D 00 00 00 00 00 7F 00 00 00 00 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
color,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,b156,all
g,2,FOX,81,2761,503,5030,1238,0,1701,32,64,32,AF00,D7,0,0,AA03AB030000,8AF409005D,99F40A0020DF,6,6,E7F90A,B2DF,2,6,1,2,1,30,3,00 D7 00 00 00 04 01 00 00 AF 00 00 00 ED 00 00 00 C7 00 00 00 AA 03 AB 03 00 00 01 00 00 00 09 00 00 00 00 00 8A F4 09 00 5D DE 00 00 F8 0D 00 80 00 00 00 00 25 0F 00 00 00 00 00 00 99 F4 0A 00 20 DF 00 00 00 00 62 06 00 00 00 00 00 00 8F 06 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 50 00 00 00 A5 06 00 00 02 00 AE 07 A5 06 C9 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 D6 04 00 00 A6 13 00 00 05 00 00 10 B4 A2 00 01 03 00 00 00 30 00 FF FF 03 00 02 00 04 00 03 00 26 2F 38 FF FF FF FF
s,3,NANCY,83,2834,543,5426,2942,0,1779,32,64,32,2003,0A,0,10,AA03AB030000,62F4090066,99F40A003DDC,1,1,E6F90A,81DC,2,6,1,2,1,D8,3,00 0A 00 00 00 CF FE 10 00 20 03 FF FF 00 00 00 00 00 00 00 00 AA 03 AB 03 00 00 03 00 00 00 09 00 00 00 00 00 62 F4 09 00 66 DD 00 00 46 0B 00 80 00 00 00 00 2C 0F 00 00 00 00 00 00 99 F4 0A 00 3D DC 00 00 00 00 A2 01 00 00 00 00 00 00 21 01 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 AB 00 00 00 F3 06 00 00 02 00 FB 07 F3 06 12 0B 00 00 06 00 32 00 01 00 64 00 32 00 02 00 7E 0B 00 00 32 15 00 00 03 00 23 00 BF 05 00 01 03 00 00 00 D8 00 FF FF 03 00 03 00 00 00 03 00 2E 21 2E 23 39 FF FF
s,0,YOUNG,85,2888,501,5012,2724,0,1821,64,64,32,2003,0B,1,CA,AD03AE030000,75F5090020,A7F50A0075DE,4,5,E7F90A,6DDE,0,0C,1,2,1,57,3,00 0B 00 01 00 00 00 CA 00 20 03 FF FF 00 00 00 00 00 00 00 00 AD 03 AE 03 00 00 03 00 00 00 09 00 00 00 00 00 75 F5 09 00 20 DE 00 00 4C 00 00 00 00 00 00 00 DB 02 00 00 00 00 00 00 A7 F5 0A 00 75 DE 00 00 00 00 B2 04 00 00 00 00 00 00 09 05 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 6C 00 00 00 1D 07 00 00 00 00 48 0B 1D 07 48 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 A4 0A 00 00 94 13 00 00 01 00 BE 11 2B DA 00 01 02 00 00 00 57 00 FF FF 02 00 00 00 07 00 03 00 39 2F 35 2E 27 FF FF
s,5,TERRY,75,2566,507,5066,2746,0,1806,32,64,32,2003,0C,2,5A,A803A9030000,B4F3090057,E7F90A0010DF,3,3,E7F90A,10DF,2,6,1,2,1,6B,3,00 0C 00 02 00 00 00 5A 00 20 03 FF FF 00 00 00 00 00 00 00 00 A8 03 A9 03 00 00 03 00 00 00 09 00 00 00 00 00 B4 F3 09 00 57 DE 00 00 6E 01 00 00 00 00 00 00 39 03 00 00 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 4C 03 00 00 00 00 00 00 0E 03 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 67 00 00 00 0E 07 00 00 02 00 06 0A 0E 07 06 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 BA 0A 00 00 CA 13 00 00 06 00 00 08 2E FD 00 01 04 00 00 00 6B 00 FF FF 04 00 05 00 02 00 03 00 34 25 32 32 39 FF FF
s,1,TIM,87,2970,540,5402,3044,0,1834,64,64,32,2003,0D,3,35,AB03AC030000,EDF40900CF,F3F40A00AEDD,3,3,E6F90A,C6DD,0,0C,1,2,1,84,3,00 0D 00 03 00 00 00 35 00 20 03 FF FF 00 00 00 00 00 00 00 00 AB 03 AC 03 00 00 03 00 00 00 09 00 00 00 00 00 ED F4 09 00 CF DD 00 00 53 0D 00 00 00 00 00 00 1C 08 00 80 00 00 00 00 F3 F4 0A 00 AE DD 00 00 00 00 52 03 00 00 00 00 00 00 41 03 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 6F 00 00 00 2A 07 00 00 00 00 46 08 2A 07 9A 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 E4 0B 00 00 1A 15 00 00 02 00 86 11 91 5C 00 01 03 00 00 00 84 00 FF FF 03 00 01 00 09 00 03 00 34 29 2D FF FF FF FF
g,4,JENNY,77,2617,511,5108,1436,0,1756,64,64,32,2003,0E,4,80,A903AA030A00,FDF3090008,3FF40A00ECDC,3,3,E6F90A,23DD,0,0C,1,2,1,B0,3,00 0E 00 04 00 00 00 80 00 20 03 FF FF 00 00 00 00 00 00 00 00 A9 03 AA 03 0A 00 01 00 00 00 09 00 00 00 00 00 FD F3 09 00 08 DD 00 00 FE 03 00 80 00 00 00 00 70 0B 00 00 00 00 00 00 3F F4 0A 00 EC DC 00 00 00 00 E6 03 00 00 00 00 00 00 83 03 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 A2 00 00 00 DC 06 00 00 00 00 8B 08 DC 06 39 0A 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 9C 05 00 00 F4 13 00 00 04 00 00 0D A5 26 00 01 03 00 00 00 B0 00 FF FF 03 00 04 00 01 00 03 00 2A 25 2E 2E 39 FF FF
see above,0,YOUNG,85,2888,501,5012,4852,0,1821,64,64,32,2003,0F,5,A5,3A003B002406,F108090084,E4080A0082E3,6,7,E7F90A,6DDE,0,0C,1,2,1,7F,3,00 0F 00 05 00 00 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00 00 3A 00 3B 00 24 06 03 00 00 00 09 00 00 00 00 00 F1 08 09 00 84 E3 00 00 65 0B 00 80 00 00 00 00 8E 02 00 80 00 00 00 00 E4 08 0A 00 82 E3 00 00 00 00 E2 06 00 00 00 00 00 00 01 07 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 30 00 00 00 1D 07 00 00 00 00 1E 07 1D 07 48 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 F4 12 00 00 94 13 00 00 00 00 B2 06 B6 6D 06 01 00 00 00 00 7F 00 00 00 00 00 00 00 00 00 03 00 39 2F 35 2E 27 FF FF
Swapping bytes Young <=> Fox Tim <=> Terry,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
color,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,b156,all
g,0,FOX,85,2888,251,2506,2506,0,1821,64,64,32,0,0,0,0,0A000B005203,E7F90A00B2,41FA0A00B3DF,4,4,E7F90A,B2DF,2,6,1,2,0,10,3,00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A 00 0B 00 52 03 01 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 B3 DF 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 1D 07 00 00 02 00 00 00 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 CA 09 00 00 CA 09 00 00 00 00 00 00 00 00 8D 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
s,1,NANCY,83,2834,271,2713,2713,0,1779,32,64,32,2003,0A,0,10,0A000B007903,E6F90A0081,40FA0A0082DC,4,4,E6F90A,81DC,2,6,1,2,0,CA,3,00 0A 00 00 00 1E 00 10 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 79 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 82 DC 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 00 00 00 00 F3 06 00 00 02 00 00 00 F3 06 12 0B 00 00 06 00 32 00 01 00 64 00 32 00 02 00 99 0A 00 00 99 0A 00 00 00 00 00 00 00 00 94 00 00 00 00 00 CA 00 00 00 00 00 01 00 00 00 03 00 2E 21 2E 23 39 FF FF
s,2,YOUNG,81,2761,252,2515,2515,0,1701,32,64,32,2003,0B,1,CA,0A000B008E03,E7F90A006D,41FA0A006EDE,4,4,E7F90A,6DDE,0,0C,1,2,0,5A,3,00 0B 00 01 00 00 00 CA 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 8E 03 03 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 6E DE 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 00 00 00 00 A5 06 00 00 00 00 00 00 A5 06 C9 0A 00 00 0C 00 32 00 01 00 64 00 32 00 02 00 D3 09 00 00 D3 09 00 00 00 00 00 00 00 00 97 00 00 00 00 00 5A 00 00 00 00 00 02 00 00 00 03 00 39 2F 35 2E 27 FF FF
s,3,TERRY,87,2970,270,2701,2701,0,1834,64,64,32,2003,0C,2,5A,0A000B008703,E7F90A0010,41FA0A0011DF,4,4,E7F90A,10DF,2,6,1,2,0,35,3,00 0C 00 02 00 00 00 5A 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 87 03 03 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 11 DF 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 2A 07 00 00 02 00 00 00 2A 07 9A 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 8D 0A 00 00 8D 0A 00 00 00 00 00 00 00 00 96 00 00 00 00 00 35 00 00 00 00 00 03 00 00 00 03 00 34 25 32 32 39 FF FF
s,4,TIM,75,2566,253,2533,2533,0,1806,32,64,32,2003,0D,3,35,0A000B009503,E6F90A00C6,40FA0A00C7DD,4,4,E6F90A,C6DD,0,0C,1,2,0,80,3,00 0D 00 03 00 00 00 35 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 95 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 C7 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 00 00 00 0E 07 00 00 00 00 00 00 0E 07 06 0A 00 00 0C 00 32 00 01 00 64 00 32 00 02 00 E5 09 00 00 E5 09 00 00 00 00 00 00 00 00 98 00 00 00 00 00 80 00 00 00 00 00 04 00 00 00 03 00 34 29 2D FF FF FF FF
g,5,JENNY,77,2617,255,2554,2554,0,1756,64,64,32,2003,0E,4,80,0A000B006E03,E6F90A0023,40FA0A0024DD,4,4,E6F90A,23DD,0,0C,1,2,0,A5,3,00 0E 00 04 00 00 00 80 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 6E 03 01 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 24 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 DC 06 00 00 00 00 00 00 DC 06 39 0A 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 FA 09 00 00 FA 09 00 00 00 00 00 00 00 00 92 00 00 00 00 00 A5 00 00 00 00 00 05 00 00 00 03 00 2A 25 2E 2E 39 FF FF
see above,0,FOX,85,2888,251,2506,2506,0,1821,64,64,32,2003,0F,5,A5,1000000,62F60A00C6,BCF60A00C6DD,4,4,E7F90A,B2DF,2,6,1,2,0,7F,3,00 0F 00 05 00 00 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 62 F6 0A 00 C6 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 BC F6 0A 00 C6 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 1D 07 00 00 02 00 00 00 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 CA 09 00 00 CA 09 00 00 00 00 00 00 00 00 8D 00 00 00 00 00 7F 00 00 00 00 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
color,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,b156,all
g,0,FOX,85,2888,501,5012,2324,0,1821,64,64,32,3101,3C,0,0,AD03AE030000,4DF5090099,A7F50A0007DE,1,2,E7F90A,B2DF,2,6,1,2,1,70,3,00 3C 01 00 00 1F 01 00 00 31 01 00 00 65 01 00 00 50 01 00 00 AD 03 AE 03 00 00 01 00 00 00 09 00 00 00 00 00 4D F5 09 00 99 DE 00 00 31 0E 00 80 00 00 00 00 E6 0F 00 00 00 00 00 00 A7 F5 0A 00 07 DE 00 00 00 00 45 01 00 00 00 00 00 00 44 02 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 7E 00 00 00 1D 07 00 00 02 00 60 07 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 14 09 00 00 94 13 00 00 01 00 3F 0A 75 0C 00 01 02 00 00 00 70 00 FF FF 02 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
s,2,NANCY,83,2834,543,5426,2870,0,1779,32,64,32,2003,0A,0,10,AA03AB030000,73F40900CF,99F40A0019DD,2,1,E6F90A,81DC,2,6,1,2,1,A6,3,00 0A 00 00 00 7F FE 10 00 20 03 FF FF 00 00 00 00 00 00 00 00 AA 03 AB 03 00 00 03 00 00 00 09 00 00 00 00 00 73 F4 09 00 CF DD 00 00 28 0D 00 00 00 00 00 00 82 0E 00 00 00 00 00 00 99 F4 0A 00 19 DD 00 00 00 00 D1 02 00 00 00 00 00 00 FF 01 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 6D 00 00 00 F3 06 00 00 02 00 FB 07 F3 06 12 0B 00 00 06 00 32 00 01 00 64 00 32 00 02 00 36 0B 00 00 32 15 00 00 04 00 00 10 82 9E 00 01 03 00 00 00 A6 00 FF FF 03 00 02 00 04 00 03 00 2E 21 2E 23 39 FF FF
s,1,YOUNG,81,2761,503,5030,2790,0,1701,32,64,32,2003,0B,1,CA,AB03AC030A00,F0F409002C,F3F40A003BDE,4,4,E7F90A,6DDE,0,0C,1,2,1,64,3,00 0B 00 01 00 00 00 CA 00 20 03 FF FF 00 00 00 00 00 00 00 00 AB 03 AC 03 0A 00 03 00 00 00 09 00 00 00 00 00 F0 F4 09 00 2C DE 00 00 8C 0A 00 00 00 00 00 00 96 01 00 00 00 00 00 00 F3 F4 0A 00 3B DE 00 00 00 00 90 04 00 00 00 00 00 00 81 04 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 5B 00 00 00 A5 06 00 00 00 00 2A 07 A5 06 C9 0A 00 00 0C 00 32 00 01 00 64 00 32 00 02 00 E6 0A 00 00 A6 13 00 00 02 00 00 0F 0F CE 00 01 03 00 00 00 64 00 FF FF 03 00 01 00 07 00 03 00 39 2F 35 2E 27 FF FF
s,5,TERRY,87,2970,540,5402,2864,0,1834,64,64,32,2003,0C,2,5A,A903AA030000,28F4090086,3FF40A00B7DC,2,0,E7F90A,10DF,2,6,1,2,1,BC,3,00 0C 00 02 00 00 00 5A 00 20 03 FF FF 00 00 00 00 00 00 00 00 A9 03 AA 03 00 00 03 00 00 00 09 00 00 00 00 00 28 F4 09 00 86 DE 00 00 87 0F 00 00 00 00 00 00 CD 0E 00 00 00 00 00 00 3F F4 0A 00 B7 DC 00 00 00 00 19 02 00 00 00 00 00 00 D7 00 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 4B 00 00 00 2A 07 00 00 02 00 9A 0B 2A 07 9A 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 30 0B 00 00 1A 15 00 00 03 00 00 09 95 CE 00 01 03 00 00 00 BC 00 FF FF 03 00 05 00 02 00 03 00 34 25 32 32 39 FF FF
s,4,TIM,75,2566,507,5066,2938,0,1806,32,64,32,2003,0D,3,35,A903AA030A00,DFF3090026,E6F90A00C6DD,4,4,E6F90A,C6DD,0,0C,1,2,0,A3,3,00 0D 00 03 00 00 00 35 00 20 03 FF FF 00 00 00 00 00 00 00 00 A9 03 AA 03 0A 00 03 00 00 00 09 00 00 00 00 00 DF F3 09 00 26 DD 00 00 1B 05 00 00 00 00 00 00 CD 04 00 80 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 A4 00 00 00 0E 07 00 00 00 00 0E 07 0E 07 06 0A 00 00 0C 00 32 00 01 00 64 00 32 00 02 00 7A 0B 00 00 CA 13 00 00 06 00 3E 11 F0 2F 00 00 01 00 00 00 A3 00 FF FF 00 00 04 00 89 01 03 00 34 29 2D FF FF FF FF
g,3,JENNY,77,2617,511,5108,1419,0,1756,64,64,32,2003,0E,4,80,AA03AB036400,62F409003C,99F40A0057DD,5,4,E6F90A,23DD,0,0C,1,2,1,98,3,00 0E 00 04 00 00 00 80 00 20 03 FF FF 00 00 00 00 00 00 00 00 AA 03 AB 03 64 00 01 00 00 00 09 00 00 00 00 00 62 F4 09 00 3C DD 00 00 28 08 00 80 00 00 00 00 53 01 00 00 00 00 00 00 99 F4 0A 00 57 DD 00 00 00 00 00 05 00 00 00 00 00 00 85 04 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 8C 00 00 00 DC 06 00 00 00 00 39 0A DC 06 39 0A 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 8B 05 00 00 F4 13 00 00 05 00 00 06 2E 33 00 01 03 00 00 00 98 00 FF FF 03 00 03 00 02 00 03 00 2A 25 2E 2E 39 FF FF
see above,0,FOX,85,2888,501,5012,2324,0,1821,64,64,32,2003,0F,5,A5,B00301000000,95F6090066,BCF60A00C6DD,1,1,E7F90A,B2DF,2,6,1,2,1,7F,3,00 0F 00 05 00 00 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00 00 B0 03 01 00 00 00 01 00 00 00 09 00 00 00 00 00 95 F6 09 00 66 DF 00 00 18 01 00 80 00 00 00 00 F1 01 00 80 00 00 00 00 BC F6 0A 00 C6 DD 00 00 00 00 24 01 00 00 00 00 00 00 2B 01 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 3B 00 00 00 1D 07 00 00 02 00 A3 07 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 14 09 00 00 94 13 00 00 01 00 DF 0F BB 6D 00 01 01 00 00 00 7F 00 FF FF 02 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
352614 -> 134265,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Swapping just terry and young who are both silver:
352614 ->456231
3 -> 1st -> 5th
4 -> 6th -> 1st
color,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,b156,all
g,0,FOX,81,2761,252,2515,2515,0,1701,32,64,32,0,0,0,0,0A000B005203,E7F90A00B2,41FA0A00B3DF,4,4,E7F90A,B2DF,2,6,1,2,0,10,3,00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A 00 0B 00 52 03 01 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 B3 DF 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 A5 06 00 00 02 00 00 00 A5 06 C9 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 D3 09 00 00 D3 09 00 00 00 00 00 00 00 00 8D 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
s,1,NANCY,83,2834,271,2713,2713,0,1779,32,64,32,2003,0A,0,10,0A000B007903,E6F90A0081,40FA0A0082DC,4,4,E6F90A,81DC,2,6,1,2,0,CA,3,00 0A 00 00 00 1E 00 10 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 79 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 82 DC 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 00 00 00 00 F3 06 00 00 02 00 00 00 F3 06 12 0B 00 00 06 00 32 00 01 00 64 00 32 00 02 00 99 0A 00 00 99 0A 00 00 00 00 00 00 00 00 94 00 00 00 00 00 CA 00 00 00 00 00 01 00 00 00 03 00 2E 21 2E 23 39 FF FF
s,2,YOUNG,75,2566,253,2533,2533,0,1806,32,64,32,2003,0B,1,CA,0A000B008E03,E7F90A006D,41FA0A006EDE,4,4,E7F90A,6DDE,0,0C,1,2,0,5A,3,00 0B 00 01 00 00 00 CA 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 8E 03 03 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 6E DE 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 00 00 00 00 0E 07 00 00 00 00 00 00 0E 07 06 0A 00 00 0C 00 32 00 01 00 64 00 32 00 02 00 E5 09 00 00 E5 09 00 00 00 00 00 00 00 00 97 00 00 00 00 00 5A 00 00 00 00 00 02 00 00 00 03 00 39 2F 35 2E 27 FF FF
s,3,TERRY,85,2888,251,2506,2506,0,1821,64,64,32,2003,0C,2,5A,0A000B008703,E7F90A0010,41FA0A0011DF,4,4,E7F90A,10DF,2,6,1,2,0,35,3,00 0C 00 02 00 00 00 5A 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 87 03 03 00 00 00 00 00 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 FA 0A 00 11 DF 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 00 00 00 00 1D 07 00 00 02 00 00 00 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 CA 09 00 00 CA 09 00 00 00 00 00 00 00 00 96 00 00 00 00 00 35 00 00 00 00 00 03 00 00 00 03 00 34 25 32 32 39 FF FF
s,4,TIM,87,2970,270,2701,2701,0,1834,64,64,32,2003,0D,3,35,0A000B009503,E6F90A00C6,40FA0A00C7DD,4,4,E6F90A,C6DD,0,0C,1,2,0,80,3,00 0D 00 03 00 00 00 35 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 95 03 03 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 C7 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 00 00 00 00 2A 07 00 00 00 00 00 00 2A 07 9A 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 8D 0A 00 00 8D 0A 00 00 00 00 00 00 00 00 98 00 00 00 00 00 80 00 00 00 00 00 04 00 00 00 03 00 34 29 2D FF FF FF FF
g,5,JENNY,77,2617,255,2554,2554,0,1756,64,64,32,2003,0E,4,80,0A000B006E03,E6F90A0023,40FA0A0024DD,4,4,E6F90A,23DD,0,0C,1,2,0,A5,3,00 0E 00 04 00 00 00 80 00 20 03 FF FF 00 00 00 00 00 00 00 00 0A 00 0B 00 6E 03 01 00 00 00 00 00 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 FA 0A 00 24 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 00 00 00 00 DC 06 00 00 00 00 00 00 DC 06 39 0A 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 FA 09 00 00 FA 09 00 00 00 00 00 00 00 00 92 00 00 00 00 00 A5 00 00 00 00 00 05 00 00 00 03 00 2A 25 2E 2E 39 FF FF
see above,0,FOX,81,2761,252,2515,2515,0,1701,32,64,32,2003,0F,5,A5,1000000,62F60A00C6,BCF60A00C6DD,4,4,E7F90A,B2DF,2,6,1,2,0,7F,3,00 0F 00 05 00 00 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 62 F6 0A 00 C6 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 BC F6 0A 00 C6 DD 00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 00 00 00 00 A5 06 00 00 02 00 00 00 A5 06 C9 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 D3 09 00 00 D3 09 00 00 00 00 00 00 00 00 8D 00 00 00 00 00 7F 00 00 00 00 00 00 00 00 00 03 00 26 2F 38 FF FF FF FF
color,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,b156,all
g,5,FOX,81,2761,503,5030,0,0,1701,32,64,32,401,4,0,0,A803A9030000,B1F309001B,E7F90A00B2DF,3,3,E7F90A,B2DF,2,6,1,2,1,70,3,00 04 01 00 00 F3 00 00 00 04 01 00 00 D8 00 00 00 27 01 00 00 A8 03 A9 03 00 00 01 00 00 00 09 00 00 00 00 00 B1 F3 09 00 1B DE 00 00 79 01 00 00 00 00 00 00 3C 09 00 80 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 B2 03 00 00 00 00 00 00 AF 03 00 00 00 00 E7 F9 0A 00 B2 DF 00 00 00 00 6C 00 00 00 A5 06 00 00 02 00 A6 06 A5 06 C9 0A 00 00 06 00 32 00 01 00 64 00 32 00 02 00 00 00 00 00 A6 13 00 00 06 00 00 01 7F B0 00 01 04 00 00 00 70 00 FF FF 04 00 05 00 03 00 03 00 26 2F 38 FF FF FF FF
s,3,NANCY,83,2834,543,5426,2852,0,1779,32,64,32,2003,0A,0,10,AA03AB030000,5BF409002A,99F40A00E4DC,2,2,E6F90A,81DC,2,6,1,2,1,B2,3,00 0A 00 00 00 C8 FD 10 00 20 03 FF FF 00 00 00 00 00 00 00 00 AA 03 AB 03 00 00 03 00 00 00 09 00 00 00 00 00 5B F4 09 00 2A DD 00 00 19 0F 00 80 00 00 00 00 7A 0A 00 80 00 00 00 00 99 F4 0A 00 E4 DC 00 00 00 00 9F 02 00 00 00 00 00 00 A9 02 00 00 00 00 E6 F9 0A 00 81 DC 00 00 00 00 AD 00 00 00 F3 06 00 00 02 00 FB 07 F3 06 12 0B 00 00 06 00 32 00 01 00 64 00 32 00 02 00 24 0B 00 00 32 15 00 00 04 00 00 10 59 12 00 01 03 00 00 00 B2 00 FF FF 03 00 03 00 03 00 03 00 2E 21 2E 23 39 FF FF
s,4,YOUNG,75,2566,507,5066,2810,0,1806,32,64,32,2003,0B,1,CA,A803A9030000,CBF309006D,E7F90A006DDE,3,3,E7F90A,6DDE,0,0C,1,2,1,98,3,00 0B 00 01 00 00 00 CA 00 20 03 FF FF 00 00 00 00 00 00 00 00 A8 03 A9 03 00 00 03 00 00 00 09 00 00 00 00 00 CB F3 09 00 6D DD 00 00 87 0B 00 00 00 00 00 00 7C 0D 00 80 00 00 00 00 E7 F9 0A 00 6D DE 00 00 00 00 B1 03 00 00 00 00 00 00 A2 03 00 00 00 00 E7 F9 0A 00 6D DE 00 00 18 00 93 00 00 00 0E 07 00 00 00 00 06 0A 0E 07 06 0A 00 00 0C 00 32 00 01 00 64 00 32 00 02 00 FA 0A 00 00 CA 13 00 00 05 00 00 10 25 6C 00 01 03 00 00 00 98 00 FF FF 03 00 04 00 01 00 03 00 39 2F 35 2E 27 FF FF
s,0,TERRY,85,2888,501,5012,2820,0,1821,64,64,32,2003,0C,2,5A,AC03AD030000,FFF409004D,4DF50A003DDC,1,1,E7F90A,10DF,2,6,1,2,1,D8,3,00 0C 00 02 00 00 00 5A 00 20 03 FF FF 00 00 00 00 00 00 00 00 AC 03 AD 03 00 00 03 00 00 00 09 00 00 00 00 00 FF F4 09 00 4D DD 00 00 28 00 00 00 00 00 00 00 81 05 00 00 00 00 00 00 4D F5 0A 00 3D DC 00 00 00 00 14 01 00 00 00 00 00 00 6A 01 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 B3 00 00 00 1D 07 00 00 02 00 28 08 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 04 0B 00 00 94 13 00 00 01 00 CC 0B 62 1C 00 01 02 00 00 00 D8 00 FF FF 02 00 00 00 00 00 03 00 34 25 32 32 39 FF FF
s,2,TIM,87,2970,540,5402,2936,0,1834,64,64,32,2003,0D,3,35,AB03AC030000,A8F409002B,F3F40A003DDC,1,0,E6F90A,C6DD,0,0C,1,2,1,D8,3,00 0D 00 03 00 00 00 35 00 20 03 FF FF 00 00 00 00 00 00 00 00 AB 03 AC 03 00 00 03 00 00 00 09 00 00 00 00 00 A8 F4 09 00 2B DE 00 00 AF 06 00 80 00 00 00 00 F8 0A 00 80 00 00 00 00 F3 F4 0A 00 3D DC 00 00 00 00 01 01 00 00 00 00 00 00 D7 00 00 00 00 00 E6 F9 0A 00 C6 DD 00 00 00 00 A4 00 00 00 2A 07 00 00 00 00 62 09 2A 07 9A 0B 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 78 0B 00 00 1A 15 00 00 02 00 00 0B 89 E2 00 01 03 00 00 00 D8 00 FF FF 03 00 02 00 02 00 03 00 34 29 2D FF FF FF FF
g,1,JENNY,77,2617,511,5108,1844,0,1756,64,64,32,2003,0E,4,80,AC03AD030000,0DF5090093,4DF50A005FDD,4,6,E6F90A,23DD,0,0C,1,2,1,96,3,00 0E 00 04 00 00 00 80 00 20 03 FF FF 00 00 00 00 00 00 00 00 AC 03 AD 03 00 00 01 00 00 00 09 00 00 00 00 00 0D F5 09 00 93 DC 00 00 AA 06 00 00 00 00 00 00 62 0D 00 00 00 00 00 00 4D F5 0A 00 5F DD 00 00 00 00 72 04 00 00 00 00 00 00 34 06 00 00 00 00 E6 F9 0A 00 23 DD 00 00 00 00 C8 00 00 00 DC 06 00 00 00 00 8B 08 DC 06 39 0A 00 00 0C 00 32 00 01 00 64 00 64 00 02 00 34 07 00 00 F4 13 00 00 03 00 00 13 69 1C 00 01 03 00 00 00 96 00 FF FF 03 00 01 00 02 00 03 00 2A 25 2E 2E 39 FF FF
see above,0,TERRY,85,2888,501,5012,4852,0,1821,64,64,32,2003,0F,5,A5,59005A008705,EC0B0900E6,DE0B0A00E8ED,8,8,E7F90A,10DF,2,6,1,2,0,7F,3,00 0F 00 05 00 00 00 A5 00 20 03 FF FF 00 00 00 00 00 00 00 00 59 00 5A 00 87 05 03 00 00 00 09 00 00 00 00 00 EC 0B 09 00 E6 ED 00 00 39 05 00 80 00 00 00 00 56 01 00 80 00 00 00 00 DE 0B 0A 00 E8 ED 00 00 00 00 08 08 00 00 00 00 00 00 24 08 00 00 00 00 E7 F9 0A 00 10 DF 00 00 00 00 3A 00 00 00 1D 07 00 00 02 00 1E 07 1D 07 48 0B 00 00 06 00 32 00 01 00 64 00 64 00 02 00 F4 12 00 00 94 13 00 00 00 00 3A 17 C1 CA EB 00 00 00 00 00 7F 00 00 00 00 00 00 00 00 00 03 00 34 25 32 32 39 FF FF
352614 ->456231,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
from ReadWriteMemory import ReadWriteMemory
def swap(f,s):
b = f.split(" ")
c = s.split(" ")
# runspeed2
b[99] = c[99]
b[100] = c[100]
# runspeed1
b[107] = c[107]
b[108] = c[108]
# top speed
b[109] = c[109]
b[110] = c[110]
# acc
b[115] = c[115]
# coop
b[119] = c[119]
# intel
b[121] = c[121]
# stamina2
b[125] = c[125]
b[126] = c[126]
# stamina
b[129] = c[129]
b[130] = c[130]
return(" ".join(b))
print("Copying bytes from ePSXe.exe")
rwm = ReadWriteMemory()
process = rwm.get_process_by_name('ePSXe.exe')
process.open()
byteBuffer = process.readByte(0xbf95b4,1183)
process.close()
allbytes = ""
for x in byteBuffer:
x = "{:02x} ".format(int(x,16))
allbytes += x
allbytes = allbytes.rstrip()
allbytes = allbytes.upper()
front = allbytes[0:104]
pbytes = allbytes[105:]
allsplit = []
n = 492
for i in range(0,len(pbytes),n):
allsplit.append(pbytes[i:i+n])
#Young(silver), Tim(silver) = Winners
#Fox (gold), Terry(silver) = Losers
n0 = allsplit[0]
n1 = allsplit[1]
# c2 to c0 Young -> Terry
n2 = swap(allsplit[2],allsplit[3])
# c3 to c4 Terry -> Young
n3 = swap(allsplit[3],allsplit[2])
n4 = allsplit[4]
n5 = allsplit[5]
n6 = allsplit[6]
patched = front+" "+n0+n1+n2+n3+n4+n5+n6
print(patched)
Chart of bytes:
(https://i.imgur.com/JIQkOP9.png)
Kind of weird it's not 6th on that swap. Leads me to believe some other piece of data matters... Or something to do with that 5/6 place swapping bug. I guess I can test that by swapping 3 and 2.
352614 -> swap 3 and 2 -> 352164
Idk what's going on there. I triple checked it and used a script to generate it. Swapping two chocobos is ugly.
FWIW in the all the same stats test I replaced bytes 100-157
I'm not sure how many answers we'll get looking at just memory.
-
Hmmm. Why do you think there's a boosting stat for the two it wants to win?
Observation. I have no hard evidence, but I've seen the lower speed chocos win over higher speed with better sprint. There might be more going on, but it looked like they were "favored" because they were making better use of their speed and sprint. Otherwise you could just bet on the two with the highest speed stat and win every time.
-
I was playing with https://github.com/ergonomy-joe/ff7-coaster to try and get an understanding of how it works.
There appears to be remnants of code to run the chocobo game as well.
https://github.com/ergonomy-joe/ff7-coaster/blob/4c9a90abe7073406c0eae0f05e84ea3b80c4cc84/NEWFF7/initpath.cpp#L272
https://github.com/ergonomy-joe/ff7-coaster/blob/4c9a90abe7073406c0eae0f05e84ea3b80c4cc84/CoasterMain.cpp#L14
AFAIK he still hasn't released the full source for coaster. Ff7lib.lib is a binary with parts he reverse engineered. I'm not quite sure why there's drama around this file. Super Mario 64 and Zelda OOT both have code on github. The code in ff7lib.lib is likely available from the 1.02 patch eidos released and you will still need assets from coaster.lgp to be able to play the game.
Anyways I tried compiling his code with vs2022 but after adding legacy_stdio_definitions.lib as a linker input, I quickly got an error saying ff7lib.lib has symbols that need to be updated. Which I would need source to do. So I downloaded a copy of vs2008 trial and the directx5sdk. After updating the project configuration to point to my extracted dx5 folder for both inc and lib I got it to compile. Running this compiled binary has been a different beast. When run it opens ff7config.exe, on close it changes the resolution and crashes silently. I added a bunch of print statements and it gets to calling a function in ff7lib.lib that initializes directx, so I really have no idea why it crashed. Pretty sure it has the reg keys its looking for. I added debug messages to the initpath file and it has updated paths to where my install is. I tried ffnx, aali's driver, and even used qemu to emulate a windows 98 computer. Windows 98 doesn't support visual studio 2008. Perhaps with 2005? When the project is compiled with 2005, music plays, but it pagefaults and shows a garbled screen. If anyone has been able to get his project to run I would be interested to know what magic I'm missing.
I found this project https://github.com/FFT-Hackers/FF7-Disassembly where they have a json blob that can be imported into ida with this plugin https://github.com/FFT-Hackers/IDA-Tools to give a bunch of functions names. It adds some names to sub772357 (the sub we broke on when we were writing chocobo names). You don't see this in the pseudecode, but at the top of the function we are taking 0xA4 bytes (164 bytes = chocobo size in memory) and looping through them. The output from ghidra is actually a lot more readable giving you an idea where you are in the 164 bytes.
void FUN_00772357(void)
{
short sVar1;
ushort uVar2;
undefined2 uVar3;
int iVar4;
int iVar5;
undefined4 uVar6;
uint uVar7;
int iVar8;
int iVar9;
ushort extraout_var;
ushort extraout_var_00;
undefined4 *puVar10;
undefined4 *puVar11;
int local_128;
undefined *local_11c;
short local_ec;
int local_e8;
uint local_dc;
int local_d8;
uint local_d4;
undefined4 local_cc;
int aiStack200 [45];
float local_14;
float local_10;
float local_c;
undefined4 local_8;
local_cc = FUN_00676578();
for (local_ec = 0; local_ec < 6; local_ec = local_ec + 1) {
*(short *)(&DAT_00e711d4 + local_ec * 0xa4) = (short)((local_ec * 0xe0) / 6) + 0x10;
}
for (local_ec = 0; local_ec < 0x28; local_ec = local_ec + 1) {
iVar4 = rand();
iVar5 = rand();
uVar3 = *(undefined2 *)(&DAT_00e711d4 + (iVar4 % 6) * 0xa4);
*(undefined2 *)(&DAT_00e711d4 + (iVar4 % 6) * 0xa4) =
*(undefined2 *)(&DAT_00e711d4 + (iVar5 % 6) * 0xa4);
*(undefined2 *)(&DAT_00e711d4 + (iVar5 % 6) * 0xa4) = uVar3;
}
for (local_ec = 0; local_ec < 6; local_ec = local_ec + 1) {
iVar4 = (int)local_ec;
iVar5 = iVar4 * 0xa4;
puVar10 = &DAT_00e71158 + iVar4 * 0x29;
if ((DAT_00e71128 == 0) || (local_ec != 0)) {
sVar1 = *(short *)(&DAT_0097a3e0 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
(&DAT_00e711b0)[iVar4 * 0x52] =
sVar1 + (short)(iVar8 % (int)*(short *)(&DAT_0097a3e2 + (uint)DAT_00dc0af3 * 8));
sVar1 = *(short *)(&DAT_0097a3e4 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
*(short *)(iVar5 + 0xe711ae) =
sVar1 + (short)(iVar8 % (int)*(short *)(&DAT_0097a3e6 + (uint)DAT_00dc0af3 * 8));
*(undefined2 *)(iVar5 + 0xe711b6) = 0x32;
*(undefined2 *)(iVar5 + 0xe711ba) = 100;
uVar6 = rand();
uVar2 = (ushort)((int)uVar6 >> 0x1f);
*(ushort *)(iVar5 + 0xe7115e) = (((ushort)uVar6 ^ uVar2) - uVar2 & 3 ^ uVar2) - uVar2;
sVar1 = *(short *)(&DAT_0097a3e0 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
(&DAT_00e711c4)[iVar4 * 0x29] = (int)sVar1 + iVar8 % 300;
(&DAT_00e711c0)[iVar4 * 0x29] = (&DAT_00e711c4)[iVar4 * 0x29];
(&DAT_00e711b8)[iVar4 * 0x52] = 1;
uVar7 = rand();
*(ushort *)(iVar5 + 0xe711a4) = (-(ushort)((uVar7 & 7) != 0) & 0xfffe) + 2;
*(short *)(iVar5 + 0xe711b4) = *(short *)(iVar5 + 0xe711bc) / 10;
*(undefined2 *)(iVar5 + 0xe711de) = 3;
uVar2 = rand();
*(ushort *)(iVar5 + 0xe711bc) = (uVar2 & 1) * 0x32 + 0x32;
}
else {
(&DAT_00e711b0)[iVar4 * 0x52] = (ushort)DAT_00dc0ae6 + (ushort)DAT_00dc0ae7 * 0x100;
*(ushort *)(iVar5 + 0xe711ae) = (ushort)DAT_00dc0ae8 + (ushort)DAT_00dc0ae9 * 0x100;
*(ushort *)(iVar5 + 0xe711b6) = (ushort)DAT_00dc0aea;
*(ushort *)(iVar5 + 0xe711ba) = (ushort)DAT_00dc0aeb;
uVar6 = rand();
uVar2 = (ushort)((int)uVar6 >> 0x1f);
*(ushort *)(iVar5 + 0xe7115e) = (((ushort)uVar6 ^ uVar2) - uVar2 & 3 ^ uVar2) - uVar2;
(&DAT_00e711c4)[iVar4 * 0x29] = (uint)DAT_00dc0af6 + (uint)DAT_00dc0af7 * 0x100;
(&DAT_00e711c0)[iVar4 * 0x29] = (&DAT_00e711c4)[iVar4 * 0x29];
(&DAT_00e711b8)[iVar4 * 0x52] = 1;
*(ushort *)(iVar5 + 0xe711a4) = (ushort)DAT_00dc0aee;
*(short *)(iVar5 + 0xe711b4) = *(short *)(iVar5 + 0xe711bc) / 10;
switch(DAT_00dc0aed) {
case 1:
*(undefined2 *)(iVar5 + 0xe711de) = 1;
break;
case 2:
*(undefined2 *)(iVar5 + 0xe711de) = 2;
break;
case 3:
case 4:
*(undefined2 *)(iVar5 + 0xe711de) = 0;
break;
default:
*(undefined2 *)(iVar5 + 0xe711de) = 3;
}
*(ushort *)(iVar5 + 0xe711bc) = (ushort)DAT_00dc0aec;
}
if (*(short *)(iVar5 + 0xe711bc) < 0x28) {
*(undefined2 *)(iVar5 + 0xe711aa) = 3;
*(short *)(iVar5 + 0xe711be) = (short)((0x28 - *(short *)(iVar5 + 0xe711bc)) / 10);
}
else if (*(short *)(iVar5 + 0xe711bc) < 0x3c) {
*(undefined2 *)(iVar5 + 0xe711aa) = 2;
*(short *)(iVar5 + 0xe711be) = (short)((0x3c - *(short *)(iVar5 + 0xe711bc)) / 5);
}
else if (*(short *)(iVar5 + 0xe711bc) < 0x50) {
*(undefined2 *)(iVar5 + 0xe711aa) = 1;
*(short *)(iVar5 + 0xe711be) = (short)((0x50 - *(short *)(iVar5 + 0xe711bc)) / 5);
}
else {
*(undefined2 *)(iVar5 + 0xe711aa) = 0;
*(short *)(iVar5 + 0xe711be) = (short)((0x6e - *(short *)(iVar5 + 0xe711bc)) / 5);
}
*(short *)(iVar5 + 0xe711b4) =
(short)((int)((int)*(short *)(iVar5 + 0xe711bc) +
((int)*(short *)(iVar5 + 0xe711bc) >> 0x1f & 7U)) >> 3);
*(undefined2 *)(iVar5 + 0xe711f0) = *(undefined2 *)(DAT_00e7112c + 4);
*(short *)(&DAT_00e711da + iVar5) = local_ec;
*(short *)(&DAT_00e7115c + iVar5) = *(short *)(iVar5 + 0xe711ae) / 2;
*(undefined2 *)(iVar5 + 0xe711a6) = *(undefined2 *)(iVar5 + 0xe711ae);
*(undefined2 *)puVar10 = 10;
*(undefined2 *)((int)&DAT_00e71158 + iVar5 + 2) = 0xb;
*(undefined2 *)(iVar5 + 0xe711dc) = 0;
*(undefined2 *)(iVar5 + 0xe711f2) = 0xffff;
*(undefined2 *)(&DAT_00e711f8 + iVar5) = 0;
*(undefined2 *)(iVar5 + 0xe711f6) = 0;
*(undefined2 *)(iVar5 + 0xe711f4) = 0;
*(undefined2 *)(iVar5 + 0xe711fa) = 0;
iVar9 = 0x100 - *(short *)(&DAT_00e711d4 + iVar5);
*(undefined2 *)(iVar5 + 0xe711ee) = *(undefined2 *)(&DAT_00e711d4 + iVar5);
iVar8 = (int)*(short *)(DAT_00e715fc + *(short *)puVar10 * 0x18) *
(int)*(short *)(&DAT_00e711d4 + iVar5) +
*(short *)(DAT_00e715fc + 8 + *(short *)puVar10 * 0x18) * iVar9;
*(short *)(&DAT_00e71168 + iVar5) = (short)(iVar8 + (iVar8 >> 0x1f & 0xffU) >> 8);
iVar8 = (int)*(short *)(DAT_00e715fc + 2 + *(short *)puVar10 * 0x18) *
(int)*(short *)(&DAT_00e711d4 + iVar5) +
*(short *)(DAT_00e715fc + 10 + *(short *)puVar10 * 0x18) * iVar9;
*(short *)(&DAT_00e7116a + iVar5) = (short)(iVar8 + (iVar8 >> 0x1f & 0xffU) >> 8);
iVar8 = (int)*(short *)(DAT_00e715fc + 4 + *(short *)puVar10 * 0x18) *
(int)*(short *)(&DAT_00e711d4 + iVar5) +
*(short *)(DAT_00e715fc + 0xc + *(short *)puVar10 * 0x18) * iVar9;
*(short *)(&DAT_00e7116c + iVar5) = (short)(iVar8 + (iVar8 >> 0x1f & 0xffU) >> 8);
*(undefined2 *)(iVar5 + 0xe71198) = *(undefined2 *)(&DAT_00e71168 + iVar5);
*(undefined2 *)(iVar5 + 0xe7119a) = *(undefined2 *)(&DAT_00e7116a + iVar5);
*(undefined2 *)(iVar5 + 0xe7119c) = *(undefined2 *)(&DAT_00e7116c + iVar5);
iVar8 = (int)*(short *)(DAT_00e715fc + *(short *)((int)&DAT_00e71158 + iVar5 + 2) * 0x18) *
(int)*(short *)(&DAT_00e711d4 + iVar5) +
*(short *)(DAT_00e715fc + 8 + *(short *)((int)&DAT_00e71158 + iVar5 + 2) * 0x18) * iVar9
;
*(short *)(iVar5 + 0xe71180) = (short)(iVar8 + (iVar8 >> 0x1f & 0xffU) >> 8);
iVar8 = (int)*(short *)(DAT_00e715fc + 2 + *(short *)((int)&DAT_00e71158 + iVar5 + 2) * 0x18) *
(int)*(short *)(&DAT_00e711d4 + iVar5) +
*(short *)(DAT_00e715fc + 10 + *(short *)((int)&DAT_00e71158 + iVar5 + 2) * 0x18) *
iVar9;
*(short *)(iVar5 + 0xe71182) = (short)(iVar8 + (iVar8 >> 0x1f & 0xffU) >> 8);
iVar8 = (int)*(short *)(DAT_00e715fc + 4 + *(short *)((int)&DAT_00e71158 + iVar5 + 2) * 0x18) *
(int)*(short *)(&DAT_00e711d4 + iVar5) +
*(short *)(DAT_00e715fc + 0xc + *(short *)((int)&DAT_00e71158 + iVar5 + 2) * 0x18) *
iVar9;
*(short *)(iVar5 + 0xe71184) = (short)(iVar8 + (iVar8 >> 0x1f & 0xffU) >> 8);
*(undefined2 *)(iVar5 + 0xe71188) = 0;
*(undefined2 *)(iVar5 + 0xe7118a) = 0;
*(undefined2 *)(iVar5 + 0xe7118c) = 0;
*(undefined2 *)(iVar5 + 0xe71190) = 0;
*(undefined2 *)(iVar5 + 0xe71192) = 0;
*(undefined2 *)(iVar5 + 0xe71194) = 0;
uVar3 = FUN_0077cfbf(DAT_00e715fc + 0xc000,DAT_00e715fc + 0xc018);
*(undefined2 *)(iVar5 + 0xe7118a) = uVar3;
(&DAT_00e711c8)[iVar4 * 0x52] = 0;
*(undefined2 *)(iVar5 + 0xe711ca) = 0;
*(short *)(iVar5 + 0xe711ce) = (short)((int)*(short *)(&DAT_00e7115c + iVar5) / DAT_00e7101c);
(&DAT_00e711d6)[iVar4 * 0x52] = 0;
*(undefined2 *)(iVar5 + 0xe711d8) = 0;
local_dc = (int)*(short *)(iVar5 + 0xe71180) - (int)*(short *)(&DAT_00e71168 + iVar5);
local_d8 = (int)*(short *)(iVar5 + 0xe71182) - (int)*(short *)(&DAT_00e7116a + iVar5);
local_d4 = (int)*(short *)(iVar5 + 0xe71184) - (int)*(short *)(&DAT_00e7116c + iVar5);
FUN_006638b0(&local_dc,&local_dc);
uVar3 = FUN_0077cf40(local_dc & 0xffff | (uint)extraout_var_00 << 0x10,
local_d4 & 0xffff | (uint)extraout_var << 0x10);
*(undefined2 *)(iVar5 + 0xe71192) = uVar3;
*(undefined2 *)(iVar5 + 0xe7118a) = *(undefined2 *)(iVar5 + 0xe71192);
}
if (DAT_00dc0af2 != '\0') {
puVar10 = &DAT_00e71158;
puVar11 = &DAT_00e711fc;
for (iVar4 = 0x29; iVar4 != 0; iVar4 = iVar4 + -1) {
*puVar11 = *puVar10;
puVar10 = puVar10 + 1;
puVar11 = puVar11 + 1;
}
_DAT_00e7123c = DAT_00e7120c;
_DAT_00e71240 = DAT_00e71210;
DAT_00e71268 = DAT_00e71268 + ((int)(DAT_00e71268 + (DAT_00e71268 >> 0x1f & 3U)) >> 2);
DAT_00e71264 = DAT_00e71264 + ((int)(DAT_00e71264 + (DAT_00e71264 >> 0x1f & 3U)) >> 2);
DAT_00e71252 = DAT_00e71252 + DAT_00e71252 / 10;
DAT_00e71254 = DAT_00e71254 + DAT_00e71254 / 10;
_DAT_00e7125a = 100;
_DAT_00e71260 = 100;
_DAT_00e71202 = 3;
_DAT_00e71282 = 0;
_DAT_00e7128c = 9;
_DAT_00e7128e = 1;
}
for (local_e8 = 0; local_e8 < 0x2d; local_e8 = local_e8 + 1) {
aiStack200[local_e8] = local_e8;
}
for (local_e8 = 0; local_e8 < 200; local_e8 = local_e8 + 1) {
iVar5 = rand();
iVar8 = rand();
iVar4 = aiStack200[iVar5 % 0x2d];
aiStack200[iVar5 % 0x2d] = aiStack200[iVar8 % 0x2d];
aiStack200[iVar8 % 0x2d] = iVar4;
}
for (local_ec = 0; local_ec < 6; local_ec = local_ec + 1) {
if ((DAT_00dc0af2 == '\0') || (local_ec != 1)) {
local_11c = &DAT_0097cc58 + aiStack200[local_ec] * 7;
for (local_128 = 0; local_128 < 6; local_128 = local_128 + 1) {
(&DAT_00e711e0)[local_128 + local_ec * 0xa4] = *local_11c;
local_11c = local_11c + 1;
}
(&DAT_00e711e0)[local_128 + local_ec * 0xa4] = 0xff;
if ((DAT_00e71128 != 0) && (local_ec != 0)) {
FUN_00773487((int)local_ec,0,0);
}
}
else {
for (local_128 = 0; local_128 < 8; local_128 = local_128 + 1) {
*(undefined *)(local_128 + 0xe71284) = (&DAT_0097c8a8)[local_128];
}
}
}
if (DAT_00e71128 != 0) {
_DAT_00e711e0 = DAT_00dc0adc;
_DAT_00e711e4 = DAT_00dc0ae0;
DAT_00e711e6 = 0xff;
}
for (local_ec = 0; local_ec < 6; local_ec = local_ec + 1) {
iVar4 = DAT_00e719e8 + *(short *)(&DAT_00e711e8 + local_ec * 0xa4) * 0x3c;
if (((DAT_00dc0af2 == '\0') || (local_ec != 1)) && ((DAT_00e71128 == 0 || (local_ec != 0)))) {
switch(*(undefined2 *)(local_ec * 0xa4 + 0xe7115e)) {
case 0:
*(undefined *)(iVar4 + 8) = 0;
*(undefined *)(iVar4 + 7) = 0;
*(undefined *)(iVar4 + 6) = 0;
local_c = (float)(uint)*(byte *)(iVar4 + 6) / _DAT_007b7f28;
local_10 = (float)(uint)*(byte *)(iVar4 + 7) / _DAT_007b7f28;
local_14 = (float)(uint)*(byte *)(iVar4 + 8) / _DAT_007b7f28;
local_8 = 0x3f800000;
FUN_00684e20(&local_14,*(undefined4 *)(*(int *)(iVar4 + 0x34) + 0xb4),local_cc);
break;
case 1:
*(undefined *)(iVar4 + 6) = 0x40;
*(undefined *)(iVar4 + 7) = 0x40;
*(undefined *)(iVar4 + 8) = 0;
local_c = (float)(uint)*(byte *)(iVar4 + 6) / _DAT_007b7f28;
local_10 = (float)(uint)*(byte *)(iVar4 + 7) / _DAT_007b7f28;
local_14 = (float)(uint)*(byte *)(iVar4 + 8) / _DAT_007b7f28;
local_8 = 0x3f800000;
FUN_00684e20(&local_14,*(undefined4 *)(*(int *)(iVar4 + 0x34) + 0xb4),local_cc);
break;
case 2:
*(undefined *)(iVar4 + 6) = 0x40;
*(undefined *)(iVar4 + 7) = 0x10;
*(undefined *)(iVar4 + 8) = 0;
local_c = (float)(uint)*(byte *)(iVar4 + 6) / _DAT_007b7f28;
local_10 = (float)(uint)*(byte *)(iVar4 + 7) / _DAT_007b7f28;
local_14 = (float)(uint)*(byte *)(iVar4 + 8) / _DAT_007b7f28;
local_8 = 0x3f800000;
FUN_00684e20(&local_14,*(undefined4 *)(*(int *)(iVar4 + 0x34) + 0xb4),local_cc);
break;
case 3:
*(undefined *)(iVar4 + 6) = 0;
*(undefined *)(iVar4 + 7) = 0x10;
*(undefined *)(iVar4 + 8) = 0x40;
local_c = (float)(uint)*(byte *)(iVar4 + 6) / _DAT_007b7f28;
local_10 = (float)(uint)*(byte *)(iVar4 + 7) / _DAT_007b7f28;
local_14 = (float)(uint)*(byte *)(iVar4 + 8) / _DAT_007b7f28;
local_8 = 0x3f800000;
FUN_00684e20(&local_14,*(undefined4 *)(*(int *)(iVar4 + 0x34) + 0xb4),local_cc);
}
}
}
return;
}
I discovered the 20 03 bytes are actually if the chocobo thinks its a long or short course. 20 -> DE when on a long course.
-
Ok, I found how run speed is generated.
On pc there are the static memory regions:
Pre: e710fc-e71142
Chocobo 1: e71143-e711e6 -> JENNY
Chocobo 2: e711e7-e7128a -> LE
Chocobo 3: e7128b-e7132e -> TIM
Chocobo 4: e7132f-e713d2 -> TOM
Chocobo 5: e713d3-e71476 -> T
Chocobo 6: e71477-e7151a -> SARA
Chocobo 7: e7151b-e715be -> JENNY
Chocobo 8: e715bf-e71662 -> NULL
c0 starts with: [NULLx20]
c8 is is the first part of c2 not c1: 00 0A 00 00 00 1E 00 35 00 20 03 FF FF [NULL-48] 9A 20 E2 0E [NULL-100]
The FFT-Hackers json blog indicated sub_7AE9C0 actually returns a random value. So renamed that function in ghidra.
Top speed is at e711b0, looking at the ghidra code you'll see:
sVar1 = *(short *)(&DAT_0097a3e0 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
(&DAT_00e711b0)[iVar4 * 0x52] = sVar1 + (short)(iVar8 % (int)*(short *)(&DAT_0097a3e2 + (uint)DAT_00dc0af3 * 8));
At first glance the [iVar4*0x52] was confusing. The value for Top Speed is at e711b0 repeating every 164 bytes.
I believe the psuedocode index is just incorrect as this line would be updating the next chocobos bytes, which doesn't seem right.
Assuming the code index should read [iVar4*0xA4]
The values 97a3e0, 97a3e2, and dc0af3 don't change between chocobos.
And the top speed is randomly generated between a set range.
The run speed value is at e711ae.
sVar1 = *(short *)(&DAT_0097a3e4 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
*(short *)(iVar5 + 0xe711ae) = sVar1 + (short)(iVar8 % (int)*(short *)(&DAT_0097a3e6 + (uint)DAT_00dc0af3 * 8));
The values of 97a3e4, 97a3e6, and dc0af3 never change. So run speed is randomly generated between a range.
Using different values from Top Speed.
The raw value for stamina is at e711c4.
sVar1 = *(short *)(&DAT_0097a3e0 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
(&DAT_00e711c4)[iVar4 * 0x29] = (int)sVar1 + iVar8 % 300;
(&DAT_00e711c0)[iVar4 * 0x29] = (&DAT_00e711c4)[iVar4 * 0x29];
Which uses 97a3e0 and dc0af3. So stamina is randomly generated within a range.
None of these static values are shared anywhere.
Sprinting is at e711a4
uVar7 = rand();
*(ushort *)(iVar5 + 0xe711a4) = (-(ushort)((uVar7 & 7) != 0) & 0xfffe) + 2;
Which is randomly set as well.
We already know cooperation and acceleration are statics, and intel is either 32 or 64, but it doesn't impact race outcome. Unless I did something wrong copying bytes.
Shoutout to ghidra for making this output so easy to read over ida.
The jockey pointer is at e711e8.
iVar4 = DAT_00e719e8 + *(short *)(&DAT_00e711e8 + local_ec * 0xa4) * 0x3c;
...
switch(*(undefined2 *)(local_ec * 0xa4 + 0xe7115e)) {
e719e8 is after the chocobos with the values 28 E0 4E 0F 23
e7115e is byte 28 -> 0,2,2,3,2,1,1,0
C1: Platinum C2: Bronze C3: Bronze C4: Silver C5: Bronze C6: Gold
Which is the jockey color order. That would have been nice to know earlier.
e7115e is randomly generated
uVar6 = rand();
uVar2 = (ushort)((int)uVar6 >> 0x1f);
*(ushort *)(iVar5 + 0xe7115e) = (((ushort)uVar6 ^ uVar2) - uVar2 & 3 ^ uVar2) - uVar2;
There are 4 jockeys:
Case 0 would give 0,0,0; platinum
Case 1 would give 64,64,0; gold
Case 2 would give 64,16,0; bronze... weird
Case 3 would give 0,16,64; silver
I don't know what these values do.
After starting the race, I put a breakpoint when it read a chocobo name and it landed in 6f5b03.
The order on screen is different from the order in memory.
What I believe is happening is depending on the jockey colors the ordering gets altered.
6f5b03 calls 6f564e which has some variables that look similar to the jockey assigned values.
Here's an example
mystery_typeb156 intel_adjb124 intel_typeb104 jockeyb28 order-b156 name Top Speed Raw Top Speed Stamina Raw Stamina Raw Stamina2 Sprinting Run Speed Intelligence Cooperation Acceleration
3 2 2 3 0 ANDY 81 2739 559 5588 3014 0 1899 32 64 32
3 2 2 1 3 GRAHAM 79 2702 523 5232 999 0 1770 32 64 32
3 2 2 0 5 FOX 84 2871 500 5000 2920 0 1762 32 64 32
3 2 0 1 1 JAMES 76 2575 505 5046 1446 0 1797 64 64 32
3 2 2 2 4 JU 76 2588 516 5156 1409 0 1886 32 64 32
3 2 0 3 2 RICA 79 2701 554 5538 2964 0 1779 64 64 32
3 2 0 1 0 JAMES 76 2575 505 5046 3606 0 1797 64 64 32
Andy,James,Rica,Graham,Ju,Fox
James,Andy,Graham,Rica,Fox,Ju
146253 became 412635
I patched the game to only give gold jockeys and the order still got messed up.
E8 E6 C2 03 00 99 33 C2 2B C2 83 E0 03 33 C2 2B C2 ->
E8 E6 C2 03 00 99 33 C2 2B C2 B8 01 00 00 00 90 90
Same with platinum jockeys. 01 -> 00
Here's my script to rip out values. I'm not sure how much more I'll work on this. I'm not quite sure where to go.
from ReadWriteMemory import ReadWriteMemory
def decodeMe(myin):
a =""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
res = ""
smy = myin.split(" ")
for val in smy:
dec = int(val, 16)
if dec >= len(a):
if dec != 255:
print("replacing char: "+val+ " with space")
dec = 0
res += a[dec]
return(res)
def parse(thebytes):
#print(thebytes)
startbytes = thebytes
thebytes = thebytes.split()
nameVal = decodeMe(" ".join(thebytes[157:]))
nameVal = nameVal.rstrip()
stamina = thebytes[130]+thebytes[129]
dec = int(stamina, 16)
staminaValRaw = dec
staminaVal = str(round(dec/10))
stamina2 = thebytes[126]+thebytes[125]
dec = int(stamina2, 16)
stamina2ValRaw = dec
stamina2Val = str(round(dec/10))
dec = int(stamina2, 16)
stamina2Val = "0x"+stamina2+"/10 = "+str(dec/10)
unk1Val = thebytes[123]
intelVal = thebytes[121]
coopVal = thebytes[119]
unk2Val = thebytes[117]
accVal = thebytes[115]
unk3Val = thebytes[113]
speed = thebytes[110]+thebytes[109]
dec = int(speed, 16)
speedValRaw = dec
speedVal = str(round(dec/34))
rs = thebytes[108]+thebytes[107]
dec = int(rs, 16)
runSpeedVal = str(dec)
unk4Val = thebytes[103]
rs = thebytes[100]+thebytes[99]
dec = int(rs, 16)
runSpeed2Val = "0x"+rs+" = "+str(dec)
sprintingVal = int(thebytes[97],16)
first = " ".join(thebytes[0:91])
otherVal = first
classVal = "".join(thebytes[9:11])
b152=str(int(thebytes[151],16))
b27=str(thebytes[27])
b104=str(thebytes[103]) # intel val
b124=str(thebytes[123]) # intel type
#print(b124+","+b104+","+b27+","+b152+","+str(nameVal)+","+str(speedVal)+","+str(speedValRaw)+","+str(staminaVal)+","+str(staminaValRaw)+","+str(stamina2ValRaw)+","+str(sprintingVal)+","+str(runSpeedVal)+","+str(intelVal)+","+str(coopVal)+","+str(accVal)+","+str(classVal))
b2jockey = str(thebytes[1])
b4choco = str(thebytes[3])
b8 = str(thebytes[7])
b22cam1=str("".join(thebytes[21:27]))
b38cam2=str("".join(thebytes[37:42]))
b62 = str("".join(thebytes[61:67]))
# 4
b73 = str(thebytes[72])
# 4
b81=str(thebytes[80])
b85=str("".join(thebytes[85:88]))
b90=str("".join(thebytes[89:91]))
b104=str(thebytes[103])
b114=str(thebytes[113])
b118=str(thebytes[117])
b124=str(thebytes[123])
b141=str(thebytes[140])
b146=str(thebytes[145])
b152=str(thebytes[151])
b156=str(thebytes[155])
return(b156+","+b124+","+b104+","+b27+","+b152+","+str(nameVal)+","+str(speedVal)+","+str(speedValRaw)+","+str(staminaVal)+","+str(staminaValRaw)+","+str(stamina2ValRaw)+","+str(sprintingVal)+","+str(runSpeedVal)+","+str(intelVal)+","+str(coopVal)+","+str(accVal)+","+str(classVal)+","+b2jockey+","+b4choco+","+b8+","+b22cam1+","+b38cam2+","+b62+","+b73+","+b81+","+b85+","+b90+","+b104+","+b114+","+b118+","+b124+","+b141+","+b146+","+startbytes+",")
#print("Copying bytes from ff7_en.exe")
rwm = ReadWriteMemory()
process = rwm.get_process_by_name('ff7_en.exe')
process.open()
byteBuffer = process.readByte(0xE710FC,1600)
process.close()
allbytes =""
for x in byteBuffer:
x = "{:02x} ".format(int(x,16))
allbytes += x
allbytes = allbytes.rstrip()
allbytes = allbytes.upper()
sbytes = allbytes
sbytes = sbytes[213:]
#print(sbytes)
add = "mystery_typeb156,intel_adjb124,intel_typeb104,jockeyb28,order-b156,name,Top Speed,Raw Top Speed,Stamina,Raw Stamina,Raw Stamina2,Sprinting,Run Speed,Intelligence,Cooperation,Acceleration,Willingness to Sprint,b2-jockeyp,b4-chocop,b8,b22-camera1,b38-camera2,b62,b73,b81,b85,b90,b104,b114,b118,b124,b141,b146,all\n"
print(add)
for i in [1, 2, 3, 4, 5, 6, 7, 8]:
tbytes = sbytes[0:492]
a = parse(tbytes)
print(a)
sbytes = sbytes[492:]
Simple script to encode a string
a =""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
#myin = "MIKE!"
myin = input("Please enter a string: ")
res = ""
for b in myin:
pos = a.find(b)
hexN = '{0:x}'.format(int(pos)).upper()
if len(hexN) == 1:
hexN = "0"+hexN
res +=hexN
print(res)
res2 ="".join(reversed([res[i:i+2] for i in range(0, len(res), 2)]))
print("reverse: "+res2)
Simple script to decode a string
a =""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
res = ""
myin = input("Please enter a space separated string: ")
smy = myin.split(" ")
for val in smy:
dec = int(val, 16)
if dec >= len(a):
if dec != 255:
print("replacing char: "+val+ " with space")
dec = 0
res += a[dec]
print(res)
here's a script to patch bytes so they are the same
from ReadWriteMemory import ReadWriteMemory
def patchBytes(a,b):
rwm = ReadWriteMemory()
process = rwm.get_process_by_name('ff7_en.exe')
process.open()
process.writeByte(a,b) #c1
process.writeByte(a+0xa4,b) #c2
process.writeByte(a+0xa4+0xa4,b) #c3
process.writeByte(a+0xa4+0xa4+0xa4,b) #c4
process.writeByte(a+0xa4+0xa4+0xa4+0xa4,b) #c5
process.writeByte(a+0xa4+0xa4+0xa4+0xa4+0xa4,b) #c6
process.writeByte(a+0xa4+0xa4+0xa4+0xa4+0xa4+0xa4,b) #c7
process.close()
def main():
patchBytes(0xe711b0,[0x1D]) #TopSpeed
patchBytes(0xe711b1,[0x0A]) #TopSpeed
patchBytes(0xe711c0,[0xd3]) #Stamina
patchBytes(0xe711c1,[0x09]) #Stamina
patchBytes(0xe711c4,[0xd3]) #Stamina
patchBytes(0xe711c5,[0x09]) #Stamina
patchBytes(0xe711ae,[0xA5]) #RunSpeed
patchBytes(0xe711af,[0x06]) #RunSpeed
patchBytes(0xe711a6,[0xA5]) #RunSpeed
patchBytes(0xe711a7,[0x06]) #RunSpeed
patchBytes(0xe711a4,[0x00]) #Sprinting
patchBytes(0xe711bc,[0x64]) #Intel
patchBytes(0xe711aa,[0x02]) #Inteltype
patchBytes(0xe711b4,[0x06]) #b114
patchBytes(0xe7114a,[0xA5]) #b8
patchBytes(0xe711d4,[0xD8]) #b146
main()
Byte 134 has the correct memory order sometimes.
-
I ran 1000 races for each class and collected data from each. It should be really helpful in testing anyone's theories without waiting for races to finish.
Order was collected using opencv match template
https://anonfiles.com/X01dzdF9xa/ff7_choco_races_1000_7z
We can get close to the right chocobos, but I'm not sure this is going to be solvable.
The point being, with the playstation version I could save state, watch the race, restore state, watch the race again, and get the same end results.
I don't have state saves with PC so I changed the jockey to always be the same and copy all the chocobo bytes, and watch the race.
I then start a new race and overwrite the new chocobo bytes with the old ones and the result is different....
If it's not the jockeys and not the stats assigned to the chocobos then some other randomness must also significantly impact how fast each chocobos runs or what movements they make, and that randomness must get generated when the minigame loads.
-
I wrote a script to go through the data set predict the winners by removing bronzes, sorting by top speed and jockey, and then printing win/loss stats per class.
from operator import attrgetter
def race(chocobos):
global wins,losses
#remove bronze/lowest jockey
chocobos2 = []
lowest = 0
findLowestJockey = False
if findLowestJockey:
lowest = 5
for c in chocobos:
if c.jockey < lowest:
lowest = c.jockey
for c in chocobos:
if c.jockey != lowest:
chocobos2.append(c)
# we need to select 2 winners so,
# if < 2 chocobos don't delete any
if len(chocobos2) < 2:
chocobos2 = chocobos
# Sort by top speed, then jockey
chocobos2.sort(key=attrgetter('topspeed','jockey'),reverse=True)
# check if we predicted the correct winners
success = 0
if chocobos2[0].place <3:
success +=1
if chocobos2[1].place <3:
success +=1
if len(chocobos2)> 2:
if chocobos2[2].place <3:
success += 1
if success >1:
wins +=1
else:
losses += 1
class Chocobo:
def __init__(self,name,ts,stamina,sprinting,jockey,course,order,place):
self.topspeed = ts
self.stamina = stamina
self.sprinting = sprinting
self.jockey = jockey
self.name = name
self.course = course
self.order = order
self.place = place
def __str__(self):
return "name=%s speed=%s stamina=%s sprinting=%s jockey=%s course=%s order=%s place=%s\n" % (self.name, self.topspeed, self.stamina, self.sprinting, self.jockey, self.course, self.order, self.place)
def __repr__(self):
return str(self)
def parse_csv(theFile):
with open(theFile) as f:
data = f.read()
sdata = data.split("win-order,")
for x in sdata:
chocobos = []
if "order-b152" in x:
lines = x.split("\n")
chocobos = []
for i in range(0,len(lines)):
if i >0 and i <7:
sline = lines[i].split(",")
name = sline[6]
place = sline[0]
jockey = int(sline[5])
# bronze -> silver -> gold -> plat
if jockey == 0: #plat
jockey = 3
elif jockey == 1: #gold
jockey = 2
elif jockey == 2: #bronze
jockey = 0
elif jockey == 3: #silver
jockey = 1
else:
print("not a jockey")
speed = sline[7]
stamina = sline[9]
end_stamina = sline[11]
sprinting = sline[12]
runspeed = sline[13]
intel = sline[14]
course = sline[17]
order = i-1
c = Chocobo(name,int(speed),stamina,int(sprinting),jockey,course,int(order),int(place))
chocobos.append(c)
if len(chocobos) == 6:
race(chocobos)
def run_rank(rank,thefile):
global wins,losses,totalwins,totallosses
wins = 0
losses = 0
parse_csv(thefile)
avg = round(((wins / (wins + losses) * 100)),2)
print(rank+"\t"+str(wins)+"\t"+str(losses)+"\t"+str(avg))
totalwins += wins
totallosses += losses
if __name__ == '__main__':
global wins,losses,totalwins,totallosses
win,losses,totalwins,totallosses = 0,0,0,0
print("Class\tWins\tLosses\tAvg")
run_rank("C","c1000.csv")
run_rank("B","b1000.csv")
run_rank("A","a1000.csv")
run_rank("S","s1000.csv")
avg = round(((totalwins / (totalwins + totallosses)) * 100),2)
print("total\t"+str(totalwins)+"\t"+str(totallosses)+"\t"+str(avg))
Class Wins Losses Avg
C 908 92 90.8
B 921 79 92.1
A 919 81 91.9
S 914 86 91.4
total 3662 338 91.55
On cases where it's wrong a lot of them are when there are sprinters.
Digging through the data I haven't made any good correlations.
I'm guessing it has something to do with a combination of sprinting, intelligence, run speed, and jockey color.
-
I learned to use python pandas and the groupby functions was able to quickly answer some questions.
import pandas as pd
adf = pd.read_csv('b1000.csv')
adf = adf.dropna()
adf = adf.drop(['order-b152','mystery_typeb156','intel_adjb124','intel_typeb104','all'],1)
adf = adf.drop(['b4-chocop','b73','b81','b104','b118','b124','b141','Cooperation','Acceleration'],1)
adf = adf.drop(['b8','b22-camera1','b38-camera2','b62','b85','b90','b114','b146','b2-jockeyp'],1)
# change rank bronze->plat->silver->gold
adf.loc[adf['jockeyb28'] == "0",'jockey'] = "3" # plat #1
adf.loc[adf['jockeyb28'] == "1",'jockey'] = "2" # gold #
adf.loc[adf['jockeyb28'] == "2",'jockey'] = "0" # bronze #0
adf.loc[adf['jockeyb28'] == "3",'jockey'] = "1" # silver #2
adf.loc[adf['Sprinting'] == "2",'Sprinting'] = "1" # Change sprinting to be 1 instead of 2
dfs = []
for i in range(0,len(adf),8):
tempdf = adf.iloc[i:i+6].reset_index(drop=True)
tempdf.iloc[0]['Willingness to Sprint'] = tempdf.iloc[1]['Willingness to Sprint']
dfs.append(tempdf)
ndf = pd.concat(dfs)
cols = ndf.columns.drop(['Willingness to Sprint','name'])
ndf[cols] = ndf[cols].apply(pd.to_numeric,errors='coerce')
ndf['Willingness to Sprint'] = ndf['Willingness to Sprint'].astype(str)
ndf['name'] = ndf['name'].astype(str)
print("Sorting by Win-Order")
result = ndf.groupby(['win-order']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by Sprinting")
result = ndf.groupby(['Sprinting']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by Intelligence")
result = ndf.groupby(['Intelligence']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by sprinting and intelligence")
result = ndf.groupby(['Sprinting','Intelligence']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by sprinting and jockey")
result = ndf.groupby(['Sprinting','jockey']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by sprinting, jockey, and intelligence")
result = ndf.groupby(['Sprinting','jockey','Intelligence']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by jockey, sprinting, and intelligence")
result = ndf.groupby(['jockey','Sprinting','Intelligence']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by jockey and intelligence")
result = ndf.groupby(['jockey','Intelligence']).mean().sort_values('win-order',ascending=True)
result = ndf.groupby(['jockey','Intelligence']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by jockey and course")
result = ndf.groupby(['jockey','Willingness to Sprint']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by jockey, course, and intel")
result = ndf.groupby(['jockey','Willingness to Sprint','Intelligence']).mean().sort_values('win-order',ascending=True)
print(result)
print("Sorting by jockey, course, and sprinting")
result = ndf.groupby(['jockey','Willingness to Sprint','Sprinting']).mean().sort_values('win-order',ascending=True)
print(result)
jockeyb28 Top Speed Raw Top Speed Stamina Raw Stamina Raw Stamina2 Sprinting Run Speed Intelligence jockey
win-order
0 1.294 99.497 3383.286 629.596 6295.898 2517.730 0.131 2391.816 85.75 2.010
1 1.335 98.224 3339.311 629.063 6290.610 2459.703 0.115 2359.365 75.00 1.987
2 1.407 96.652 3285.818 629.828 6298.272 2398.962 0.119 2342.110 73.05 1.881
3 1.507 95.789 3257.094 630.105 6301.026 2256.965 0.118 2338.647 71.60 1.595
4 1.663 95.841 3259.200 630.472 6304.766 2080.643 0.149 2343.339 72.85 1.057
5 1.831 96.326 3274.975 629.105 6291.018 1762.689 0.127 2324.116 70.95 0.459
The higher your top speed the more likely you are to win
On average stamina is about the same
There's a 1/8 chance you'll be sprinting
The higher your Run Speed the more likely you are to win
The higher your Intelligence the more likely you are to win
The higher your Jockey in the order bronze->silver->gold->plat the more likely you are to win
win-order jockeyb28 Top Speed Raw Top Speed Stamina Raw Stamina Raw Stamina2 Run Speed Intelligence jockey
Sprinting
0 2.492272 1.502576 97.046938 3299.790689 629.557146 6295.561152 2243.590727 2350.134135 74.775806 1.500859
1 2.553360 1.530962 97.109354 3301.028986 630.645586 6306.395257 2263.548090 2348.274045 75.494071 1.479578
There's a ~50:50 it'll be a smart or dumb sprinter. On average sprinting doesn't seem to affect: intel, top speed, run speed, or jockey values. Something we already knew from reversing but good to reinforce.
win-order jockeyb28 Top Speed Raw Top Speed Stamina Raw Stamina Raw Stamina2 Run Speed jockey
Sprinting Intelligence
0 100 2.218329 1.522526 97.063150 3300.390065 629.576819 6295.778206 2318.897959 2348.717366 1.486330
1 100 2.273902 1.550388 97.188630 3303.116279 630.591731 6305.808786 2327.726098 2350.826873 1.457364
0 50 2.761346 1.482980 97.031014 3299.201967 629.537821 6295.347958 2169.622163 2351.525719 1.515129
1 50 2.844086 1.510753 97.026882 3298.857527 630.701613 6307.005376 2196.782258 2345.618280 1.502688
If your chocobo is smart it will place on average ~0.5 ranks better
win-order jockeyb28 Top Speed Raw Top Speed Stamina Raw Stamina Raw Stamina2 Run Speed Intelligence
Sprinting jockey
0 3 1.861621 0.0 96.997706 3298.064985 629.915902 6299.232416 2990.270642 2351.064220 73.967890
2 1.911518 1.0 96.919908 3295.415713 629.495042 6294.860412 1238.028986 2350.661327 74.980931
1 1.921970 3.0 97.110606 3302.078788 629.346212 6293.474242 2985.032576 2347.711364 75.340909
1 3 1.928962 0.0 97.229508 3305.409836 630.103825 6300.961749 2928.617486 2337.163934 75.409836
2 1.989362 1.0 97.239362 3305.585106 629.494681 6295.106383 1279.276596 2350.632979 74.202128
1 2.000000 3.0 96.989899 3296.818182 631.439394 6314.393939 3066.070707 2349.363636 76.010101
0 0 4.288786 2.0 97.159754 3303.609831 629.473118 6294.694316 1754.290323 2351.125192 74.807988
1 0 4.289474 2.0 96.989474 3296.689474 631.478947 6314.463158 1760.578947 2355.505263 76.315789
Plats are most likely to win, then golds, then silvers
win-order jockeyb28 Top Speed Raw Top Speed Stamina Raw Stamina Raw Stamina2 Sprinting Run Speed
jockey Willingness to Sprint Intelligence
2 2003 100 1.396783 1.0 97.061662 3300.072386 629.705094 6297.083110 2090.383378 0.126005 2344.648794
3 DE03 100 1.456044 0.0 97.156593 3303.412088 631.035714 6310.373626 2544.604396 0.148352 2353.222527
2003 100 1.620787 0.0 96.862360 3293.235955 629.308989 6293.202247 3441.238764 0.109551 2348.660112
1 DE03 100 1.629921 3.0 97.223097 3305.482940 629.133858 6291.223097 2535.677165 0.133858 2344.900262
2 DE03 100 1.670241 1.0 96.825737 3292.549598 628.994638 6289.865952 992.037534 0.117962 2349.037534
1 2003 100 1.680307 3.0 96.913043 3295.460358 630.506394 6305.120205 3448.478261 0.132992 2347.148338
3 DE03 50 2.061425 0.0 96.972973 3296.948403 630.078624 6300.855037 2568.179361 0.127764 2347.132678
2 2003 50 2.118110 1.0 97.073491 3300.194226 629.325459 6293.228346 1276.524934 0.144357 2354.763780
1 DE03 50 2.210938 3.0 97.164062 3303.692708 629.570312 6295.661458 2579.807292 0.098958 2352.401042
2003 50 2.226519 3.0 97.082873 3301.055249 629.223757 6292.386740 3431.577348 0.157459 2347.207182
3 2003 50 2.313187 0.0 97.115385 3302.381868 629.302198 6293.043956 3435.837912 0.104396 2348.664835
2 DE03 50 2.497312 1.0 96.876344 3293.865591 629.959677 6299.435484 611.454301 0.112903 2354.102151
0 2003 100 4.078125 2.0 97.026042 3299.354167 629.473958 6294.583333 1782.341146 0.148438 2351.864583
DE03 100 4.270718 2.0 97.580110 3316.770718 629.497238 6295.077348 1721.218232 0.118785 2352.734807
50 4.347107 2.0 96.955923 3296.446281 629.187328 6291.724518 1721.327824 0.126722 2353.267218
2003 50 4.462141 2.0 97.005222 3298.793734 630.715405 6307.065274 1791.785901 0.114883 2349.005222
Golds burn more stamina
Golds are better at short courses, plats are better at long
Silvers are pretty close to plats on shorts and golds on longs
Bronzes are garbage
Sprinting makes them change ranking more in this case too.
jockey Willingness to Sprint Sprinting
2 2003 1 1.637255 1.0 97.647059 3319.509804 628.745098 6287.666667 1705.245098 2351.235294 73.039216
3 DE03 1 1.773585 0.0 97.745283 3322.113208 628.575472 6286.018868 2553.867925 2340.820755 75.471698
0 1.775940 0.0 96.950376 3296.475188 630.842105 6308.430075 2557.556391 2351.472180 73.308271
2 2003 0 1.780675 1.0 96.976994 3297.102761 629.633436 6296.303681 1675.052147 2349.529141 75.000000
1 DE03 0 1.918639 3.0 97.192308 3304.588757 629.443787 6294.325444 2557.689349 2349.183432 74.408284
2003 0 1.925466 3.0 97.024845 3299.444099 629.243789 6292.580745 3433.610248 2346.166149 76.319876
DE03 1 1.943820 3.0 97.202247 3304.550562 628.662921 6286.808989 2558.887640 2344.730337 78.651685
3 2003 0 1.950233 0.0 97.046656 3299.709176 628.958009 6289.720062 3437.790047 2350.642302 74.650078
2 DE03 0 2.040971 1.0 96.863429 3293.746586 629.358118 6293.432473 805.647951 2351.781487 74.962064
1 2003 1 2.045872 3.0 96.816514 3290.504587 633.706422 6336.917431 3480.192661 2353.146789 73.853211
3 2003 1 2.142857 0.0 96.519481 3282.415584 632.207792 6321.532468 3444.506494 2332.129870 75.324675
2 DE03 1 2.406977 1.0 96.755814 3289.069767 630.383721 6303.930233 774.058140 2349.918605 75.581395
0 2003 0 4.268769 2.0 97.064565 3300.714715 630.024024 6300.144144 1786.487988 2349.516517 74.549550
1 4.277228 2.0 96.693069 3288.257426 630.554455 6305.247525 1790.811881 2356.504950 78.217822
DE03 1 4.303371 2.0 97.325843 3306.258427 632.528090 6324.921348 1726.269663 2354.370787 74.157303
0 4.309748 2.0 97.259434 3306.641509 628.896226 6288.987421 1720.573899 2352.809748 75.078616
Taking into account golds are better than plats at short courses on speed ties we get:
Class Wins Losses Avg
C 911 89 91.1
B 922 78 92.2
A 919 81 91.9
S 915 85 91.5
total 3667 333 91.67
Which is only very marginally better.
I'm not sure how to add intel or the effect of course on jockey and sprinting
Sorting by intel then run speed gives a pretty good estimate too
Class Wins Losses Avg
C 898 102 89.8
B 893 107 89.3
A 903 97 90.3
S 899 101 89.9
total 3593 407 89.83
as does sorting by intel, top speed, then run speed
Class Wins Losses Avg
C 918 82 91.8
B 910 90 91.0
A 914 86 91.4
S 909 91 90.9
total 3651 349 91.27
-
(https://i.imgur.com/v1kAU9W.jpeg)
https://anonfiles.com/z63aZ2Ifx0/chocotrainer_zip
I wrote a python trainer to quickly edit values. Because of the extreme similarity between the memory layouts between pc and psx, it works on both pc (ff7_en.exe) and on a playstation emulator (ePSXe.exe). You will need to manually update the process name and base address used by your computer. I recommend using cheat engine to search for the name of the first chocobo (ie: PAU = 0x352130, my string encoding script can generate this) and counting up 164 bytes in the memory viewer.
Updating the jockey does not update the color displayed.
Setting 2byte values to less than 256 does not work (how I wrote the trainer, easy to change if need be).
Setting run speed -> 256 has the chocobo run at a really slow speed
Setting top speed -> 256 has the chocobo sprint at a really slow speed
Bronze is the only jockey that has a variable run speed (run2) which will adjust well above or below run1.
If stamina is set really low, none of them will choose to run fully out of stamina
Whether or not a chocobo gets stuck behind another seems to be largely luck based. I think intel helps, but idk how much.
With save states, I've observed, sometimes increasing a chocobos run speed/top speed will cause it to rank lower.
Here's the best I've done so far at guessing the winners.
#evaluator.py
from operator import attrgetter
import copy
count = 0
def merge(c1,c2):
for i in range(len(c1)):
c1[i].val = i
for i in range(len(c2)):
c2[i].val = i
for i in range(len(c1)):
for y in c2:
if c1[i].name == y.name:
c1[i].val += y.val
return c1
def race(chocobos):
#remove bronze/lowest jockey
chocobos2 = []
lowest = 0
course = ""
for c in chocobos:
if c.place != 0:
course = c.course
for c in chocobos:
if c.jockey != lowest:
chocobos2.append(c)
# we need to select 2 winners so,
# if < 2 chocobos don't delete any
if len(chocobos2) < 2:
chocobos2 = chocobos
chocobos3 = copy.deepcopy(chocobos2)
# Sort by top speed, then jockey
chocobos2.sort(key=attrgetter('topspeed','jockey'),reverse=True)
# Sort by intel and run speed
chocobos3.sort(key=attrgetter('runspeed'), reverse=True)
#chocobos4 = copy.deepcopy(chocobos2)
#chocobos4.sort(key=attrgetter('intel'), reverse=True)
# check if we predicted the correct winners
#chocobos2 = merge(chocobos2,chocobos3).copy()
#chocobos2 = merge(chocobos2,chocobos4).copy()
notchocobos2 = copy.deepcopy(chocobos2)
for i in range(len(notchocobos2)):
notchocobos2[i].val = float(i)
nothing = 0
for i in range(len(chocobos2)):
chocobos2[i].val = float(i)
# if we are a top runner bump it up
for j in range(len(chocobos3)):
if chocobos2[i].name == chocobos3[j].name:
if j == 0:
chocobos2[i].val -= 3
if j == 1:
chocobos2[i].val -= 2
if j == 2:
chocobos2[i].val -= 0.5
if j == 3:
chocobos2[i].val += 0
if j == 4:
chocobos2[i].val += 1
if j == 5:
chocobos2[i].val += 2
# if we are smart bump it up
if chocobos2[i].intel == 100:
chocobos2[i].val -= 2
# adjust for jockey course advantage/disadvantage
if "2003" in course:
if chocobos2[i].jockey == 3:
chocobos2[i].val += 0.5
if chocobos2[i].sprinting == 2:
chocobos2[i].val += .5
elif chocobos2[i].jockey == 2:
chocobos2[i].val -= 0.5
if chocobos2[i].sprinting == 2:
chocobos2[i].val -= .5
else:
nothing +=1
else:
if chocobos2[i].jockey == 3:
chocobos2[i].val -= 0.5
if chocobos2[i].sprinting == 2:
chocobos2[i].val -= .5
elif chocobos2[i].jockey == 2:
chocobos2[i].val += 0.5
if chocobos2[i].sprinting == 2:
chocobos2[i].val += .5
else:
nothing +=1
chocobos2.sort(key=lambda x: (x.val,-x.topspeed, -x.jockey))
success = 0
if chocobos2[0].place <3:
success +=1
if chocobos2[1].place <3:
success +=1
if len(chocobos2)> 2:
if chocobos2[2].place <3:
success += 1
#print("Predicted: "+str(chocobos2))
#input("Hello")
if success >1:
return 1
else:
#print("og: "+str(notchocobos2))
#print("predicted: "+str(chocobos2))
chocobos2.sort(key=attrgetter('place'))
#print("actual: "+str(chocobos2))
#input("here")
return 2
class Chocobo:
def __init__(self,name,ts,stamina,sprinting,jockey,jockey2,course,order,place,intel,runspeed):
self.topspeed = ts
self.stamina = stamina
self.sprinting = sprinting
self.jockey = jockey
self.jockey2 = jockey2
self.name = name
self.course = course
self.order = order
self.place = place
self.intel = intel
self.runspeed = int(runspeed)
self.rss = int(runspeed)+int(ts)
self.val = float(0)
def __str__(self):
return "name=%s speed=%s stamina=%s sprinting=%s jockey=%s ogjockey=%s course=%s order=%s place=%s intel=%s rs=%s rss=%s val=%s\n" % (self.name, self.topspeed, self.stamina/10, self.sprinting, self.jockey, self.jockey2, self.course, self.order, self.place, self.intel, self.runspeed, self.rss, self.val)
def __repr__(self):
return str(self)
def parse_csv(theFile):
wins = 0
losses = 0
with open(theFile) as f:
data = f.read()
sdata = data.split("win-order,")
for x in sdata:
chocobos = []
if "order-b152" in x:
lines = x.split("\n")
chocobos = []
for i in range(0,len(lines)):
if i >0 and i <7:
sline = lines[i].split(",")
name = sline[6]
place = sline[0]
jockey = int(sline[5])
# bronze -> silver -> gold -> plat
if jockey == 0: #plat
jockey = 3
elif jockey == 1: #gold
jockey = 2
elif jockey == 2: #bronze
jockey = 0
elif jockey == 3: #silver
jockey = 1
else:
print("not a jockey")
speed = sline[7]
stamina = sline[10]
end_stamina = sline[11]
sprinting = sline[12]
runspeed = sline[13]
intel = sline[14]
course = sline[17]
order = i-1
c = Chocobo(name,int(speed),int(stamina)/2,int(sprinting),jockey,jockey,course,int(order),int(place),int(intel),int(runspeed))
chocobos.append(c)
if len(chocobos) == 6:
res = race(chocobos)
if res == 1:
wins +=1
elif res == 2:
losses +=1
else:
print("how")
return wins,losses
def run_rank(rank,thefile):
w,l = parse_csv(thefile)
avg = round(((w / (w + l) * 100)),2)
print(rank+"\t"+str(w)+"\t"+str(l)+"\t"+str(avg))
return w,l
if __name__ == '__main__':
tw,tl = 0,0
print("Class\tWins\tLosses\tAvg")
w,l = run_rank("C","c1000.csv")
tw += w
tl += l
w,l = run_rank("B","b1000.csv")
tw += w
tl += l
w,l = run_rank("A","a1000.csv")
tw += w
tl += l
w,l = run_rank("S","s1000.csv")
tw += w
tl += l
avg = round(((tw / (tw + tl)) * 100),2)
print("total\t"+str(tw)+"\t"+str(tl)+"\t"+str(avg))
Class Wins Losses Avg
C 940 60 94.0
B 943 57 94.3
A 957 43 95.7
S 951 49 95.1
total 3791 209 94.77
-
I took another look at this and wanted to consider possible RNG manipulation and I think I've found something.
With bizhawk on the psx version I found there is a sole single 4 byte memory address at 0x51568 that when frozen will generate the same chocobo races, winning items, and end result. That address starts at 0 and increments seemingly by one for every frame after the playstation logo, I stop watched it at ~33/s. We can power cycle the console and have it always start at 0. Using the bizhawk emulator we can set the seed, frame perfectly start the race, and pull out the results, then repeat for a window of time we care about. So if it takes 60s to run up to the chocobo counter from a save we would generate the races from 60s to 70s to make a 10s window of the races where we know the result. This is a much smaller task instead of needing to generate 4.3 billion races per class, now we only need to generate 300 races per class. The timer method seems like a very viable strategy, I just need to script up something to pull out the race data to search through and confirm.
-
Ok I wrote a macro to automate binhawk to edit the memory address, run the race, pull out the win order with opencv, dump the memory, pull the chocobo stats, and repeat.
The 10 second window is actually ~600 of the counter at 0x51568, I timed myself running from the save at the ropeway station which took about 70s, I then added 20s for padding.
At the 90 second mark you should be at frame 5400.
To make matters worse whether or not it is a short or long course is a 50/50 determined by 0x95DC8
So we'll have to guess two tickets and do 1200 automations ~1min/piece for each rank.
It's going to take a while to run this. About 20 hrs for each rank assuming the script doesn't break.
I'm a little concerned chocobo behavior might be linked to some other variable and and I'm gonna sink a lot of time to be able to only get a 95% or so level of accuracy.
I won't be able to test this until 20hrs of automation run without issue.
-
Ok the first batch finished and the data set is confirmed working for class B short courses with PSX for the 90-100s window on actual hardware :)
https://pastebin.com/ChgYj9kD
You can also use a PS2 with fast load as well. I'll share once the script runs for all the classes.
Xbox/iOS/PS4/PC does not work. They must not count frames to seed the minigame. I'm not sure what it's doing, perhaps @nfitc1 could chime in.
The pointers you listed here don't contain values that change
https://forums.qhimm.com/index.php?topic=11514.0
-
Taking a look at the PC side of things. Random should be baked into kernel.bin/kernel2.bin so that makes me think it should share how it's generated with the psx.
With cheat engine I found the variable that determines course length is a single byte at FF7_EN.exe+8BF588 which can be frozen using a 1ms interval as a thread always creating long or short races. That's promising.
FF7_EN.exe+5A070C appears to be incrementing with each frame. I can't progress the game frame by frame to verify, but it does increase by 600/10s. This counter starts once you load a save. The names don't match and the speed and stamina values in our time window from PSX do not work for when this value gets to 5400. I tried freezing the value but it does not create the same chocobos and neither does nulling out what increments this value. You can find it in ghidra at FUN_0040ab81 which 0xDC08B8 stores the in game time and increments the counter. Cheat engine can null out the change by searching for what modifies that address. You can also change A1 0C 07 9A 00 83 C0 01 to A1 0C 07 9A 00 90 90 90 with hxd to have it not add one to the counter. Telling cheat engine to follow what accesses this value doesn't show anything extra on the betting menu load, although it doesn't show anything for the course byte either which we know impacts the result.
When I pause with the counter not nulled or patched then unpause the game the frame counter jumps by the time it was paused, kinda weird.
-
PSX data set complete. I wrote a tool to search it and recorded a video (https://www.youtube.com/watch?v=jxXfq9jtcNk (https://www.youtube.com/watch?v=jxXfq9jtcNk)) of it working. The tool is available on my github at https://ff7man.github.io/rng.html (https://ff7man.github.io/rng.html).
-
I took another go at things to try and understand the pc side.
FF7_EN.exe+5A8730 -> Frames since last button press
FF7_EN.exe+5A8718 -> Windows uptime in milliseconds
FF7_EN.exe+5A8720 -> Windows uptime in milliseconds
FF7_EN.exe+9DFEB0 -> Windows uptime in milliseconds
FF7_EN.exe+9E65BC -> Windows uptime in milliseconds
FF7_EN.exe+8C0990 -> Direction person in background walks
I froze these values by nulling out the operations that increment the counters with nops in cheat engine.
I also did the same with IGT, the byte that sets the course, and the frame counter, but each race still generates different stats...
Taking a look at ghidra
sub_772357 -> Sets memory for chocobo attributes, calling random multiple times
sub_7ae9c0(rand via fft-hackers) -> Gets a random value by calling sub_40de00 (_getptd via fft-hackers) then modifying it and returning a value less than 0x7fff (32,767)
sub_40dee0(_getptd via fft-hackers) ->
Calls TlsGetValue(dwTlsIndex) where dwTlsIndex when called from 7ae9c0 it is always 2B.
Thread local storage is a way for Windows to store a variable that can be shared between threads
https://guidedhacking.com/threads/thread-local-storage-tls-variants-how-to-read-them.17036/
Task Manager shows my ff7_en.exe to have 22 threads, modifying the process name and updating the threads 10->22 in DynamicTLSExternal.exe
Dumps some data... but it doesn't make sense to me.
Looking for references that call TlsSetValue we get:
40df16 -> Current function, updating the value if it is 0
40de86 -> Calls TlsAlloc and sets lpTlsValue using 410e30(1,0x74) -> which then gets complicated
40e008 -> retrieves the value, gets 6 sets of 4 bytes and does something with them, then sets the value to 0
7b0ad0 -> sets the value to whatever calls 7b0aa0 -> which is set to the threadid which was set from 410e30(1,0x74)
410e30 uses 007beff4
calls 40cb10(9) -> uses 7bbf08 -> starts critical section
calls 411240() -> 7beff0, does data manip with for loops -> gets more complicated
calls 40cb90(9) -> uses 7bbf08 -> ends critical section
Allocates memory on the heap
checks to make sure 9a0c14 is 0
updates 9a0c18 to be 0x74
loops
It then uses sub_410e30(calloc via fft-hackers) to allocate 116 bytes of memory and updates the value if its not zero...
Which doesn't make sense as calloc should zero the values.
Calloc itself is a tad messy and ida and ghidra show almost entirely different psuedocode for it.
In ida it heap allocates, loops a few times, calls some weird functions which also loop, and memsets to 0
In ghidra it does a heapalloc zeroing memory then does a bunch of looping changing various values
Tbh I'm not sure what's going on... It doesn't appear to be using random or system time anywhere... But something must be changing the data from one frame to the next.
If it succeeds at setting the thread value with TlsSetValue() it'll call sub_40dec0(lpTlsValue) (aka _initpd) which just takes the 20th byte and puts it to 1 and the 80th byte to the value at address 0x7bc128 (05?)
To take a different approach.
Using Cheat Engine I overwrite the return value from rand() to always give the same value (4).
This can be done by looking at the memory at 7ae9e7 and modifying the op code to be mov, eax, 4
Upon starting the chocobo betting screen the game gets stuck in a loop near 0x77737D in sub_7772ae. I'm not sure why.
Changing the value at 7ae9e7 to a different value (3) and clicking on the ff7 window will show a gray screen.
Again stuck in a loop in the same space, changing to (2) and clicking on the ff7 window and the game loads.
All the chocobos have the same stats and jockey color 88/300 in class B and run in a single file line neat.
On reruns they are the same :) too bad we had to tamper the game to do this.
5,3,2-> gold 88/300
6,3,2-> bronze 88/300
7,3,2-> silver 88/300
8,3,2-> plat sprinting 88/300
9,3,2-> hangs -> 1 -> gold 88/300
a,3,2-> bronze 88/301
b,3,2-> silver 88/301
c,3,2-> hangs ->1 -> plat 88/301 not sprinting
d,3,2-> gold 88/301
f,3,2-> plat sprinting 88/301
So the first hang point is the jockey and if it is a multiple of 8 it's sprinting.
After some testing I found it's definitely hanging because it is waiting for a new random to get new item values.
If it already has an item it asks rand again in a loop until different.
1 = Potion
2 = Mega-elixir
3 = Hi potion
6 = Turbo Ether
7 = Swift Bolt
Names are stored in memory close to FF7_EN.exe+57CC74 (JOHN)
Breaking on memory write shows 0x773063 in sub_772357
Which shows the chocobo names are set via two random calls. Random is not called for each chocobo.
In a race random is called ~23,497 times
Loading the betting screen random is called 720 times
Look for what accesses FF7_EN.exe+8BF588 shows nothing... which is weird as it determines if the race is gonna be long or short.
I want to say I'm closer to solving the PC side, but the more I dig the more confusing it gets to me.
-
It would be nice if we could save states and reload them on pc.
I installed dosbox-x with ff7 in windows 98. When you take a save state and load it, the screen goes black.
I installed yuzu with ff7 to emulate the switch version, but the game is not supported, and only plays the loading screen music with a black screen.
I installed vmware workstation and created a virtual machine with ff7 steam installed and it is able to save and reload snapshots.
So
I started the game and the moment after I accepted the race, but before the dialog closed, I swapped windows pausing the game.
I noticed the frame counter still increases even when the game is paused. This might explain the frame counter not being used.
Regardless I could then create a snapshot and run the race. When I restored the snapshot I could wait seconds later before unpausing the game and get the same chocobo stats.
That's promising.
Just as a test I took a new snapshot on the menu and restored it, I noticed I'm also get the same chocobo stats no matter what else I do... that doesn't seem right.
I took yet another new snapshot to verify. This time after loading the save and it too generates the same chocobos...
Either the vmware snapshots are causing problems or it's setting some variable on game load and race finish.
The course selector byte is still incrementing after a restore showing that randomness is not broken.
Searching memory for values that don't change during the game, but do after a race. I eventually got 9 results.
FF7_EN.exe+8FFEE0
FF7_EN.exe+9E0DF4
FF7_EN.exe+9E0DF8
FF7_EN.exe+9E0E20
FF7_EN.exe+A3B8D4
FF7_EN.exe+A3B8DC
FF7_EN.exe+A3BA30
FF7_EN.exe+A3BA38
FF7_EN.exe+A3BAF4
Freezing them all crashed the game. Individually freezing them didn't create the same stats. Neither did freezing regions of memory as groups.
I installed android-x86 with ff7 in vmware workstation and it also generates the same races per snapshot.
The same happened when I used virtualbox snapshots with android-x86.
So weird.
-
I created a snapshot shortly after starting a game and it generates the same chocobos. If snapshots are not broken it's setting the seed at game load, not save load.
Looking at procmon I saw this address at the start of the stack 770800 and eventually found 7af06b and 7b1ecf when loading chocobo.lgp. These function call GetLocalTime and GetSystemTime.
I tried to hook the functions using detours to spoof what data they return and show a message box (https://renenyffenegger.ch/notes/Windows/tools/event-hooking/set-date-in-process/index) but it doesn't appear to be calling my hooked function with ff7.exe even though it works with notepad. I'm not sure why. Perhaps they aren't used and its calling some other time function instead or something jank about how they are called.
I'm using the non steam ff7 for pc as I'm not sure how to bypass steam ff7_en.exe calling the loader.
It would make sense for the snapshots to be working and for the PC variants to be using a variable based off time we can't manipulate.
-
In my adventures in figuring out how chocobo betting works. I was taking a look at RNG and stumbled upon the Wonder Catcher.
I don't think anyone has really taken a look at it yet.
So here's what you need to know about it.
Your odds of a prize are determined solely by the single byte random seed. (The same long or short selector address for chocobos and for most of random in the game)
Here are the prizes:
0 nothing
1 nothing
2 potion
3 potion
4 potion
5 1gp
6 potion
7 nothing
8 100gp
9 nothing
10 nothing
11 nothing
12 potion
13 potion
14 nothing
15 1gp
16 potion
17 phoenix down
18 potion
19 nothing
20 potion
21 potion
22 potion
23 potion
24 potion
25 potion
26 potion
27 3gp
28 potion
29 potion
30 nothing
31 potion
32 potion
33 nothing
34 potion
35 nothing
36 potion
37 potion
38 nothing
39 nothing
40 potion
41 nothing
42 nothing
43 nothing
44 potion
45 potion
46 1gp
47 nothing
48 1gp
49 nothing
50 nothing
51 potion
52 nothing
53 nothing
54 1gp
55 1gp
56 potion
57 potion
58 nothing
59 potion
60 nothing
61 potion
62 potion
63 1gp
64 1gp
65 1gp
66 1gp
67 potion
68 phoenix
69 3gp
70 1gp
71 nothing
72 nothing
73 potion
74 potion
75 phoenix
76 nothing
77 potion
78 1gp
79 potion
80 3gp
81 potion
82 1gp
83 nothing
84 nothing
85 1gp
86 nothing
87 nothing
88 3gp
89 1gp
90 nothing
91 3gp
92 potion
93 1gp
94 nothing
95 nothing
96 potion
97 1gp
98 potion
99 potion
100 1gp
101 1gp
102 potion
103 potion
104 nothing
105 3gp
106 1gp
107 potion
108 potion
109 potion
110 1gp
111 potion
112 nothing
113 nothing
114 nothing
115 3gp
116 1gp
117 potion
118 3gp
119 3gp
120 nothing
121 potion
122 nothing
123 1gp
124 potion
125 nothing
126 1gp
127 potion
128 phoenix
129 1gp
130 potion
131 potion
132 1gp
133 nothing
134 potion
135 3gp
136 3gp
137 nothing
138 3gp
139 potion
140 potion
141 potion
142 potion
143 nothing
144 nothing
145 nothing
146 potion
147 potion
148 nothing
149 3gp
150 nothing
151 nothing
152 1gp
153 elixir
154 1gp
155 phoenix
156 nothing
157 potion
158 nothing
159 1gp
160 1gp
161 potion
162 3gp
163 potion
164 1gp
165 3gp
166 1gp
167 nothing
168 nothing
169 potion
170 potion
171 potion
172 3gp
173 potion
174 1gp
175 nothing
176 1gp
177 nothing
178 1gp
179 potion
180 phoenix
181 1gp
182 nothing
183 phoenix
184 potion
185 potion
186 potion
187 potion
188 1gp
189 3gp
190 potion
191 nothing
192 potion
193 potion
194 1gp
195 potion
196 potion
197 potion
198 1gp
199 1gp
200 3gp
201 nothing
202 nothing
203 1gp
204 potion
205 nothing
206 potion
207 1gp
208 nothing
209 potion
210 nothing
211 potion
212 potion
213 nothing
214 phoenix
215 1gp
216 potion
217 elixir
218 potion
219 nothing
220 nothing
221 potion
222 nothing
223 potion
224 potion
225 3gp
226 1gp
227 nothing
228 nothing
229 potion
230 potion
231 nothing
232 1gp
233 potion
234 potion
235 nothing
236 1gp
237 nothing
238 nothing
239 1gp
240 nothing
241 1gp
242 nothing
243 1gp
244 nothing
245 potion
246 potion
247 1gp
248 potion
249 potion
250 potion
251 nothing
252 potion
253 potion
254 3gp
255 potion
To get the top prize 100gp you need the end seed value to be 8. Meaning you need to press the "Try it" prompt at ~253/0xFD. On playstation I found there was only a 8 frame window to do so at 6988-6996 frames since save load (~116s) . The next window occurs 92 seconds later at frame 12524. Manipulating this window would require pressing the button in 0.133s which isn't easy and tbh you would be much better off with chocobo betting to earn gp.
-
Wonder Catcher is the lowest hanging fruit in GS. I didn’t even remember it was a thing.
The Chocobo race reversing is extremely interesting! I’ve often wondered about it. I wish I could help, but my laptop is half busted.
-
Sorry to hear about your laptop, hopefully you get something new soon.
Good news, I figured out how to exploit the PC version :)
I discovered today chocobo betting on PC 100% does get it's initial random value from the system clock. And how do I know this?
Well although I couldn't hook all the time functions in windows. There is a program for linux that is well developed called libfaketime.
But ff7 isn't on linux, how'd you get around that?
I compiled a 32bit libfaketime and installed ff7 1998 in 32bit wine on ubuntu 22.04.
I was able to set a clock time at load and the races were always one of three sets of chocobos.
I'm guessing this is because there are slight differences to loading time.
ubuntu@ubuntu:~/.wine/drive_c/Program Files/Square Soft, Inc/Final Fantasy VII$ WINEARCH=win32 LDFLAGS=-m32 LD_PRELOAD=/home/ubuntu/libfaketime32/src/libfaketime.so.1 FAKETIME="@2000-01-01 11:12:13" FAKETIME_DONT_RESET=1 /home/ubuntu/wine-7.0-4-proton-x86/bin/wine ff7.exe
I got the idea from here: https://www.youtube.com/watch?v=-pkf-AaFXLo
https://github.com/wolfcw/libfaketime -> To compile as 32bit you need to add -m32 to CFLAG and LDFLAGS in src/Makefile
The version of Wine I used was prebuilt and I got it's libraries from `apt install wine winetricks`.
https://github.com/Kron4ek/Wine-Builds/releases/download/7.0-4-proton/wine-7.0-4-proton-x86.tar.xz
Installing ff7 was not easy. Here's some guides I needed.
https://appdb.winehq.org/objectManager.php?sClass=version&iId=10317
https://forums.qhimm.com/index.php?topic=11994.0
https://www.codeweavers.com/support/wiki/linux/faq/videomemorysize
One thing to note, winetricks didn't like some of the download links for the dependencies, so some of them had to be manually added to the winetricks cache folder.
With this new knowledge you might be excited to learn, libfaketime is actually not even needed, setting the date and then immediately starting the game has the same effect.
date -s "9 NOV 2022 18:00:00" && WINEARCH=win32 /home/ubuntu/wine-7.0-4-proton-x86/bin/wine ff7.exe
Even running the commands back to back with a split second in between works.
On windows you can disable automatic time updates, set a time from command prompt, and pretty quickly press the play button.
time 23:00:00.00
On an xbox you can go offline, reboot the xbox.
Now from settings you can set a time, start the game, kill the game, set a time, start the game, and you will get the same result.
I'm really surprised they didn't use a higher precision of time or use uptime.
If someone was really inclined you could probably set up a dns server, to redirect the ntp client of whatever device you are using to a server you control and do the same trick.
A container running https://github.com/btfak/sntp and dnsmasq could easily do the job.
-
I wanted to take another stab to see if using machine learning instead of me guessing could render better results.
Initial results showed that with only 4000 races machine learning and deep learning models aren't very effective ~50% at guessing winners.
I needed a faster way to generate data so I took a look into lua scripts with bizhawk.
With bizhawk address 0x0B7654 has the first chocobo name and I could map out the rest of the stats from there.
Thanks to https://gamehacking.org/game/88853 I found the correct race position is actually in the chocobo bytes at -24 from the name.
If I read all the positions when position 6 is set I will know the correct order and that the race has finished.
Piecing it all together that script looks like this: https://raw.githubusercontent.com/ff7man/chocobo-betting/main/ml/race.lua
Disabling audio and and video rendering with the nymashock core and I'm getting about 300fps receiving data from a race every ~29s (class c) ~25s (class b) ~19s (class a) ~17s (class s)
AceZephyr1 modified duckstation to run races and can do the same in ~2-3s, I wish that code and data set was public somewhere.
Bizhawk is slower because it is single threaded, but thankfully as a workaround you can run multiple copies of the emulator at the same time without impacting the speed of the others too much.
Running 4 emulators at the same time I was getting ~250fps on each on a ryzen 5 3600 with only a 36% cpu utilization.
I let this run for a week and a half or so and collected 50mb of data. https://github.com/ff7man/chocobo-betting/tree/main/ml has data for every race for the first 7+ min from psx power on.
Following https://www.kaggle.com/code/cullensun/deep-learning-model-for-hong-kong-horse-racing
I used that model to test and evaluate the data set.
https://raw.githubusercontent.com/ff7man/chocobo-betting/main/ml/deeplearn.py
Even with the large dataset and full access to the race data it is only able to guess the correct winning tickets ~76% of the time.
I guess statistical analysis beats out deeplearning/machine learning today. At least with my abilities.
-
I just realized I made a big mistake when calculating winners I wasn't checking if first and second were both in the final set. The odds of winning are actually much lower.
success = 0
if chocobos2[0].place <3:
success +=1
if chocobos2[1].place <3:
success +=1
if len(chocobos2)> 2:
if chocobos2[2].place <3:
success += 1
if success >1:
wins +=1
else:
losses += 1
should really be
guesses = []
c0 = chocobos2[0].place
c1 = chocobos2[1].place
c2 = 7
if len(chocobos2) >2:
c2 = chocobos2[2].place
guesses.append([c0,c1])
guesses.append([c0,c2])
if len(chocobos2) >2:
guesses.append([c1,c2])
win = False
for guess in guesses:
if 0 in guess and 1 in guess:
win = True
if win:
wins +=1
else:
losses += 1
The fuzzfinger method is only 63.38% accurate
Class Wins Losses Avg
C 649 351 64.9
B 657 343 65.7
A 606 394 60.6
S 623 377 62.3
total 2535 1465 63.38
My modification is 73.70%
Class Wins Losses Avg
C 712 288 71.2
B 775 225 77.5
A 741 259 74.1
S 720 280 72.0
total 2948 1052 73.7
Deeplearning is around ~76% knowing all data and at ~63% for visible data.
-
AceZephyr was nice and shared some things with me, so I added them to my repo https://github.com/ff7man/chocobo-betting/tree/main/acezephyr
1. A reverse engineered script that can generate items for frames when racing. I modified it to be able to generate betting data as well. You can use it to tell you what item you will win when manipulating RNG on psx. Quick modifications could also tell you what frames have items you care about too.
2. Modifications to Duckstation to race and extract data at a faster pace
3. Data generated from the modified Duckstation (The data is from racing not betting so it's not too useful for me)
It's possible the script could work on PC as well. You would need to change some things as PC uses 45 names instead of 43 and has different prize tables for ranks. Current time will also play a role.
ergonomy_joes source is super helpful, he lists them here: https://github.com/ergonomy-joe/ff7-chocobo/blob/main/NEWFF7/D_0097C8A8.cpp
The chocobo generation logic is here: https://github.com/ergonomy-joe/ff7-chocobo/blob/main/NEWFF7/C_00772340.cpp
The random seed is set via time() in https://github.com/ergonomy-joe/ff7-chocobo/blob/main/NEWFF7/C_00779C90.cpp