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;
__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);
126 changes: 126 additions & 0 deletions ConnectionClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#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
{
__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);
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);
52 changes: 52 additions & 0 deletions IPXConnClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#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(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);
Loading