Add ConnectionClass and SendQueueType - #75
Conversation
Add TheirSync
| struct TheirSync | ||
| { | ||
| DEFINE_ARRAY_REFERENCE(TheirSync, [8], Array, 0xAFA358) | ||
|
|
||
| int frame; | ||
| int __send; | ||
| int __recv; | ||
| int timing_C; | ||
| int __router_resp; | ||
| int timing_14; | ||
| }; |
There was a problem hiding this comment.
This is how we mapped it in TS.
struct FrameSyncStruct {
int frame; /// other players' frame #
unsigned int sent; // # cmds other player claims to have sent
unsigned int recv; // # cmds actually received from others
unsigned int timing;
FrameSyncStruct(void);
};
FrameSyncStruct::FrameSyncStruct(void)
{
frame = -1;
sent = 0;
recv = 0;
timing = 0;
}
Evidently, there is something new in YR, but please take time to clean up field names and not just dump IDA WIP stuff in.
| COMMAND_AND_CONQUER = 0xaa01, | ||
| COMMAND_AND_CONQUER0 = 0xaa00 |
There was a problem hiding this comment.
| COMMAND_AND_CONQUER = 0xaa01, | |
| COMMAND_AND_CONQUER0 = 0xaa00 | |
| COMMAND_AND_CONQUER4 = 0xaa04, /// YR | |
| COMMAND_AND_CONQUER3 = 0xaa03, /// RA2 | |
| COMMAND_AND_CONQUER2 = 0xaa02, /// TS | |
| COMMAND_AND_CONQUER1 = 0xaa01, | |
| COMMAND_AND_CONQUER0 = 0xaa00 |
| { JMP_THIS(0x5403F0); } | ||
|
|
||
| public: | ||
| __int16 ProductID; |
There was a problem hiding this comment.
| __int16 ProductID; | |
| unsigned short ProductID; |
Don't use __ types.
| DWORD Immed_Set; | ||
| DWORD ID; |
There was a problem hiding this comment.
| DWORD Immed_Set; | |
| DWORD ID; | |
| int Immed_Set; | |
| int ID; |
| public: | ||
| IPXAddressClass Address; | ||
| NetNodeType ImmediateAddress; | ||
| PROTECTED_PROPERTY(BYTE, align_5E[0x2]); |
There was a problem hiding this comment.
Is there a need for all this alignment to excplicitly be here? Applies to everywhere.
| CONN_NAME_MAX = 40 | ||
| }; | ||
|
|
||
| DEFINE_REFERENCE(WORD, Socket, 0xAA0568) |
There was a problem hiding this comment.
| DEFINE_REFERENCE(WORD, Socket, 0xAA0568) | |
| DEFINE_REFERENCE(unsigned short, Socket, 0xAA0568) |
| int __resends; | ||
| int __numlost; | ||
| int __percentlost; | ||
| int __missedoverall; | ||
| int __missedmagic; |
There was a problem hiding this comment.
| int __resends; | |
| int __numlost; | |
| int __percentlost; | |
| int __missedoverall; | |
| int __missedmagic; | |
| int NumResends; | |
| int NumLost; | |
| int PercentLost; | |
| int MissedOverall; | |
| int MissedMagic; |
| struct GlobalHeaderType | ||
| { | ||
| CommHeaderType Header; | ||
| __int16 ProductID; | ||
| }; |
There was a problem hiding this comment.
This is technically not a thing, the functions actually pass void* and then cast as necessary
| DWORD DelaySum; | ||
| DWORD NumDelay; | ||
| DWORD MeanDelay; | ||
| DWORD MaxDelay; | ||
|
|
||
| SendQueueType* SendQueue; | ||
| int SendCount; // number of entries currently queued | ||
| DWORD SendTotal; // total ever added, used as the outgoing packet ID | ||
| int* SendIndex; | ||
|
|
||
| ReceiveQueueType* ReceiveQueue; | ||
| int ReceiveCount; | ||
| DWORD ReceiveTotal; |
| CommHeaderType* Buffer; | ||
| int ExtraLen; // size of the extra data (an IPXAddressClass, for global conns) | ||
| void* ExtraBuffer; | ||
| __int16 Port; // destination port this entry was queued for |
ZivDero
left a comment
There was a problem hiding this comment.
Went over this against TS's combuf/connect/ipxconn/ipxgconn and against gamemd.exe in IDA — checked every vtable slot, every offset and every static_assert. The structural work is solid, and all five new headers compile standalone at /std:c++20. Five things are wrong.
Bugs
1. IPXConnClass::Broadcast passes the wrong register — JMP_THIS on a 2-arg static __fastcall (IPXConnClass.h:33)
EPILOG_THISCALL's pop ecx only restores ECX by accident, when MSVC happens to allocate the frame with push ecx. With two register params it doesn't. Compiling the exact pattern (MSVC 14.51, x86, /O2):
; static int __fastcall Broadcast(void*, int) { JMP_THIS(0x53F830); }
push ebp / mov ebp,esp / sub esp,8
mov DWORD PTR _l$[ebp], edx ; [ebp-8] = buflen
mov DWORD PTR _b$[ebp], ecx ; [ebp-4] = buf
pop ecx ; <-- ecx = [ebp-8] = buflen, NOT buf
mov esp,ebp / pop ebp / jmp 0x53F830Broadcast at 0x53F830 opens with mov eax, ecx and hands that straight to PacketTransport->Broadcast as the buffer pointer, so this would send from the address buflen. Use JMP_STD — same as VoxClass::PlayTaunt/SilenceIndex in this PR, whose codegen leaves ECX/EDX untouched.
Open_Socket/Close_Socket (lines 23, 25) survive only because with a single register arg MSVC emits push ecx for the frame, so pop ecx restores the original — please switch them too rather than relying on that.
2. IPXAddressClass::field_A comment is backwards (IPX.h:25-26)
Widening to 12 bytes is right — Queue_Send(…, &addr, 12, port) at 0x53FC99, PacketTransport->Write(…, &addr, 12, …) at 0x53F812, and IPXConnClass::Address spans 0x4C–0x58. But the trailing two bytes are not the IP endpoint. As_String (0x53F0E0) prints, in IP mode, "%d.%d.%d.%d ; %d" from NodeAddress[0..3] (the IPv4) and *(unsigned short*)NetworkNumber — the port lives at offset 0, and set_Port (0x53F200) writes there. The default ctor (0x53ECB0) initialises only bytes 0–9 and never touches 0xA–0xB; they get copied purely because the game moves the struct as 12 bytes. So: tail padding, meaningful in neither mode.
3. TacticalClass::AI() duplicates an existing virtual (TacticalClass.h:100)
0x6D2540 is vtable slot +0x5C of Tactical — LogicClass::AI dispatches it via call [eax+5Ch], and +0x60 is sub_6DBB60, which YRpp already declares as TacticalClass's first new virtual. Slot +0x5C is therefore the one YRpp models as AbstractClass::Update(). Adding a non-virtual AI() gives two names for the same slot, and a direct call bypasses anyone who replaces Update. Please declare it as virtual void Update() override JMP_THIS(0x6D2540); instead.
4. EditBeaconMessage has no return value (BeaconManagerClass.h:27)
const wchar_t* and the shouldBroadcast rename are both right — the 4th arg does gate the global-message send (is_local_action && beacon->Owner == PlayerPtr->ID && send). But void → char isn't. Every return in 0x431450 hands back whatever is left in EAX: this->Count, a BeaconClass*, Players.ActiveCount, Try_Create_Radar_Event's bool, or beacon_id + 3*house. All four in-game callers (0x48D810, 0x55E5F3, 0x55E734, 0x55E7A9) discard it. Keep void.
5. MSTimer comment is inverted (Timer.h:27)
SystemTimer::GetTime is timeGetTime(); shr eax, 4 (0x6C8C40), so its ticks are 16 milliseconds each — a sixteenth of the resolution, not "sixteenths of one [millisecond]". Cross-checked live: 0x887348 = 0xADAEF, 0x887328 = 0xADAEF7, exactly >>4 apart.
Nits
GameTimers::FrameTimer(Timer.h:138) shadows theFrameTimerclock struct declared at line 13 of the same header. It matches the game's own symbol, but it reads badly next toCDTimerClass = TimerStruct<FrameTimer>.- Timer.h's new includes don't make it self-contained — it still fails standalone on
std::convertible_to,DWORD,JMP_STDandUnsorted::CurrentFrame. IsValidTauntCommandis stricter thanPlayTaunt, which masks (Command & 0xF,(Command >> 4) & 0xF) rather than rejecting high bits —0x101plays but fails validation. Fine as a sanitiser for network input; the comment atVoxClass.h:21just isn't accurate about the game's behaviour.- Worth documenting on
ConnectionClass::PacketBuf: for a global connection it points at a 16-byte header (CommHeaderType+unsigned short ProductIDat +14) —PacketBuf->ProductID = this->ProductIDat0x53FC3F, payload at&PacketBuf[1],Queue_Send(…, buflen + 16, …), versusbuflen + 14in the base class. DroppingGlobalHeaderTypewas the right call, but nobody should compute a payload offset fromsizeof(CommHeaderType)on a global conn without a warning in the header.
Verified correct
Everything else checked out, including the parts that would be easiest to get wrong:
- Vtables match declaration order exactly —
ConnectionClassat0x7E51B4,IPXConnClassat0x7EC0CC,IPXGlobalConnClassat0x7EC10C, includingPurge_Send_Queuesitting beforeService_Send_Queue,Send_To_Addressappended at slot 10, and all seven newIPXGlobalConnClassvirtuals landing at slots 11–17 in declaration order despite the public/protected split. Send(…, bool isGlobalConn, short port)—retn 18hconfirms six args, and0x48C2D7computes the 5th asthis == Ipx.IPXGlobalConn2. Good call typing itbool.CommHeaderType= 14 bytes withint field_8/short field_C, written as exactly a dword and a word at0x53FBE6/0x53FBECand0x53F75B/0x53F75F.ForwardToat +3 comes fromOwnerTalk.Get_Player_Flagsonly underViaPacketRouter— comment is right.SendQueueType::Portat+0x20(mov [eax+esi+20h], di,retn 14h), and the 1/2/4 flag bits are manipulated as a plain int at offset 0 inQueue_Send.ReceiveQueueType::Timeis genuinely whatStrip_Packetsages on.TheirSync[7]at0xAFA358, stride0x18— the reset loop runs0xAFA35C→0xAFA404, andmy_sentsits at0xAFA400.RouterResponseTimeat+0x10is fed byGlobal_Response_Time3underViaPacketRouter;LastHeardTimeat+0x14is atimeGetTime()stamp (0x646DF0).- Timers —
0x887348is initialised by??__EFrameTimer,0x887328byinit_NFTTimer;0xAFA450is literallyCDTimerClass<FrameTimerClass> Queue_AI_Multiplayer::skip_crc. - Beacon flags —
Bitfield |= 1underhouse < 8inPlaceBeacon(0x430CB9),Bitfield |= 2afterUnselect_AllinSelectBeacon(0x431050),Bitfield & 1tested first inVisibleToPlayer, andtest [eax+0Ch], 2is exactly the scan the −1/−1 path uses. All three comments are accurate. IPXGlobalConnClasstail —ProductID@0xB8,BridgeNet@0xBA/BridgeNode@0xBE/IsBridge@0xC4,LastAddress@0xCC+LastPacketID@0xD0+LastCount@0xD4(capacity) +LastRXIndex@0xD8, all confirmed inReceive_Packet/Send.Socketis written as a word (0x53F4F4), sounsigned shortis right.
No description provided.