Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions CommBufferClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#pragma once

#include <YRPP.h>

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;
short 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, short port)
{ JMP_THIS(0x48B410); }
int UnQueue_Send(void* buf, int* buflen, int index, void* extrabuf, int* extralen, short 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(unsigned int delay)
{ JMP_THIS(0x48BA10); }
unsigned int Avg_Response_Time()
{ JMP_THIS(0x48BA80); }
unsigned int Max_Response_Time()
{ JMP_THIS(0x48BA90); }

// Properties
public:
int MaxSend;
int MaxReceive;
int MaxPacketSize;
int MaxExtraSize;

unsigned int DelaySum;
unsigned int NumDelay;
unsigned int MeanDelay;
unsigned int MaxDelay;

SendQueueType* SendQueue;
int SendCount; // number of entries currently queued
unsigned int SendTotal; // total ever added, used as the outgoing packet ID
int* SendIndex;

ReceiveQueueType* ReceiveQueue;
int ReceiveCount;
unsigned int ReceiveTotal;
int* ReceiveIndex;

int DebugOffset;
int DebugSize;
char** DebugNames;
int DebugNameStart;
int DebugNameCount;
};
static_assert(sizeof(CommBufferClass) == 0x58);
113 changes: 113 additions & 0 deletions ConnectionClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#pragma once

#include <YRPP.h>
#include <CommBufferClass.h>

// 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
{
short MagicNumber;
ConnectionEnum Code;
char ForwardTo;
int PacketID;
int field_8;
short field_C;
};
#pragma pack(pop)
static_assert(sizeof(CommHeaderType) == 14);

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 unsigned int 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, short port) = 0;

// Properties
public:
CommBufferClass* Queue;
int NumResends;
int NumLost;
int PercentLost;
int MissedOverall;
int MissedMagic;
unsigned int MaxPacketLen; // includes the CommHeaderType
CommHeaderType* PacketBuf;
unsigned short MagicNum;
unsigned int RetryDelta; // delay before a packet is re-sent
unsigned int MaxRetries;
unsigned int 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);
21 changes: 21 additions & 0 deletions IPX.h
Original file line number Diff line number Diff line change
@@ -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);
51 changes: 51 additions & 0 deletions IPXConnClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include <YRPP.h>
#include <ConnectionClass.h>
#include <IPX.h>

class NOVTABLE IPXConnClass : public ConnectionClass
{
public:
enum IPXConnTag
{
CONN_NAME_MAX = 40
};

DEFINE_REFERENCE(unsigned short, 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(unsigned short socket)
{ JMP_THIS(0x53F5F0); }
static void __fastcall Close_Socket(unsigned short 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, short port) override
{ JMP_THIS(0x53F5D0); }

virtual int Send_To_Address(CommHeaderType* buf, int buflen, IPXAddressClass* address, NetNodeType* nodeOverride, bool isGlobalConn, short port)
{ JMP_THIS(0x53F650); }

public:
IPXAddressClass Address;
NetNodeType ImmediateAddress;

int Immed_Set;
int ID;
wchar_t Name[CONN_NAME_MAX];
};
static_assert(sizeof(IPXConnClass) == 0xB8);
72 changes: 72 additions & 0 deletions IPXGlobalConnClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include <YRPP.h>
#include <IPXConnClass.h>

class NOVTABLE IPXGlobalConnClass : public IPXConnClass
{
public:
enum GlobalConnectionEnum
{
GLOBAL_MAGICNUM = 0x1235,
COMMAND_AND_CONQUER4 = 0xaa04, /// YR
COMMAND_AND_CONQUER3 = 0xaa03, /// RA2
COMMAND_AND_CONQUER2 = 0xaa02, /// TS
COMMAND_AND_CONQUER1 = 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, short port, int packet_id)
{ JMP_THIS(0x53FBD0); }

virtual int Receive_Packet(void* buf, int buflen, IPXAddressClass* address, short port)
{ JMP_THIS(0x53FCB0); }

virtual int Get_Packet(void* buf, int* buflen, IPXAddressClass* address, unsigned short* product_id)
{ JMP_THIS(0x53FF10); }

virtual int Peek_Packet(int index, void* buf, int* buflen, IPXAddressClass* address, unsigned short* 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, short port)
{ JMP_THIS(0x540340); }

virtual void Strip_Packets(unsigned int 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, short port) override
{ JMP_THIS(0x540050); }
virtual int Send_To_Address(CommHeaderType* buf, int buflen, IPXAddressClass* address, NetNodeType* nodeOverride, bool isGlobalConn, short port) override
{ JMP_THIS(0x5403F0); }

public:
unsigned short ProductID;

NetNumType BridgeNet;
NetNodeType BridgeNode;
int IsBridge;
bool field_C8;
IPXAddressClass* LastAddress;
int* LastPacketID;
int LastCount;
int LastRXIndex;
};
static_assert(sizeof(IPXGlobalConnClass) == 0xDC);
Loading