From 8d49d2a8b76f1ee861d283e9d398b4149a523ef8 Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Wed, 5 Aug 2026 10:10:41 +1200 Subject: [PATCH 1/4] Add ConnectionClass --- ConnectionClass.h | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 ConnectionClass.h diff --git a/ConnectionClass.h b/ConnectionClass.h new file mode 100644 index 00000000..da850335 --- /dev/null +++ b/ConnectionClass.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +class ConnManClassVtbl; +class CommBufferClass; +class GlobalHeaderType; +class CommHeaderType; + +class ConnectionClass +{ +public: + ConnManClassVtbl* vtable; + CommBufferClass* Queue; + int __resends; + int __numlost; + int __percentlost; + int __missedoverall; + int __missedmagic; + DWORD MaxPacketLen; + GlobalHeaderType* PacketBuf; + WORD MagicNum; + DWORD RetryDelta; + DWORD MaxRetries; + DWORD Timeout; + int NumRecNoAck; + int NumRecAck; + int NumSendNoAck; + int NumSendAck; + int LastSeqID; + int LastReadID; +}; +static_assert(sizeof(ConnectionClass) == 0x4C); + +class SendQueueType +{ +public: + int Flags; + int FirstTime; + int LastTime; + int SendCount; + int BufLen; + CommHeaderType* Buffer; + int ExtraLen; + int ExtraBuffer; + __int16 owntalk_20; +}; +static_assert(sizeof(SendQueueType) == 0x24); From 602db14b0ec09dad6e4e972465e4f1caf0bb932d Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Wed, 5 Aug 2026 10:38:05 +1200 Subject: [PATCH 2/4] Update ConnectionClass Add TheirSync --- ConnectionClass.h | 32 ++++++++++++++++++++++++++++++-- TheirSync.h | 16 ++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 TheirSync.h diff --git a/ConnectionClass.h b/ConnectionClass.h index da850335..18af589d 100644 --- a/ConnectionClass.h +++ b/ConnectionClass.h @@ -4,8 +4,36 @@ class ConnManClassVtbl; class CommBufferClass; -class GlobalHeaderType; -class CommHeaderType; + +enum class ConnectionEnum : unsigned char +{ + PACKET_DATA_ACK = 0, + PACKET_DATA_NOACK = 1, +}; + +#pragma pack(push, 1) +struct CommHeaderType +{ + __int16 MagicNumber; + ConnectionEnum Code; + char forwardto; + int PacketID; + char field_8; + char field_9; + char field_A; + char field_B; + char field_C; + char field_D; +}; +#pragma pack(pop) +static_assert(sizeof(CommHeaderType) == 14); + +struct GlobalHeaderType +{ + CommHeaderType Header; + __int16 ProductID; +}; +static_assert(sizeof(GlobalHeaderType) == 16); class ConnectionClass { diff --git a/TheirSync.h b/TheirSync.h new file mode 100644 index 00000000..a56a4ea9 --- /dev/null +++ b/TheirSync.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct TheirSync +{ + DEFINE_ARRAY_REFERENCE(TheirSync, [8], Array, 0xAFA358) + + int frame; + int __send; + int __recv; + int timing_C; + int __router_resp; + int timing_14; +}; +static_assert(sizeof(TheirSync) == 0x18); From 79d242578df8f5c33aeaaa3a8f3162e83804697e Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Thu, 6 Aug 2026 15:52:02 +1200 Subject: [PATCH 3/4] Updates --- CommBufferClass.h | 120 +++++++++++++++++++++++++++++++++++++++++++ ConnectionClass.h | 100 +++++++++++++++++++++++++++--------- IPX.h | 21 ++++++++ IPXConnClass.h | 53 +++++++++++++++++++ IPXGlobalConnClass.h | 70 +++++++++++++++++++++++++ 5 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 CommBufferClass.h create mode 100644 IPXConnClass.h create mode 100644 IPXGlobalConnClass.h diff --git a/CommBufferClass.h b/CommBufferClass.h new file mode 100644 index 00000000..ce15e44b --- /dev/null +++ b/CommBufferClass.h @@ -0,0 +1,120 @@ +#pragma once + +#include + +struct CommHeaderType; + +// Flag bits shared by both queue entry types. Westwood declared these as +// single-bit bitfields; the game reads and writes them as a plain int. +enum CommQueueFlags +{ + COMMQUEUE_IS_ACTIVE = 0x1, // this entry holds a packet and is ready to be processed + COMMQUEUE_IS_READ = 0x2, // send queue: ACK received. receive queue: caller has read it + COMMQUEUE_IS_ACK = 0x4, // an ACK has been sent for this packet +}; + +// One outgoing queue entry. +struct SendQueueType +{ + int Flags; + int FirstTime; // time this packet was first sent + int LastTime; // time this packet was last sent + int SendCount; // number of times this packet has been sent + int BufLen; // size of the packet stored in this entry + 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 +}; +static_assert(sizeof(SendQueueType) == 0x24); + +// One incoming queue entry. Unlike RA's, YR's tracks the time the packet was +// received so IPXGlobalConnClass::Strip_Packets can age entries out. +struct ReceiveQueueType +{ + int Flags; + int Time; + int BufLen; + void* Buffer; + int ExtraLen; + void* ExtraBuffer; +}; +static_assert(sizeof(ReceiveQueueType) == 0x18); + +// Stores the packets sent and received by a single ConnectionClass. Entries +// are addressed through an index array rather than moved, so unqueueing an +// entry does not disturb the others. +class NOVTABLE CommBufferClass +{ +public: + virtual ~CommBufferClass() RX; + + // Clears both queues. Init_Send_Queue clears only the send queue, which + // is what a reconnect needs. + void Init() + { JMP_THIS(0x48B2A0); } + void Init_Send_Queue() + { JMP_THIS(0x48B390); } + + // Send queue. Queue_Send returns zero when the queue is full. + int Queue_Send(void* buf, int buflen, void* extrabuf, int extralen, __int16 port) + { JMP_THIS(0x48B410); } + int UnQueue_Send(void* buf, int* buflen, int index, void* extrabuf, int* extralen, __int16 port) + { JMP_THIS(0x48B570); } + SendQueueType* Get_Send(int index) + { JMP_THIS(0x48B720); } + int Num_Send() const + { return this->SendCount; } + int Max_Send() const + { return this->MaxSend; } + + // Receive queue. + int Queue_Receive(void* buf, int buflen, void* extrabuf, int extralen) + { JMP_THIS(0x48B750); } + int UnQueue_Receive(void* buf, int* buflen, int index, void* extrabuf, int* extralen) + { JMP_THIS(0x48B890); } + ReceiveQueueType* Get_Receive(int index) + { JMP_THIS(0x48B9E0); } + int Num_Receive() const + { return this->ReceiveCount; } + int Max_Receive() const + { return this->MaxReceive; } + + // Response time tracking. The caller feeds in a delay whenever it detects + // an outgoing message has been ACK'd; the class keeps a running mean. + void Add_Delay(DWORD delay) + { JMP_THIS(0x48BA10); } + DWORD Avg_Response_Time() + { JMP_THIS(0x48BA80); } + DWORD Max_Response_Time() + { JMP_THIS(0x48BA90); } + + // Properties +public: + int MaxSend; + int MaxReceive; + int MaxPacketSize; + int MaxExtraSize; + + 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; + int* ReceiveIndex; + + int DebugOffset; + int DebugSize; + char** DebugNames; + int DebugNameStart; + int DebugNameCount; +}; +static_assert(sizeof(CommBufferClass) == 0x58); diff --git a/ConnectionClass.h b/ConnectionClass.h index 18af589d..db0cdd96 100644 --- a/ConnectionClass.h +++ b/ConnectionClass.h @@ -1,16 +1,34 @@ #pragma once #include +#include -class ConnManClassVtbl; -class CommBufferClass; +// A single "connection" with another system. This is a pure virtual base +// class; a derived class supplies Init (protocol-specific setup) and Send +// (the actual hardware-dependent transmit). +// +// Every packet the application sends is prefixed with a CommHeaderType. The +// header carries a magic number (unique per product, so foreign traffic can +// be rejected), a code saying whether this is data or an ACK, and a packet ID +// used to detect resends and to hand packets to the application in order. +// +// Service() drives the ACK/retry logic and must be polled as often as +// possible. Because a derived class can override Service_Send_Queue and +// Service_Receive_Queue, a protocol that already guarantees delivery can skip +// ACKing entirely. +// Values for CommHeaderType::Code. enum class ConnectionEnum : unsigned char { - PACKET_DATA_ACK = 0, - PACKET_DATA_NOACK = 1, + PACKET_DATA_ACK = 0, // a data packet requiring an ACK + PACKET_DATA_NOACK = 1, // a data packet not requiring an ACK + PACKET_ACK = 2, // an ACK for a packet + PACKET_COUNT = 3, // for computational purposes }; +// The header prefixed to every packet the connection sends. YR widened RA's +// 7-byte header to 14 bytes; `forwardto` holds the packet router forwarding +// mask and is only filled in for internet games routed via the packet router. #pragma pack(push, 1) struct CommHeaderType { @@ -28,6 +46,8 @@ struct CommHeaderType #pragma pack(pop) static_assert(sizeof(CommHeaderType) == 14); +// The header used by IPXGlobalConnClass. It adds a product ID so several +// applications can share one socket and still tell their own packets apart. struct GlobalHeaderType { CommHeaderType Header; @@ -35,42 +55,72 @@ struct GlobalHeaderType }; static_assert(sizeof(GlobalHeaderType) == 16); -class ConnectionClass +class NOVTABLE ConnectionClass { public: - ConnManClassVtbl* vtable; + virtual ~ConnectionClass() RX; + + virtual void Init() + { JMP_THIS(0x48BF10); } + + // Queues a packet for sending. `forwardto` is only written into the + // header when the game is an internet game running via the packet router. + virtual int Send_Packet(void* buf, int buflen, int ack_req, char forwardto) + { JMP_THIS(0x48BF40); } + + // Tells the connection a packet has arrived; the connection manager calls + // this after parsing an incoming datagram. + virtual int Receive_Packet(CommHeaderType* buf, int buflen) + { JMP_THIS(0x48C040); } + + // Pulls the next application packet out of the receive queue, stripping + // the CommHeaderType. Returns zero when nothing is ready. + virtual int Get_Packet(void* buf, int* buflen) + { JMP_THIS(0x48C320); } + + // The main polling routine. Should be called as often as possible. + virtual int Service() + { JMP_THIS(0x48C3B0); } + + // Returns the current time in 60ths of a second, which is the unit the + // retry logic works in. + static DWORD Time() + { JMP_STD(0x48C600); } + +protected: + // Drops send queue entries that have been ACK'd. The base implementation + // is a stub returning zero; IPXGlobalConnClass overrides it. + virtual int Purge_Send_Queue() + { JMP_THIS(0x48C590); } + + virtual int Service_Send_Queue() + { JMP_THIS(0x48C3E0); } + virtual int Service_Receive_Queue() + { JMP_THIS(0x48C5A0); } + + // Performs the hardware-dependent send. Pure virtual, and protected + // because only the ACK/retry logic calls it, never the application. + virtual int Send(CommHeaderType* buf, int buflen, void* extrabuf, int extralen, bool isGlobalConn, __int16 port) = 0; + + // Properties +public: CommBufferClass* Queue; int __resends; int __numlost; int __percentlost; int __missedoverall; int __missedmagic; - DWORD MaxPacketLen; + DWORD MaxPacketLen; // includes the CommHeaderType GlobalHeaderType* PacketBuf; WORD MagicNum; - DWORD RetryDelta; + DWORD RetryDelta; // delay before a packet is re-sent DWORD MaxRetries; DWORD Timeout; int NumRecNoAck; int NumRecAck; int NumSendNoAck; int NumSendAck; - int LastSeqID; - int LastReadID; + int LastSeqID; // ID of the last consecutively-received packet + int LastReadID; // ID of the last PACKET_DATA_ACK packet read }; static_assert(sizeof(ConnectionClass) == 0x4C); - -class SendQueueType -{ -public: - int Flags; - int FirstTime; - int LastTime; - int SendCount; - int BufLen; - CommHeaderType* Buffer; - int ExtraLen; - int ExtraBuffer; - __int16 owntalk_20; -}; -static_assert(sizeof(SendQueueType) == 0x24); diff --git a/IPX.h b/IPX.h index 0fca4eb5..7d8c3f4c 100644 --- a/IPX.h +++ b/IPX.h @@ -1,7 +1,28 @@ #pragma once +// The four-byte network number and six-byte node (MAC) address that together +// identify a machine on an IPX network. Westwood declared these as raw array +// typedefs; they are wrapped in structs here so they can be used as members +// without decaying to pointers. +struct NetNumType +{ + unsigned char Value[4]; +}; +static_assert(sizeof(NetNumType) == 4); + +struct NetNodeType +{ + unsigned char Value[6]; +}; +static_assert(sizeof(NetNodeType) == 6); + class IPXAddressClass { +public: unsigned char NetworkNumber[4]; unsigned char NodeAddress[6]; + // YR carries UDP/IP endpoints in this struct as well as real IPX + // addresses, so the trailing two bytes are only meaningful in IP mode. + unsigned char field_A[2]; }; +static_assert(sizeof(IPXAddressClass) == 12); diff --git a/IPXConnClass.h b/IPXConnClass.h new file mode 100644 index 00000000..3c322632 --- /dev/null +++ b/IPXConnClass.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +class NOVTABLE IPXConnClass : public ConnectionClass +{ +public: + enum IPXConnTag + { + CONN_NAME_MAX = 40 // max number of chars allowed for a connection name + }; + + DEFINE_REFERENCE(WORD, Socket, 0xAA0568) + DEFINE_REFERENCE(int, Configured, 0xAA05A4) + DEFINE_REFERENCE(int, SocketOpen, 0xAA05A8) + DEFINE_REFERENCE(int, Listening, 0xAA05AC) + + virtual void Init() override + { JMP_THIS(0x53F4E0); } + + static int __fastcall Open_Socket(WORD socket) + { JMP_THIS(0x53F5F0); } + static void __fastcall Close_Socket(WORD socket) + { JMP_THIS(0x53F630); } + + static int Start_Listening() + { JMP_STD(0x53F540); } + static int Stop_Listening() + { JMP_STD(0x53F5B0); } + + static int __fastcall Broadcast(void* buf, int buflen) + { JMP_THIS(0x53F830); } + +protected: + virtual int Send(CommHeaderType* buf, int buflen, void* extrabuf, int extralen, bool isGlobalConn, __int16 port) override + { JMP_THIS(0x53F5D0); } + + virtual int Send_To_Address(CommHeaderType* buf, int buflen, IPXAddressClass* address, NetNodeType* nodeOverride, bool isGlobalConn, __int16 port) + { JMP_THIS(0x53F650); } + + // Properties +public: + IPXAddressClass Address; + NetNodeType ImmediateAddress; + PROTECTED_PROPERTY(BYTE, align_5E[0x2]); + + DWORD Immed_Set; + DWORD ID; + wchar_t Name[CONN_NAME_MAX]; +}; +static_assert(sizeof(IPXConnClass) == 0xB8); diff --git a/IPXGlobalConnClass.h b/IPXGlobalConnClass.h new file mode 100644 index 00000000..ccabcbb5 --- /dev/null +++ b/IPXGlobalConnClass.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +class NOVTABLE IPXGlobalConnClass : public IPXConnClass +{ +public: + enum GlobalConnectionEnum + { + GLOBAL_MAGICNUM = 0x1235, + COMMAND_AND_CONQUER = 0xaa01, + COMMAND_AND_CONQUER0 = 0xaa00 + }; + + virtual int Send_Packet(void* buf, int buflen, int ack_req, char forwardto) override + { JMP_THIS(0x540610); } + virtual int Receive_Packet(CommHeaderType* buf, int buflen) override + { JMP_THIS(0x540630); } + virtual int Get_Packet(void* buf, int* buflen) override + { JMP_THIS(0x540650); } + + virtual int Send_Packet(void* buf, int buflen, IPXAddressClass* address, int ack_req, __int16 port, int packet_id) + { JMP_THIS(0x53FBD0); } + + virtual int Receive_Packet(GlobalHeaderType* buf, int buflen, IPXAddressClass* address, __int16 port) + { JMP_THIS(0x53FCB0); } + + virtual int Get_Packet(void* buf, int* buflen, IPXAddressClass* address, WORD* product_id) + { JMP_THIS(0x53FF10); } + + virtual int Peek_Packet(int index, void* buf, int* buflen, IPXAddressClass* address, WORD* product_id, int* packet_id) + { JMP_THIS(0x53FFA0); } + + virtual int Mark_Packet_Read(int index) + { JMP_THIS(0x540030); } + + virtual int Purge_Send_Queue_To(IPXAddressClass* address, __int16 port) + { JMP_THIS(0x540340); } + + virtual void Strip_Packets(DWORD age) + { JMP_THIS(0x540110); } + + void Set_Bridge(NetNumType* bridge) + { JMP_THIS(0x5402B0); } + +protected: + virtual int Purge_Send_Queue() override + { JMP_THIS(0x5402D0); } + virtual int Service_Receive_Queue() override + { JMP_THIS(0x5400D0); } + virtual int Send(CommHeaderType* buf, int buflen, void* extrabuf, int extralen, bool isGlobalConn, __int16 port) override + { JMP_THIS(0x540050); } + virtual int Send_To_Address(CommHeaderType* buf, int buflen, IPXAddressClass* address, NetNodeType* nodeOverride, bool isGlobalConn, __int16 port) override + { JMP_THIS(0x5403F0); } + +public: + __int16 ProductID; + + NetNumType BridgeNet; + NetNodeType BridgeNode; + int IsBridge; + bool field_C8; + PROTECTED_PROPERTY(BYTE, align_C9[0x3]); + IPXAddressClass* LastAddress; + int* LastPacketID; + int LastCount; + int LastRXIndex; +}; +static_assert(sizeof(IPXGlobalConnClass) == 0xDC); From ae9a8418ee618201d69ecc2b0124cc24ea0b6147 Mon Sep 17 00:00:00 2001 From: "[11EJ11]" Date: Thu, 6 Aug 2026 15:52:37 +1200 Subject: [PATCH 4/4] Update comment --- IPXConnClass.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPXConnClass.h b/IPXConnClass.h index 3c322632..881d963f 100644 --- a/IPXConnClass.h +++ b/IPXConnClass.h @@ -9,7 +9,7 @@ class NOVTABLE IPXConnClass : public ConnectionClass public: enum IPXConnTag { - CONN_NAME_MAX = 40 // max number of chars allowed for a connection name + CONN_NAME_MAX = 40 }; DEFINE_REFERENCE(WORD, Socket, 0xAA0568) @@ -40,7 +40,6 @@ class NOVTABLE IPXConnClass : public ConnectionClass virtual int Send_To_Address(CommHeaderType* buf, int buflen, IPXAddressClass* address, NetNodeType* nodeOverride, bool isGlobalConn, __int16 port) { JMP_THIS(0x53F650); } - // Properties public: IPXAddressClass Address; NetNodeType ImmediateAddress;