Open zach2good opened 3 months ago
In case it is useful to anyone else that may look into and/or work on the Chocobo Racing system, here is the information I shared with Zach from my reversing of the client that is related to the racing system.
Please Note: Things marked as Unknown
in any of the below reversed pseudocode means that I have not personally validated its purpose and have not been given a name at this time. While some things may have a assumed/guessed purpose, it is not named in my stuff until I can hand-test and validate to ensure its meaning.
The client makes use of the following packets in relation to the Chocobo Racing system:
C2S
- 0x009B
- Scoreboard RequestS2C
- 0x0069
- Race Information Update(s)S2C
- 0x0074
- Scoreboard Information Update(s)The client can make a request to the server to see the current scoreboard by using the 0x009B
packet. This request is made using the following:
char __cdecl sub_100EA560(uint32_t param, void* callback, uint32_t cbparam)
{
if ( !PTR_pGlobalNowZone )
return 0;
auto pkt = (packet_c2s_09B_t *)FUNC_gcZoneSendQueSearch(0x9B, 0, 0);
if ( !pkt )
return 0;
pkt->Param = param;
pkt->Kind = 2;
FUNC_gcZoneSendQueSet((EN_QUE *)pkt, 0x0C, 0);
PTR_pGlobalNowZone->ChocoboRacingSys.Unknown13_Func = callback;
PTR_pGlobalNowZone->ChocoboRacingSys.Unknown13_FuncParam = cbparam;
return 1;
}
This function is invoked when the client first opens the scoreboard race card window (CTkChocoboRaceCard
). The callback function is invoked upon receiving the 0x0074
packet response from the server which holds the updated scoreboard data, when all data has been populated.
The incoming 0x00074
packet handler from the server looks like this:
char __cdecl FUNC_Packet_Incoming_0x0074(GC_ZONE *zone, GP_GAME_PACKET_HEAD *head, uint8_t *pkt)
{
if ( !PTR_pGlobalNowZone )
return 1;
switch ( pkt[16] )
{
case 1u:
memset(PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11, 0, sizeof(PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11));
*(_DWORD *)&PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[12] = *((_DWORD *)pkt + 5);
*(_DWORD *)&PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[16] = *((_DWORD *)pkt + 6);
return 1;
case 2u:
qmemcpy(&PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[12 * pkt[17] + 20], pkt + 20, 0x60u);
return 1;
case 3u:
qmemcpy(&PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[20 * pkt[17] + 116], pkt + 20, 0xA0u);
return 1;
case 4u:
*(_DWORD *)PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11 = *((_DWORD *)pkt + 1);
*(_DWORD *)&PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[4] = *((_DWORD *)pkt + 2);
*(_WORD *)&PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[8] = *((_WORD *)pkt + 6);
PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11[10] |= 0x10u;
if ( PTR_pGlobalNowZone->ChocoboRacingSys.Unknown13_Func )
PTR_pGlobalNowZone->ChocoboRacingSys.Unknown13_Func(PTR_pGlobalNowZone->ChocoboRacingSys.Unknown13_FuncParam, PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11);
return 1;
}
return 1;
}
The actual callback function simply updates a few values inside of the race card window:
// a1 = The CTkChocoboRaceCard object instance.
// a2 = The PTR_pGlobalNowZone->ChocoboRacingSys.Unknown11 buffer.
int __cdecl sub_10203960(int a1, int a2)
{
int result; // eax
result = a2 + 20;
*(_DWORD *)(a1 + 36) = a2 + 116;
*(_DWORD *)(a1 + 32) = a2 + 20;
return result;
}
And then the actual window renderer that makes use of this data looks like this:
void __thiscall FUNC_CTkChocoboRaceCard_OnDrawPrimitive(int this)
{
int v2; // ebp
int v3; // ebx
int i; // edi
if ( *(_DWORD *)(this + 36) && *(_DWORD *)(this + 32) )
{
v2 = 0;
v3 = 0;
for ( i = 0; i < 96; i += 12 )
{
sub_102039B0(*(__int16 *)(this + 20), v2 + *(__int16 *)(this + 22), (_DWORD *)(v3 + *(_DWORD *)(this + 36)), i + *(_DWORD *)(this + 32));
v3 += 20;
v2 += 30;
}
}
}
void __stdcall sub_102039B0(int a1, int a2, _DWORD *a3, int a4)
{
_DWORD *MenuRes; // esi
int v5; // ebp
unsigned int v6; // eax
MenuRes = (_DWORD *)FUNC_GetMenuRes(2u);
if ( MenuRes )
{
FUNC_YkDrawString(a1 + 32, a2 + 5, (int)(a3 + 1), 0x80808080);
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * ((*(_DWORD *)(a4 + 4) >> 5) & 7) + 1820), a1 + 58, a2 + 18, 0x80808080, 0, 0);
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * (*(unsigned __int8 *)(a4 + 5) >> 5) + 1820), a1 + 104, a2 + 18, 0x80808080, 0, 0);
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * (*(_BYTE *)(a4 + 6) >> 5) + 1820), a1 + 153, a2 + 18, 0x80808080, 0, 0);
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * (*(unsigned __int8 *)(a4 + 7) >> 5) + 1820), a1 + 203, a2 + 18, 0x80808080, 0, 0);
if ( (*a3 & 3) != 0 )
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * (*a3 & 3) + 1736), a1 + 224, a2 + 8, 0x80808080, 0, 0);
v5 = a2 + 8;
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * ((*(_DWORD *)(a4 + 8) >> 25) & 7) + 1756), a1 + 240, a2 + 8, 0x80808080, 0, 0);
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * (*(_BYTE *)(a4 + 11) & 1) + 1776), a1 + 240, a2 + 8, 0x80808080, 0, 0);
v6 = (*(_DWORD *)(a4 + 8) >> 20) & 0xF;
if ( v6 && v6 <= 8 )
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * v6 + 1848), a1 + 256, v5, 0x80808080, 0, 0);
if ( ((*(_DWORD *)(a4 + 8) >> 9) & 0xF) != 0 )
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * ((*(_DWORD *)(a4 + 8) >> 9) & 0xF) + 1792), a1 + 276, a2 + 7, 0x80808080, 0, 0);
if ( ((*(_DWORD *)(a4 + 8) >> 13) & 0xF) != 0 )
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * ((*(_DWORD *)(a4 + 8) >> 13) & 0xF) + 1792), a1 + 276, a2 + 17, 0x80808080, 0, 0);
FUNC_YmMenuShape_Draw(*(_DWORD **)(*MenuRes + 4 * (*(_DWORD *)a4 & 0xF) + 1784), a1 + 333, v5, 0x80808080, 0, 0);
}
}
For more information on how these packet layouts look, please see:
0x009B
- https://github.com/atom0s/XiPackets/tree/main/world/client/0x009B0x0074
- https://github.com/atom0s/XiPackets/tree/main/world/server/0x0074The other packet used with this system is the race information update packet 0x0069
. The server will send multiple instances of this packet to fully populate the various data about the race. This includes:
RaceParams
ChocoboParams
SectionParams
The packet handler for this looks like:
char __cdecl FUNC_Packet_Incoming_0x0069(GC_ZONE *zone, GP_GAME_PACKET_HEAD *head, uint8_t *pkt)
{
switch ( pkt[4] )
{
case 1u:
zone->ChocoboRacingSys.DownloadFlg = 0;
*(_QWORD *)zone->ChocoboRacingSys.RaceParams = *((_QWORD *)pkt + 1);
return 1;
case 2u:
zone->ChocoboRacingSys.DownloadFlg = 0;
qmemcpy(&zone->ChocoboRacingSys.ChocoboParams[3 * pkt[5]], pkt + 8, pkt[6]);
return 1;
case 3u:
zone->ChocoboRacingSys.DownloadFlg = 0;
qmemcpy(&zone->ChocoboRacingSys.SectionParams[3 * pkt[5]], pkt + 8, pkt[6]);
return 1;
case 4u:
zone->ChocoboRacingSys.DownloadFlg = 0;
qmemcpy(&zone->ChocoboRacingSys.ResultParams, pkt + 8, 4 * (pkt[6] >> 2));
qmemcpy(&zone->ChocoboRacingSys.SectionParams[(pkt[6] >> 2) + 96], &pkt[4 * (pkt[6] >> 2) + 8], pkt[6] & 3);
return 1;
default:
zone->ChocoboRacingSys.DownloadFlg = 1;
break;
}
return 1;
}
When populating the Chocobo system information, the server will send multiple 0x0069
packets to update the various bits of data needed that have changed. If the packet mode is 1, 2, 3 or 4 then the system will be marked as 'not ready' until a final packet is received using a different mode value which will trigger the default handler and mark the system as ready.
For more information on how this packet layout looks, please see: https://github.com/atom0s/XiPackets/tree/main/world/server/0x0069
Aside from the card window handling shown above, this systems' data is also mainly used within the event VM system. The main opcode this system uses is the opcode 0x00BF
. This opcode is used to load and push various data from the Chocobo system into the event VM to be used with other opcodes.
You can find more information about this opcode handler here: https://github.com/atom0s/XiEvents/blob/main/OpCodes/0x00BF.md
I affirm:
Describe the feature
As of https://github.com/LandSandBoat/server/pull/5920 we have race playout. So now it's possible to start pulling apart those data packets and plug in things for races. It's likely going to be horrendously boring to do by hand, but that comes with the territory of anything to do with Chocobo Minigames...
Advice
(thanks atom0s!) Use Ashita v4 and set
/fps 0
, turn down resolution and as many settings as possible to maximise FPS so races play out faster.Chocobo Circuit
Scoreboard / Paddock
Racing
Misc
VieweD
Links
Scoreboard data: https://github.com/atom0s/XiPackets/tree/main/world/server/0x0074 Race data: https://github.com/atom0s/XiPackets/blob/main/world/server/0x0069/README.md