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 new file mode 100644 index 00000000..db0cdd96 --- /dev/null +++ b/ConnectionClass.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include + +// 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, // 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 +{ + __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); + +// 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; + __int16 ProductID; +}; +static_assert(sizeof(GlobalHeaderType) == 16); + +class NOVTABLE ConnectionClass +{ +public: + 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; // includes the CommHeaderType + GlobalHeaderType* PacketBuf; + WORD MagicNum; + DWORD RetryDelta; // delay before a packet is re-sent + DWORD MaxRetries; + DWORD Timeout; + int NumRecNoAck; + int NumRecAck; + int NumSendNoAck; + int NumSendAck; + 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); 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..881d963f --- /dev/null +++ b/IPXConnClass.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +class NOVTABLE IPXConnClass : public ConnectionClass +{ +public: + enum IPXConnTag + { + CONN_NAME_MAX = 40 + }; + + 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); } + +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); 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);