start encapsulating LAN

This commit is contained in:
Arisotura
2024-08-07 16:39:30 +02:00
parent 7b40d3f6ca
commit 8e4daeace7
4 changed files with 234 additions and 172 deletions

View File

@ -45,14 +45,13 @@
#define INVALID_SOCKET (socket_t)-1 #define INVALID_SOCKET (socket_t)-1
#endif #endif
#include <enet/enet.h> // REMOVEME REMOVEME REMOVEME
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "LAN.h" #include "LAN.h"
using namespace melonDS;
namespace LAN namespace melonDS
{ {
const u32 kDiscoveryMagic = 0x444E414C; // LAND const u32 kDiscoveryMagic = 0x444E414C; // LAND
@ -61,6 +60,8 @@ const u32 kPacketMagic = 0x4946494E; // NIFI
const u32 kProtocolVersion = 1; const u32 kProtocolVersion = 1;
const u32 kLocalhost = 0x0100007F;
enum enum
{ {
Chan_Cmd = 0, // channel 0 -- control commands Chan_Cmd = 0, // channel 0 -- control commands
@ -76,51 +77,14 @@ enum
Cmd_PlayerDisconnect, // 05 -- both -- signal disconnected state (not receiving MP frames) Cmd_PlayerDisconnect, // 05 -- both -- signal disconnected state (not receiving MP frames)
}; };
struct MPPacketHeader
{
u32 Magic;
u32 SenderID;
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
u32 Length;
u64 Timestamp;
};
const int kDiscoveryPort = 7063; const int kDiscoveryPort = 7063;
const int kLANPort = 7064; const int kLANPort = 7064;
socket_t DiscoverySocket;
u32 DiscoveryLastTick;
std::map<u32, DiscoveryData> DiscoveryList;
Platform::Mutex* DiscoveryMutex = nullptr;
bool Active; LAN::LAN() noexcept : Inited(false)
bool IsHost;
ENetHost* Host;
ENetPeer* RemotePeers[16];
Player Players[16];
u32 PlayerPing[16];
int NumPlayers;
int MaxPlayers;
u16 ConnectedBitmask;
Player MyPlayer;
u32 HostAddress;
bool Lag;
int MPRecvTimeout;
int LastHostID;
ENetPeer* LastHostPeer;
std::queue<ENetPacket*> RXQueue;
u32 FrameCount;
bool Init()
{ {
DiscoveryMutex = Platform::Mutex_Create(); DiscoveryMutex = Platform::Mutex_Create();
PlayersMutex = Platform::Mutex_Create();
DiscoverySocket = INVALID_SOCKET; DiscoverySocket = INVALID_SOCKET;
DiscoveryLastTick = 0; DiscoveryLastTick = 0;
@ -128,7 +92,7 @@ bool Init()
Active = false; Active = false;
IsHost = false; IsHost = false;
Host = nullptr; Host = nullptr;
Lag = false; //Lag = false;
memset(RemotePeers, 0, sizeof(RemotePeers)); memset(RemotePeers, 0, sizeof(RemotePeers));
memset(Players, 0, sizeof(Players)); memset(Players, 0, sizeof(Players));
@ -144,19 +108,18 @@ bool Init()
FrameCount = 0; FrameCount = 0;
// TODO we init enet here but also in Netplay // TODO make this somewhat nicer
// that is redundant
if (enet_initialize() != 0) if (enet_initialize() != 0)
{ {
printf("enet shat itself :(\n"); printf("enet shat itself :(\n");
return false; return;
} }
printf("enet init OK\n"); printf("enet init OK\n");
return true; Inited = true;
} }
void DeInit() LAN::~LAN() noexcept
{ {
if (DiscoverySocket) if (DiscoverySocket)
{ {
@ -187,12 +150,46 @@ void DeInit()
enet_deinitialize(); enet_deinitialize();
Platform::Mutex_Free(DiscoveryMutex); Platform::Mutex_Free(DiscoveryMutex);
DiscoveryMutex = nullptr; Platform::Mutex_Free(PlayersMutex);
} }
bool StartDiscovery() std::map<u32, LAN::DiscoveryData> LAN::GetDiscoveryList()
{ {
Platform::Mutex_Lock(DiscoveryMutex);
auto ret = DiscoveryList;
Platform::Mutex_Unlock(DiscoveryMutex);
return ret;
}
std::vector<LAN::Player> LAN::GetPlayerList()
{
Platform::Mutex_Lock(PlayersMutex);
std::vector<Player> ret;
for (int i = 0; i < 16; i++)
{
if (Players[i].Status == Player_None) continue;
// make a copy of the player entry, fix up the address field
Player newp = Players[i];
if (newp.ID == MyPlayer.ID)
newp.Address = kLocalhost;
else if (newp.Status == Player_Host)
newp.Address = HostAddress;
ret.push_back(newp);
}
Platform::Mutex_Unlock(PlayersMutex);
return ret;
}
bool LAN::StartDiscovery()
{
if (!Inited) return false;
int res; int res;
DiscoverySocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); DiscoverySocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
@ -231,8 +228,10 @@ bool StartDiscovery()
return true; return true;
} }
void EndDiscovery() void LAN::EndDiscovery()
{ {
if (!Inited) return;
if (DiscoverySocket != INVALID_SOCKET) if (DiscoverySocket != INVALID_SOCKET)
{ {
closesocket(DiscoverySocket); closesocket(DiscoverySocket);
@ -243,8 +242,11 @@ void EndDiscovery()
Active = false; Active = false;
} }
bool StartHost(const char* playername, int numplayers) bool LAN::StartHost(const char* playername, int numplayers)
{ {
if (!Inited) return false;
if (numplayers > 16) return false;
ENetAddress addr; ENetAddress addr;
addr.host = ENET_HOST_ANY; addr.host = ENET_HOST_ANY;
addr.port = kLANPort; addr.port = kLANPort;
@ -255,17 +257,21 @@ bool StartHost(const char* playername, int numplayers)
return false; return false;
} }
Platform::Mutex_Lock(PlayersMutex);
Player* player = &Players[0]; Player* player = &Players[0];
memset(player, 0, sizeof(Player)); memset(player, 0, sizeof(Player));
player->ID = 0; player->ID = 0;
strncpy(player->Name, playername, 31); strncpy(player->Name, playername, 31);
player->Status = Player_Host; player->Status = Player_Host;
player->Address = 0x0100007F; player->Address = kLocalhost;
NumPlayers = 1; NumPlayers = 1;
MaxPlayers = numplayers; MaxPlayers = numplayers;
memcpy(&MyPlayer, player, sizeof(Player)); memcpy(&MyPlayer, player, sizeof(Player));
HostAddress = 0x0100007F; Platform::Mutex_Unlock(PlayersMutex);
HostAddress = kLocalhost;
LastHostID = -1; LastHostID = -1;
LastHostPeer = nullptr; LastHostPeer = nullptr;
@ -279,8 +285,10 @@ bool StartHost(const char* playername, int numplayers)
return true; return true;
} }
bool StartClient(const char* playername, const char* host) bool LAN::StartClient(const char* playername, const char* host)
{ {
if (!Inited) return false;
Host = enet_host_create(nullptr, 16, 2, 0, 0); Host = enet_host_create(nullptr, 16, 2, 0, 0);
if (!Host) if (!Host)
{ {
@ -298,12 +306,16 @@ bool StartClient(const char* playername, const char* host)
return false; return false;
} }
Platform::Mutex_Lock(PlayersMutex);
Player* player = &MyPlayer; Player* player = &MyPlayer;
memset(player, 0, sizeof(Player)); memset(player, 0, sizeof(Player));
player->ID = 0; player->ID = 0;
strncpy(player->Name, playername, 31); strncpy(player->Name, playername, 31);
player->Status = Player_Connecting; player->Status = Player_Connecting;
Platform::Mutex_Unlock(PlayersMutex);
ENetEvent event; ENetEvent event;
int conn = 0; int conn = 0;
u32 starttick = SDL_GetTicks(); u32 starttick = SDL_GetTicks();
@ -331,6 +343,7 @@ bool StartClient(const char* playername, const char* host)
u32 version = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24); u32 version = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24);
if (magic != kLANMagic) continue; if (magic != kLANMagic) continue;
if (version != kProtocolVersion) continue; if (version != kProtocolVersion) continue;
if (data[10] > 16) continue;
MaxPlayers = data[10]; MaxPlayers = data[10];
@ -383,7 +396,7 @@ bool StartClient(const char* playername, const char* host)
} }
void ProcessDiscovery() void LAN::ProcessDiscovery()
{ {
if (DiscoverySocket == INVALID_SOCKET) if (DiscoverySocket == INVALID_SOCKET)
return; return;
@ -473,15 +486,10 @@ void ProcessDiscovery()
} }
Platform::Mutex_Unlock(DiscoveryMutex); Platform::Mutex_Unlock(DiscoveryMutex);
// update the list in the connect dialog if needed
//if (lanClientDlg)
// lanClientDlg->updateDiscoveryList();
} }
} }
void HostUpdatePlayerList() void LAN::HostUpdatePlayerList()
{ {
u8 cmd[2+sizeof(Players)]; u8 cmd[2+sizeof(Players)];
cmd[0] = Cmd_PlayerList; cmd[0] = Cmd_PlayerList;
@ -489,18 +497,13 @@ void HostUpdatePlayerList()
memcpy(&cmd[2], Players, sizeof(Players)); memcpy(&cmd[2], Players, sizeof(Players));
ENetPacket* pkt = enet_packet_create(cmd, 2+sizeof(Players), ENET_PACKET_FLAG_RELIABLE); ENetPacket* pkt = enet_packet_create(cmd, 2+sizeof(Players), ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(Host, Chan_Cmd, pkt); enet_host_broadcast(Host, Chan_Cmd, pkt);
//if (lanDlg)
// lanDlg->updatePlayerList();
} }
void ClientUpdatePlayerList() void LAN::ClientUpdatePlayerList()
{ {
//if (lanDlg)
// lanDlg->updatePlayerList();
} }
void ProcessHostEvent(ENetEvent& event) void LAN::ProcessHostEvent(ENetEvent& event)
{ {
switch (event.type) switch (event.type)
{ {
@ -539,12 +542,16 @@ void ProcessHostEvent(ENetEvent& event)
ENetPacket* pkt = enet_packet_create(cmd, 11, ENET_PACKET_FLAG_RELIABLE); ENetPacket* pkt = enet_packet_create(cmd, 11, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, Chan_Cmd, pkt); enet_peer_send(event.peer, Chan_Cmd, pkt);
Platform::Mutex_Lock(PlayersMutex);
Players[id].ID = id; Players[id].ID = id;
Players[id].Status = Player_Connecting; Players[id].Status = Player_Connecting;
Players[id].Address = event.peer->address.host; Players[id].Address = event.peer->address.host;
event.peer->data = &Players[id]; event.peer->data = &Players[id];
NumPlayers++; NumPlayers++;
Platform::Mutex_Unlock(PlayersMutex);
RemotePeers[id] = event.peer; RemotePeers[id] = event.peer;
} }
else else
@ -604,10 +611,14 @@ void ProcessHostEvent(ENetEvent& event)
break; break;
} }
Platform::Mutex_Lock(PlayersMutex);
player.Status = Player_Client; player.Status = Player_Client;
player.Address = event.peer->address.host; player.Address = event.peer->address.host;
memcpy(hostside, &player, sizeof(Player)); memcpy(hostside, &player, sizeof(Player));
Platform::Mutex_Unlock(PlayersMutex);
// broadcast updated player list // broadcast updated player list
HostUpdatePlayerList(); HostUpdatePlayerList();
} }
@ -642,7 +653,7 @@ void ProcessHostEvent(ENetEvent& event)
} }
} }
void ProcessClientEvent(ENetEvent& event) void LAN::ProcessClientEvent(ENetEvent& event)
{ {
switch (event.type) switch (event.type)
{ {
@ -685,7 +696,9 @@ void ProcessClientEvent(ENetEvent& event)
int id = player->ID; int id = player->ID;
RemotePeers[id] = nullptr; RemotePeers[id] = nullptr;
Platform::Mutex_Lock(PlayersMutex);
player->Status = Player_Disconnected; player->Status = Player_Disconnected;
Platform::Mutex_Unlock(PlayersMutex);
ClientUpdatePlayerList(); ClientUpdatePlayerList();
} }
@ -703,6 +716,8 @@ void ProcessClientEvent(ENetEvent& event)
if (event.packet->dataLength != (2+sizeof(Players))) break; if (event.packet->dataLength != (2+sizeof(Players))) break;
if (data[1] > 16) break; if (data[1] > 16) break;
Platform::Mutex_Lock(PlayersMutex);
NumPlayers = data[1]; NumPlayers = data[1];
memcpy(Players, &data[2], sizeof(Players)); memcpy(Players, &data[2], sizeof(Players));
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
@ -710,8 +725,7 @@ void ProcessClientEvent(ENetEvent& event)
Players[i].Name[31] = '\0'; Players[i].Name[31] = '\0';
} }
//if (lanDlg) Platform::Mutex_Unlock(PlayersMutex);
// lanDlg->updatePlayerList();
// establish connections to any new clients // establish connections to any new clients
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
@ -765,7 +779,7 @@ void ProcessClientEvent(ENetEvent& event)
} }
} }
void ProcessEvent(ENetEvent& event) void LAN::ProcessEvent(ENetEvent& event)
{ {
if (IsHost) if (IsHost)
ProcessHostEvent(event); ProcessHostEvent(event);
@ -776,7 +790,7 @@ void ProcessEvent(ENetEvent& event)
// 0 = per-frame processing of events and eventual misc. frame // 0 = per-frame processing of events and eventual misc. frame
// 1 = checking if a misc. frame has arrived // 1 = checking if a misc. frame has arrived
// 2 = waiting for a MP frame // 2 = waiting for a MP frame
void Process(int type) void LAN::ProcessLAN(int type)
{ {
if (!Host) return; if (!Host) return;
//printf("Process(%d): %d %d\n", type, RXQueue.empty(), RXQueue.size()); //printf("Process(%d): %d %d\n", type, RXQueue.empty(), RXQueue.size());
@ -868,10 +882,12 @@ void Process(int type)
} }
} }
void ProcessFrame() void LAN::Process()
{ {
if (!Active) return;
ProcessDiscovery(); ProcessDiscovery();
Process(0); ProcessLAN(0);
FrameCount++; FrameCount++;
if (FrameCount >= 60) if (FrameCount >= 60)
@ -893,12 +909,7 @@ void ProcessFrame()
} }
void SetMPRecvTimeout(int timeout) void LAN::Begin(int inst)
{
MPRecvTimeout = timeout;
}
void MPBegin()
{ {
if (!Host) return; if (!Host) return;
@ -911,7 +922,7 @@ void MPBegin()
enet_host_broadcast(Host, Chan_Cmd, pkt); enet_host_broadcast(Host, Chan_Cmd, pkt);
} }
void MPEnd() void LAN::End(int inst)
{ {
if (!Host) return; if (!Host) return;
@ -923,7 +934,7 @@ void MPEnd()
} }
int SendMPPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) int LAN::SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
{ {
if (!Host) return 0; if (!Host) return 0;
@ -952,11 +963,11 @@ int SendMPPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
return len; return len;
} }
int RecvMPPacketGeneric(u8* packet, bool block, u64* timestamp) int LAN::RecvPacketGeneric(u8* packet, bool block, u64* timestamp)
{ {
if (!Host) return 0; if (!Host) return 0;
Process(block ? 2 : 1); ProcessLAN(block ? 2 : 1);
if (RXQueue.empty()) return 0; if (RXQueue.empty()) return 0;
ENetPacket* enetpacket = RXQueue.front(); ENetPacket* enetpacket = RXQueue.front();
@ -983,33 +994,33 @@ int RecvMPPacketGeneric(u8* packet, bool block, u64* timestamp)
} }
int SendMPPacket(u8* packet, int len, u64 timestamp) int LAN::SendPacket(int inst, u8* packet, int len, u64 timestamp)
{ {
return SendMPPacketGeneric(0, packet, len, timestamp); return SendPacketGeneric(0, packet, len, timestamp);
} }
int RecvMPPacket(u8* packet, u64* timestamp) int LAN::RecvPacket(int inst, u8* packet, u64* timestamp)
{ {
return RecvMPPacketGeneric(packet, false, timestamp); return RecvPacketGeneric(packet, false, timestamp);
} }
int SendMPCmd(u8* packet, int len, u64 timestamp) int LAN::SendCmd(int inst, u8* packet, int len, u64 timestamp)
{ {
return SendMPPacketGeneric(1, packet, len, timestamp); return SendPacketGeneric(1, packet, len, timestamp);
} }
int SendMPReply(u8* packet, int len, u64 timestamp, u16 aid) int LAN::SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid)
{ {
return SendMPPacketGeneric(2 | (aid<<16), packet, len, timestamp); return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp);
} }
int SendMPAck(u8* packet, int len, u64 timestamp) int LAN::SendAck(int inst, u8* packet, int len, u64 timestamp)
{ {
return SendMPPacketGeneric(3, packet, len, timestamp); return SendPacketGeneric(3, packet, len, timestamp);
} }
int RecvMPHostPacket(u8* packet, u64* timestamp) int LAN::RecvHostPacket(int inst, u8* packet, u64* timestamp)
{ {
if (LastHostID != -1) if (LastHostID != -1)
{ {
@ -1019,10 +1030,10 @@ int RecvMPHostPacket(u8* packet, u64* timestamp)
return -1; return -1;
} }
return RecvMPPacketGeneric(packet, true, timestamp); return RecvPacketGeneric(packet, true, timestamp);
} }
u16 RecvMPReplies(u8* packets, u64 timestamp, u16 aidmask) u16 LAN::RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask)
{ {
if (!Host) return 0; if (!Host) return 0;
@ -1034,7 +1045,7 @@ u16 RecvMPReplies(u8* packets, u64 timestamp, u16 aidmask)
for (;;) for (;;)
{ {
Process(2); ProcessLAN(2);
if (RXQueue.empty()) if (RXQueue.empty())
{ {
// no more replies available // no more replies available

View File

@ -22,77 +22,128 @@
#include <string> #include <string>
#include <map> #include <map>
#include <enet/enet.h>
#ifndef socket_t
#ifdef __WIN32__
#include <winsock2.h>
#define socket_t SOCKET
#else
#define socket_t int
#endif
#endif
#include "types.h" #include "types.h"
#include "Platform.h" #include "Platform.h"
#include "MPInterface.h"
namespace LAN namespace melonDS
{ {
using namespace melonDS;
enum PlayerStatus class LAN : public MPInterface
{ {
Player_None = 0, // no player in this entry public:
Player_Client, // game client LAN() noexcept;
Player_Host, // game host LAN(const LAN&) = delete;
Player_Connecting, // player still connecting LAN& operator=(const LAN&) = delete;
Player_Disconnected, // player disconnected LAN(LAN&& other) = delete;
LAN& operator=(LAN&& other) = delete;
~LAN() noexcept;
enum PlayerStatus
{
Player_None = 0, // no player in this entry
Player_Client, // game client
Player_Host, // game host
Player_Connecting, // player still connecting
Player_Disconnected, // player disconnected
};
struct Player
{
int ID;
char Name[32];
PlayerStatus Status;
u32 Address;
};
struct DiscoveryData
{
u32 Magic;
u32 Version;
u32 Tick;
char SessionName[64];
u8 NumPlayers;
u8 MaxPlayers;
u8 Status; // 0=idle 1=playing
};
bool StartDiscovery();
void EndDiscovery();
bool StartHost(const char* player, int numplayers);
bool StartClient(const char* player, const char* host);
std::map<u32, DiscoveryData> GetDiscoveryList();
std::vector<Player> GetPlayerList();
void Process() override;
void Begin(int inst) override;
void End(int inst) override;
int SendPacket(int inst, u8* data, int len, u64 timestamp) override;
int RecvPacket(int inst, u8* data, u64* timestamp) override;
int SendCmd(int inst, u8* data, int len, u64 timestamp) override;
int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) override;
int SendAck(int inst, u8* data, int len, u64 timestamp) override;
int RecvHostPacket(int inst, u8* data, u64* timestamp) override;
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) override;
private:
bool Inited;
bool Active;
bool IsHost;
ENetHost* Host;
ENetPeer* RemotePeers[16];
socket_t DiscoverySocket;
u32 DiscoveryLastTick;
std::map<u32, DiscoveryData> DiscoveryList;
Platform::Mutex* DiscoveryMutex;
Player Players[16];
u32 PlayerPing[16];
int NumPlayers;
int MaxPlayers;
Platform::Mutex* PlayersMutex;
Player MyPlayer;
u32 HostAddress;
u16 ConnectedBitmask;
int MPRecvTimeout;
int LastHostID;
ENetPeer* LastHostPeer;
std::queue<ENetPacket*> RXQueue;
u32 FrameCount;
void ProcessDiscovery();
void HostUpdatePlayerList();
void ClientUpdatePlayerList();
void ProcessHostEvent(ENetEvent& event);
void ProcessClientEvent(ENetEvent& event);
void ProcessEvent(ENetEvent& event);
void ProcessLAN(int type);
int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp);
int RecvPacketGeneric(u8* packet, bool block, u64* timestamp);
}; };
struct Player
{
int ID;
char Name[32];
PlayerStatus Status;
u32 Address;
};
struct DiscoveryData
{
u32 Magic;
u32 Version;
u32 Tick;
char SessionName[64];
u8 NumPlayers;
u8 MaxPlayers;
u8 Status; // 0=idle 1=playing
};
extern bool Active;
extern std::map<u32, DiscoveryData> DiscoveryList;
extern Platform::Mutex* DiscoveryMutex; // TODO: turn into Platform::Mutex or rework this to be nicer
extern Player Players[16];
extern u32 PlayerPing[16];
extern int NumPlayers;
extern int MaxPlayers;
extern Player MyPlayer;
extern u32 HostAddress;
bool Init();
void DeInit();
bool StartDiscovery();
void EndDiscovery();
bool StartHost(const char* player, int numplayers);
bool StartClient(const char* player, const char* host);
void ProcessFrame();
void SetMPRecvTimeout(int timeout);
void MPBegin();
void MPEnd();
int SendMPPacket(u8* data, int len, u64 timestamp);
int RecvMPPacket(u8* data, u64* timestamp);
int SendMPCmd(u8* data, int len, u64 timestamp);
int SendMPReply(u8* data, int len, u64 timestamp, u16 aid);
int SendMPAck(u8* data, int len, u64 timestamp);
int RecvMPHostPacket(u8* data, u64* timestamp);
u16 RecvMPReplies(u8* data, u64 timestamp, u16 aidmask);
} }
#endif // LAN_H #endif // LAN_H

View File

@ -34,15 +34,6 @@ struct MPStatusData
u16 MPReplyBitmask; // bitmask of which clients replied in time u16 MPReplyBitmask; // bitmask of which clients replied in time
}; };
struct MPPacketHeader
{
u32 Magic;
u32 SenderID;
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
u32 Length;
u64 Timestamp;
};
constexpr u32 kPacketQueueSize = 0x10000; constexpr u32 kPacketQueueSize = 0x10000;
constexpr u32 kReplyQueueSize = 0x10000; constexpr u32 kReplyQueueSize = 0x10000;
constexpr u32 kMaxFrameSize = 0x948; constexpr u32 kMaxFrameSize = 0x948;

View File

@ -34,6 +34,15 @@ enum MPInterfaceType
MPInterface_Netplay, MPInterface_Netplay,
}; };
struct MPPacketHeader
{
u32 Magic;
u32 SenderID;
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
u32 Length;
u64 Timestamp;
};
class MPInterface class MPInterface
{ {
public: public: