Miscellaneous Forums > Scripting and Reverse Engineering
[FF7] Chocobo Betting
ff7man:
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.
--- Code: ---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;
}
--- End code ---
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.
ff7man:
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:
--- Code: ---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));
--- End code ---
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.
--- Code: ---sVar1 = *(short *)(&DAT_0097a3e4 + (uint)DAT_00dc0af3 * 8);
iVar8 = rand();
*(short *)(iVar5 + 0xe711ae) = sVar1 + (short)(iVar8 % (int)*(short *)(&DAT_0097a3e6 + (uint)DAT_00dc0af3 * 8));
--- End code ---
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.
--- Code: ---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];
--- End code ---
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
--- Code: ---uVar7 = rand();
*(ushort *)(iVar5 + 0xe711a4) = (-(ushort)((uVar7 & 7) != 0) & 0xfffe) + 2;
--- End code ---
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.
--- Code: ---iVar4 = DAT_00e719e8 + *(short *)(&DAT_00e711e8 + local_ec * 0xa4) * 0x3c;
...
switch(*(undefined2 *)(local_ec * 0xa4 + 0xe7115e)) {
--- End code ---
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
--- Code: ---uVar6 = rand();
uVar2 = (ushort)((int)uVar6 >> 0x1f);
*(ushort *)(iVar5 + 0xe7115e) = (((ushort)uVar6 ^ uVar2) - uVar2 & 3 ^ uVar2) - uVar2;
--- End code ---
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
--- Code: ---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
--- End code ---
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.
--- Code: ---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
--- End code ---
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.
--- Code: ---from ReadWriteMemory import ReadWriteMemory
def decodeMe(myin):
a =""" !"#$%&'()*+,-./0123456789:;<=>[email protected][\]^_`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:]
--- End code ---
Simple script to encode a string
--- Code: ---a =""" !"#$%&'()*+,-./0123456789:;<=>[email protected][\]^_`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)
--- End code ---
Simple script to decode a string
--- Code: ---a =""" !"#$%&'()*+,-./0123456789:;<=>[email protected][\]^_`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)
--- End code ---
here's a script to patch bytes so they are the same
--- Code: ---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()
--- End code ---
Byte 134 has the correct memory order sometimes.
ff7man:
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.
ff7man:
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.
--- Code: ---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))
--- End code ---
--- Code: ---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
--- End code ---
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.
ff7man:
I learned to use python pandas and the groupby functions was able to quickly answer some questions.
--- Code: ---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)
--- End code ---
--- Code: --- 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
--- End code ---
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
--- Code: --- 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
--- End code ---
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.
--- Code: --- 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
--- End code ---
If your chocobo is smart it will place on average ~0.5 ranks better
--- Code: --- 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
--- End code ---
Plats are most likely to win, then golds, then silvers
--- Code: --- 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
--- End code ---
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.
--- Code: ---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
--- End code ---
Taking into account golds are better than plats at short courses on speed ties we get:
--- Code: ---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
--- End code ---
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
--- Code: ---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
--- End code ---
as does sorting by intel, top speed, then run speed
--- Code: ---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
--- End code ---
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version