EOSSDK-Holos/main.cpp
2026-06-28 21:33:05 -03:00

2084 lines
No EOL
78 KiB
C++

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <vector>
#include <functional>
#pragma comment(lib, "ws2_32.lib")
// Atomic helpers
static inline LONG AtomicRead(LONG* val) { return InterlockedCompareExchange(val, 0, 0); }
static inline void AtomicWrite(LONG* val, LONG newVal) { InterlockedExchange(val, newVal); }
// Async callback queue
static std::vector<std::function<void()>> g_callbackQueue;
static CRITICAL_SECTION g_callbackQueueCs;
static void InitCallbackQueue() {
InitializeCriticalSection(&g_callbackQueueCs);
}
static void ShutdownCallbackQueue() {
DeleteCriticalSection(&g_callbackQueueCs);
}
static void QueueCallback(std::function<void()> callback) {
EnterCriticalSection(&g_callbackQueueCs);
g_callbackQueue.push_back(std::move(callback));
LeaveCriticalSection(&g_callbackQueueCs);
}
static void DrainCallbackQueue() {
EnterCriticalSection(&g_callbackQueueCs);
std::vector<std::function<void()>> localQueue;
localQueue.swap(g_callbackQueue);
LeaveCriticalSection(&g_callbackQueueCs);
for (auto& cb : localQueue) {
if (cb) cb();
}
}
// logging system
FILE *logFile = NULL;
FILE *packetFile = NULL;
static CRITICAL_SECTION g_packetCs;
void InitLogFiles();
void CloseLogFiles();
void Log(const char *format, ...) {
if (!logFile) return;
va_list args;
va_start(args, format);
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(logFile, "[%02u:%02u:%02u.%03u] ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
vfprintf(logFile, format, args);
fprintf(logFile, "\n");
fflush(logFile);
va_end(args);
}
static void HexDump(const void *data, size_t len, char *out) {
const unsigned char *p = (const unsigned char *)data;
for (size_t i = 0; i < len; ++i) sprintf(out + i * 3, "%02X ", p[i]);
if (len > 0) out[len * 3 - 1] = '\0';
}
static void AsciiDump(const void *data, size_t len, char *out) {
const unsigned char *p = (const unsigned char *)data;
for (size_t i = 0; i < len; ++i) out[i] = (p[i] >= 32 && p[i] < 127) ? (char)p[i] : '.';
out[len] = '\0';
}
void DumpPacket(const char *direction, const void *data, uint32_t len,
const struct sockaddr_in *addr, uint8_t channel) {
if (!packetFile || !data || len == 0) return;
EnterCriticalSection(&g_packetCs);
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(packetFile, "\n========== PACKET %s [%02u:%02u:%02u.%03u] ==========\n",
direction, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
if (addr) fprintf(packetFile, "Peer: %s:%d ", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
fprintf(packetFile, "Size: %u bytes Channel: %u\n", len, channel);
const unsigned char *p = (const unsigned char *)data;
for (uint32_t offset = 0; offset < len; offset += 16) {
uint32_t lineLen = (len - offset) > 16 ? 16 : (len - offset);
char hex[16 * 3 + 1] = {0};
char ascii[16 + 1] = {0};
HexDump(p + offset, lineLen, hex);
AsciiDump(p + offset, lineLen, ascii);
fprintf(packetFile, "%04X %-48s %s\n", offset, hex, ascii);
}
fprintf(packetFile, "==================================================\n");
fflush(packetFile);
LeaveCriticalSection(&g_packetCs);
}
// Forward declarations
void InitNetwork();
void LogStateSnapshot(const char *context);
void ResetConnectionState();
void FirePeerConnectionEstablished(void* remotePeerId);
void FirePeerConnectionRequest(void* remotePeerId);
void FireLobbyMemberJoined(void* memberId);
void HostOnFirstPacketReceived(const struct sockaddr_in* from);
void SendHelloAck(const struct sockaddr_in* to);
void TryFireHostEvents(void* clientId);
static void SendHelloAckLocked(const struct sockaddr_in* to);
static void HostOnFirstPacketReceivedNoLock(const struct sockaddr_in* from);
static bool DiscardMitmHelloIfPresentLocked();
// Network State
SOCKET udpSocket = INVALID_SOCKET;
struct sockaddr_in targetAddr;
static LONG g_isHost = 0;
static LONG g_initialized = 0;
char g_dllDir[MAX_PATH] = {0};
char g_searchAppVersion[64] = "1.2.0";
char g_searchOnlineRule[64] = "Koikoi";
char g_lobbyAppVersion[64] = "1.2.0";
char g_lobbyOnlineRule[64] = "Koikoi";
char g_lobbyPlatform[32] = "Steam";
char g_lobbyEosProductId[64] = "DIRECTIP_HOST_PUID";
int64_t g_lobbyWinRate = 10;
bool g_lobbyIsCrossPlatform = true;
static LONG g_clientSawSearchResult = 0;
static uint32_t g_maxMembers = 4;
const char *kMitmHello = "EOSMITM_HELLO";
static const char *kMitmAck = "EOSMITM_ACK";
static LONG g_peerConnected = 0;
static LONG g_lobbyHasRemoteMember = 0;
static LONG g_hostFiredEvents = 0;
static LONG g_helloAckReceived = 0;
static LONG g_hostHasClientAddr = 0;
static LONG g_establishedFired = 0;
static LONG g_lobbyMemberJoinedFired = 0;
static LONG g_connectionRequestFired = 0;
static LONG g_initLock = 0;
static CRITICAL_SECTION g_hostEventCs;
static CRITICAL_SECTION g_socketCs;
static HANDLE g_stopEvent = NULL;
static HANDLE g_peerWatchThread = NULL;
static uint64_t g_notifIdCounter = 10;
// Rate limiting for ResetConnectionState
static DWORD g_lastResetTime = 0;
static LONG g_resetCount = 0;
void LogStateSnapshot(const char *context) {
Log("[State] %s | isHost=%d peerConnected=%d lobbyHasRemoteMember=%d initialized=%d clientSawSearchResult=%d",
context ? context : "???", (int)AtomicRead(&g_isHost), (int)AtomicRead(&g_peerConnected),
(int)AtomicRead(&g_lobbyHasRemoteMember), (int)AtomicRead(&g_initialized),
(int)AtomicRead(&g_clientSawSearchResult));
}
// EOS Types Mock
typedef int32_t EOS_EResult;
#define EOS_Success 0
#define EOS_NotFound 8
#define EOS_InvalidParameters 4
typedef void *EOS_HP2P;
typedef void *EOS_ProductUserId;
struct EOS_P2P_SocketId {
int32_t ApiVersion;
char SocketName[33];
};
static EOS_P2P_SocketId g_socketId = { 2, "DirectIP" };
static EOS_ProductUserId HostProductUserId() { return (EOS_ProductUserId)1; }
static EOS_ProductUserId ClientProductUserId() { return (EOS_ProductUserId)2; }
static EOS_ProductUserId LocalProductUserId() {
InitNetwork();
return AtomicRead(&g_isHost) ? HostProductUserId() : ClientProductUserId();
}
static EOS_ProductUserId RemoteProductUserId() {
InitNetwork();
return AtomicRead(&g_isHost) ? ClientProductUserId() : HostProductUserId();
}
// P2P structs (must be complete before extern "C" functions use them)
struct EOS_P2P_SendPacketOptions {
int32_t ApiVersion;
EOS_ProductUserId LocalUserId;
EOS_ProductUserId RemoteUserId;
const void *SocketId;
uint8_t Channel;
uint32_t DataLengthBytes;
const void *Data;
bool bAllowDelayedDelivery;
uint8_t Reliability;
bool bDisableAutoAcceptConnection;
};
struct EOS_P2P_GetNextReceivedPacketSizeOptions {
int32_t ApiVersion;
EOS_ProductUserId LocalUserId;
const uint8_t *RequestedChannel;
};
struct EOS_P2P_ReceivePacketOptions {
int32_t ApiVersion;
EOS_ProductUserId LocalUserId;
uint32_t MaxDataSizeBytes;
const uint8_t *RequestedChannel;
};
// Callback types
struct EOS_P2P_OnIncomingConnectionRequestInfo {
void* ClientData;
EOS_ProductUserId LocalUserId;
EOS_ProductUserId RemoteUserId;
const EOS_P2P_SocketId* SocketId;
};
typedef void (*EOS_P2P_OnIncomingConnectionRequestCallback)(const EOS_P2P_OnIncomingConnectionRequestInfo* Data);
struct EOS_P2P_OnPeerConnectionEstablishedInfo {
void* ClientData;
EOS_ProductUserId LocalUserId;
EOS_ProductUserId RemoteUserId;
const EOS_P2P_SocketId* SocketId;
int32_t ConnectionType;
};
typedef void (*EOS_P2P_OnPeerConnectionEstablishedCallback)(const EOS_P2P_OnPeerConnectionEstablishedInfo* Data);
static EOS_P2P_OnIncomingConnectionRequestCallback g_connectionRequestCb = nullptr;
static void* g_connectionRequestCbData = nullptr;
static EOS_P2P_OnPeerConnectionEstablishedCallback g_connectionEstablishedCb = nullptr;
static void* g_connectionEstablishedCbData = nullptr;
struct EOS_Lobby_LobbyMemberStatusReceivedCallbackInfo {
void* ClientData;
const char* LobbyId;
EOS_ProductUserId TargetUserId;
int32_t CurrentStatus;
};
typedef void (*EOS_Lobby_OnLobbyMemberStatusReceivedCallback)(const EOS_Lobby_LobbyMemberStatusReceivedCallbackInfo* Data);
static EOS_Lobby_OnLobbyMemberStatusReceivedCallback g_memberStatusCb = nullptr;
static void* g_memberStatusCbData = nullptr;
struct EOS_Lobby_LobbyUpdateReceivedCallbackInfo {
void* ClientData;
const char* LobbyId;
};
typedef void (*EOS_Lobby_OnLobbyUpdateReceivedCallback)(const EOS_Lobby_LobbyUpdateReceivedCallbackInfo* Data);
struct EOS_Lobby_LobbyMemberUpdateReceivedCallbackInfo {
void* ClientData;
const char* LobbyId;
EOS_ProductUserId TargetUserId;
};
typedef void (*EOS_Lobby_OnLobbyMemberUpdateReceivedCallback)(const EOS_Lobby_LobbyMemberUpdateReceivedCallbackInfo* Data);
static EOS_Lobby_OnLobbyUpdateReceivedCallback g_lobbyUpdateCb = nullptr;
static void* g_lobbyUpdateCbData = nullptr;
static EOS_Lobby_OnLobbyMemberUpdateReceivedCallback g_memberUpdateCb = nullptr;
static void* g_memberUpdateCbData = nullptr;
void ResetConnectionState() {
// Rate limit resets to prevent rapid re-initialization loops
DWORD now = GetTickCount();
if (now - g_lastResetTime < 100) {
if (InterlockedIncrement(&g_resetCount) > 5) {
Log("[Net] ResetConnectionState rate-limited (too many rapid resets)");
InterlockedDecrement(&g_resetCount);
return;
}
} else {
AtomicWrite(&g_resetCount, 0);
}
g_lastResetTime = now;
Log("[Net] Resetting connection state for new session");
// Signal and wait for watch thread to exit before touching the socket
if (g_peerWatchThread) {
if (g_stopEvent) SetEvent(g_stopEvent);
WaitForSingleObject(g_peerWatchThread, 3000);
// Only close handle if it's still valid
EnterCriticalSection(&g_socketCs);
if (g_peerWatchThread) {
CloseHandle(g_peerWatchThread);
g_peerWatchThread = NULL;
}
LeaveCriticalSection(&g_socketCs);
}
EnterCriticalSection(&g_socketCs);
if (udpSocket != INVALID_SOCKET) {
closesocket(udpSocket);
udpSocket = INVALID_SOCKET;
}
LeaveCriticalSection(&g_socketCs);
AtomicWrite(&g_peerConnected, 0);
AtomicWrite(&g_lobbyHasRemoteMember, 0);
AtomicWrite(&g_hostFiredEvents, 0);
AtomicWrite(&g_clientSawSearchResult, 0);
AtomicWrite(&g_helloAckReceived, 0);
AtomicWrite(&g_hostHasClientAddr, 0);
AtomicWrite(&g_establishedFired, 0);
AtomicWrite(&g_lobbyMemberJoinedFired, 0);
AtomicWrite(&g_connectionRequestFired, 0);
AtomicWrite(&g_initialized, 0);
AtomicWrite(&g_isHost, 0);
g_connectionRequestCb = nullptr;
g_connectionRequestCbData = nullptr;
g_connectionEstablishedCb = nullptr;
g_connectionEstablishedCbData = nullptr;
g_memberStatusCb = nullptr;
g_memberStatusCbData = nullptr;
g_lobbyUpdateCb = nullptr;
g_lobbyUpdateCbData = nullptr;
g_memberUpdateCb = nullptr;
g_memberUpdateCbData = nullptr;
}
void InitNetwork() {
if (AtomicRead(&g_initialized)) return;
if (InterlockedCompareExchange(&g_initLock, 1, 0) != 0) {
while (!AtomicRead(&g_initialized)) Sleep(1);
return;
}
if (AtomicRead(&g_initialized)) { InterlockedExchange(&g_initLock, 0); return; }
WSADATA wsaData;
int wsaErr = WSAStartup(MAKEWORD(2, 2), &wsaData);
Log("[Net] WSAStartup result: %d", wsaErr);
char iniPath[MAX_PATH];
snprintf(iniPath, sizeof(iniPath), "%sdirectip.ini", g_dllDir);
char role[32] = {0};
char ip[64] = {0};
char portStr[16] = {0};
GetPrivateProfileStringA("Network", "Role", "Client", role, sizeof(role), iniPath);
GetPrivateProfileStringA("Network", "IP", "127.0.0.1", ip, sizeof(ip), iniPath);
GetPrivateProfileStringA("Network", "Port", "7777", portStr, sizeof(portStr), iniPath);
int port = atoi(portStr);
AtomicWrite(&g_isHost, (_stricmp(role, "Host") == 0) ? 1 : 0);
EnterCriticalSection(&g_socketCs);
udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (udpSocket == INVALID_SOCKET) {
Log("[Net] socket() failed: %d", WSAGetLastError());
LeaveCriticalSection(&g_socketCs);
InterlockedExchange(&g_initLock, 0);
return;
}
// Allow rapid rebind to avoid WSAEADDRINUSE
int reuse = 1;
if (setsockopt(udpSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
Log("[Net] setsockopt(SO_REUSEADDR) failed: %d", WSAGetLastError());
}
u_long mode = 1;
ioctlsocket(udpSocket, FIONBIO, &mode);
struct sockaddr_in bindAddr = {0};
bindAddr.sin_family = AF_INET;
bindAddr.sin_addr.s_addr = INADDR_ANY;
bindAddr.sin_port = htons(AtomicRead(&g_isHost) ? port : 0);
int bindRes = bind(udpSocket, (struct sockaddr *)&bindAddr, sizeof(bindAddr));
LeaveCriticalSection(&g_socketCs);
Log("[Net] bind() result: %d (port %d)", bindRes, AtomicRead(&g_isHost) ? port : 0);
if (bindRes != 0) {
Log("[Net] bind() failed, aborting network init");
EnterCriticalSection(&g_socketCs);
if (udpSocket != INVALID_SOCKET) { closesocket(udpSocket); udpSocket = INVALID_SOCKET; }
LeaveCriticalSection(&g_socketCs);
InterlockedExchange(&g_initLock, 0);
return;
}
targetAddr.sin_family = AF_INET;
targetAddr.sin_port = htons(port);
targetAddr.sin_addr.s_addr = inet_addr(ip);
// Read optional lobby attributes from ini
char maxMembersStr[16] = "4";
GetPrivateProfileStringA("Network", "MaxMembers", "4", maxMembersStr, sizeof(maxMembersStr), iniPath);
g_maxMembers = (uint32_t)atoi(maxMembersStr);
char platformStr[32] = "Steam";
GetPrivateProfileStringA("Network", "Platform", "Steam", platformStr, sizeof(platformStr), iniPath);
strncpy(g_lobbyPlatform, platformStr, sizeof(g_lobbyPlatform) - 1);
g_lobbyPlatform[sizeof(g_lobbyPlatform) - 1] = '\0';
char winRateStr[32] = "10";
GetPrivateProfileStringA("Network", "WinRate", "10", winRateStr, sizeof(winRateStr), iniPath);
g_lobbyWinRate = _atoi64(winRateStr);
char crossStr[16] = "true";
GetPrivateProfileStringA("Network", "IsCrossPlatform", "true", crossStr, sizeof(crossStr), iniPath);
g_lobbyIsCrossPlatform = (_stricmp(crossStr, "true") == 0 || strcmp(crossStr, "1") == 0);
char appVerStr[64] = "1.2.0";
GetPrivateProfileStringA("Network", "AppVersion", "1.2.0", appVerStr, sizeof(appVerStr), iniPath);
strncpy(g_lobbyAppVersion, appVerStr, sizeof(g_lobbyAppVersion) - 1);
g_lobbyAppVersion[sizeof(g_lobbyAppVersion) - 1] = '\0';
char ruleStr[64] = "Koikoi";
GetPrivateProfileStringA("Network", "OnlineRule", "Koikoi", ruleStr, sizeof(ruleStr), iniPath);
strncpy(g_lobbyOnlineRule, ruleStr, sizeof(g_lobbyOnlineRule) - 1);
g_lobbyOnlineRule[sizeof(g_lobbyOnlineRule) - 1] = '\0';
Log("[Net] Initialized. Role=%s Target=%s:%d BindPort=%d ini=%s",
role, ip, port, AtomicRead(&g_isHost) ? port : 0, iniPath);
LogStateSnapshot("InitNetwork");
AtomicWrite(&g_initialized, 1);
InterlockedExchange(&g_initLock, 0);
}
void FirePeerConnectionEstablished(EOS_ProductUserId remotePeerId) {
Log("[Net] >> FirePeerConnectionEstablished remote=%p", remotePeerId);
if (!g_connectionEstablishedCb) {
Log("[Net] g_connectionEstablishedCb is NULL, cannot fire yet");
return;
}
if (InterlockedCompareExchange(&g_establishedFired, 1, 0) != 0) {
Log("[Net] FirePeerConnectionEstablished already delivered, skipping");
return;
}
EOS_P2P_OnPeerConnectionEstablishedInfo info = {0};
info.ClientData = g_connectionEstablishedCbData;
info.LocalUserId = LocalProductUserId();
info.RemoteUserId = remotePeerId;
info.SocketId = &g_socketId;
info.ConnectionType = 0;
Log("[Net] Invoking g_connectionEstablishedCb");
g_connectionEstablishedCb(&info);
Log("[Net] << FirePeerConnectionEstablished");
}
void FirePeerConnectionRequest(EOS_ProductUserId remotePeerId) {
Log("[Net] >> FirePeerConnectionRequest remote=%p", remotePeerId);
AtomicWrite(&g_peerConnected, 1);
AtomicWrite(&g_lobbyHasRemoteMember, 1);
if (g_connectionRequestCb) {
if (InterlockedCompareExchange(&g_connectionRequestFired, 1, 0) != 0) {
Log("[Net] FirePeerConnectionRequest already delivered, skipping");
return;
}
LogStateSnapshot("FirePeerConnectionRequest");
EOS_P2P_OnIncomingConnectionRequestInfo info = {0};
info.ClientData = g_connectionRequestCbData;
info.LocalUserId = LocalProductUserId();
info.RemoteUserId = remotePeerId;
info.SocketId = &g_socketId;
Log("[Net] Invoking g_connectionRequestCb");
g_connectionRequestCb(&info);
// Do not auto-fire established here. The game must call AcceptConnection.
} else {
Log("[Net] g_connectionRequestCb is NULL, will retry when registered");
}
Log("[Net] << FirePeerConnectionRequest");
}
void FireLobbyMemberJoined(EOS_ProductUserId memberId) {
Log("[Net] >> FireLobbyMemberJoined member=%p", memberId);
AtomicWrite(&g_lobbyHasRemoteMember, 1);
if (!g_memberStatusCb) {
Log("[Net] g_memberStatusCb is NULL, cannot fire yet");
return;
}
if (InterlockedCompareExchange(&g_lobbyMemberJoinedFired, 1, 0) != 0) {
Log("[Net] FireLobbyMemberJoined already delivered, skipping");
return;
}
EOS_Lobby_LobbyMemberStatusReceivedCallbackInfo info = {0};
info.ClientData = g_memberStatusCbData;
info.LobbyId = "DirectIP_Lobby";
info.TargetUserId = memberId;
info.CurrentStatus = 0;
Log("[Net] Invoking g_memberStatusCb for JOINED");
g_memberStatusCb(&info);
Log("[Net] << FireLobbyMemberJoined");
}
static void SendHelloAckLocked(const struct sockaddr_in* to) {
if (udpSocket == INVALID_SOCKET || !to) return;
sendto(udpSocket, kMitmAck, (int)strlen(kMitmAck), 0,
(struct sockaddr*)to, sizeof(*to));
Log("[Net] Sent ACK to %s:%d", inet_ntoa(to->sin_addr), ntohs(to->sin_port));
}
void SendHelloAck(const struct sockaddr_in* to) {
if (udpSocket == INVALID_SOCKET || !to) return;
EnterCriticalSection(&g_socketCs);
SendHelloAckLocked(to);
LeaveCriticalSection(&g_socketCs);
}
static void HostOnFirstPacketReceivedNoLock(const struct sockaddr_in* from) {
if (!from) return;
if (!AtomicRead(&g_hostHasClientAddr)) {
targetAddr = *from;
AtomicWrite(&g_hostHasClientAddr, 1);
}
Log("[Net] Host detected client at %s:%d — firing member+connection events",
inet_ntoa(from->sin_addr), ntohs(from->sin_port));
LogStateSnapshot("HostFirstPacket");
TryFireHostEvents(ClientProductUserId());
}
void HostOnFirstPacketReceived(const struct sockaddr_in* from) {
if (!from) return;
EnterCriticalSection(&g_socketCs);
HostOnFirstPacketReceivedNoLock(from);
LeaveCriticalSection(&g_socketCs);
}
void TryFireHostEvents(void* clientId) {
EOS_ProductUserId id = (EOS_ProductUserId)clientId;
if (!AtomicRead(&g_hostFiredEvents)) {
EnterCriticalSection(&g_hostEventCs);
if (!AtomicRead(&g_hostFiredEvents)) {
AtomicWrite(&g_hostFiredEvents, 1);
LeaveCriticalSection(&g_hostEventCs);
AtomicWrite(&g_lobbyHasRemoteMember, 1);
AtomicWrite(&g_peerConnected, 1);
FireLobbyMemberJoined(id);
FirePeerConnectionRequest(id);
// Established is intentionally omitted; game must call AcceptConnection
} else {
LeaveCriticalSection(&g_hostEventCs);
}
} else {
// Fallbacks for late-registered callbacks only
if (AtomicRead(&g_lobbyHasRemoteMember) && g_memberStatusCb &&
InterlockedCompareExchange(&g_lobbyMemberJoinedFired, 1, 0) == 0) {
FireLobbyMemberJoined(id);
}
if (AtomicRead(&g_peerConnected) && g_connectionRequestCb &&
InterlockedCompareExchange(&g_connectionRequestFired, 1, 0) == 0) {
FirePeerConnectionRequest(id);
}
// Removed Established fallback to enforce AcceptConnection contract
}
}
static bool DiscardMitmHelloIfPresentLocked() {
// Caller must hold g_socketCs
if (udpSocket == INVALID_SOCKET) return false;
char peekBuf[64];
struct sockaddr_in from;
int fromLen = sizeof(from);
int peeked = recvfrom(udpSocket, peekBuf, sizeof(peekBuf), MSG_PEEK,
(struct sockaddr *)&from, &fromLen);
bool result = false;
size_t helloLen = strlen(kMitmHello);
size_t ackLen = strlen(kMitmAck);
if (peeked == (int)helloLen && memcmp(peekBuf, kMitmHello, helloLen) == 0) {
recvfrom(udpSocket, peekBuf, sizeof(peekBuf), 0,
(struct sockaddr *)&from, &fromLen);
if (AtomicRead(&g_isHost)) {
if (!AtomicRead(&g_hostHasClientAddr)) {
targetAddr = from;
AtomicWrite(&g_hostHasClientAddr, 1);
}
SendHelloAckLocked(&from);
TryFireHostEvents(ClientProductUserId());
}
Log("[Net] Filtered DirectIP hello from %s:%d",
inet_ntoa(from.sin_addr), ntohs(from.sin_port));
result = true;
} else if (!AtomicRead(&g_isHost) && peeked == (int)ackLen &&
memcmp(peekBuf, kMitmAck, ackLen) == 0) {
recvfrom(udpSocket, peekBuf, sizeof(peekBuf), 0,
(struct sockaddr *)&from, &fromLen);
AtomicWrite(&g_helloAckReceived, 1);
AtomicWrite(&g_peerConnected, 1);
AtomicWrite(&g_lobbyHasRemoteMember, 1);
Log("[Net] Filtered DirectIP ACK from %s:%d",
inet_ntoa(from.sin_addr), ntohs(from.sin_port));
result = true;
}
return result;
}
DWORD WINAPI PeerWatchThread(LPVOID) {
InitNetwork();
Log("[Thread] PeerWatchThread started. isHost=%d", (int)AtomicRead(&g_isHost));
if (AtomicRead(&g_isHost)) {
int loopCount = 0;
while (loopCount < 400) {
if (WaitForSingleObject(g_stopEvent, 0) == WAIT_OBJECT_0) break;
char peekBuf[64];
struct sockaddr_in from;
int fromLen = sizeof(from);
bool consumedHello = false;
bool gotNonHello = false;
EnterCriticalSection(&g_socketCs);
u_long bytesAvail = 0;
if (ioctlsocket(udpSocket, FIONREAD, &bytesAvail) == 0 && bytesAvail > 0) {
int peeked = recvfrom(udpSocket, peekBuf, sizeof(peekBuf), MSG_PEEK,
(struct sockaddr*)&from, &fromLen);
if (peeked > 0) {
size_t helloLen = strlen(kMitmHello);
if (peeked == (int)helloLen &&
memcmp(peekBuf, kMitmHello, helloLen) == 0) {
recvfrom(udpSocket, peekBuf, sizeof(peekBuf), 0,
(struct sockaddr*)&from, &fromLen);
consumedHello = true;
} else {
gotNonHello = true;
}
}
}
LeaveCriticalSection(&g_socketCs);
if (consumedHello) {
Log("[Net] Consumed DirectIP hello from client %s:%d",
inet_ntoa(from.sin_addr), ntohs(from.sin_port));
EnterCriticalSection(&g_socketCs);
if (!AtomicRead(&g_hostHasClientAddr)) {
targetAddr = from;
AtomicWrite(&g_hostHasClientAddr, 1);
}
LeaveCriticalSection(&g_socketCs);
SendHelloAck(&from);
TryFireHostEvents(ClientProductUserId());
continue;
}
if (gotNonHello) {
if (!AtomicRead(&g_hostHasClientAddr)) {
EnterCriticalSection(&g_socketCs);
if (!AtomicRead(&g_hostHasClientAddr)) {
targetAddr = from;
AtomicWrite(&g_hostHasClientAddr, 1);
}
LeaveCriticalSection(&g_socketCs);
}
TryFireHostEvents(ClientProductUserId());
// FIX: Exit after request+member are fired, not waiting for Established
if (AtomicRead(&g_connectionRequestFired) && AtomicRead(&g_lobbyMemberJoinedFired)) {
Log("[Thread] Host bootstrap complete, exiting watch thread");
break;
}
}
if (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected)) {
TryFireHostEvents(ClientProductUserId());
if (AtomicRead(&g_connectionRequestFired) && AtomicRead(&g_lobbyMemberJoinedFired)) {
Log("[Thread] Host bootstrap complete, exiting watch thread");
break;
}
}
Sleep(50);
if (++loopCount % 20 == 0) {
Log("[Thread] Host waiting for client hello... (%d loops)", loopCount);
}
}
} else {
int helloRetry = 0;
while (AtomicRead(&g_helloAckReceived) == 0 && AtomicRead(&g_peerConnected) == 0) {
if (WaitForSingleObject(g_stopEvent, 0) == WAIT_OBJECT_0) break;
EnterCriticalSection(&g_socketCs);
sendto(udpSocket, kMitmHello, (int)strlen(kMitmHello), 0,
(struct sockaddr *)&targetAddr, sizeof(targetAddr));
LeaveCriticalSection(&g_socketCs);
Log("[Net] Client sent DirectIP hello to host (attempt %d)", helloRetry + 1);
DWORD start = GetTickCount();
while (GetTickCount() - start < 250) {
if (WaitForSingleObject(g_stopEvent, 0) == WAIT_OBJECT_0) break;
char buf[64];
struct sockaddr_in from;
int fromLen = sizeof(from);
int r = -1;
EnterCriticalSection(&g_socketCs);
u_long bytesAvail = 0;
if (ioctlsocket(udpSocket, FIONREAD, &bytesAvail) == 0 && bytesAvail > 0) {
r = recvfrom(udpSocket, buf, sizeof(buf), MSG_PEEK,
(struct sockaddr*)&from, &fromLen);
}
LeaveCriticalSection(&g_socketCs);
if (r > 0) {
size_t ackLen = strlen(kMitmAck);
if (r == (int)ackLen && memcmp(buf, kMitmAck, ackLen) == 0) {
EnterCriticalSection(&g_socketCs);
recvfrom(udpSocket, buf, sizeof(buf), 0,
(struct sockaddr*)&from, &fromLen);
LeaveCriticalSection(&g_socketCs);
AtomicWrite(&g_helloAckReceived, 1);
AtomicWrite(&g_peerConnected, 1);
AtomicWrite(&g_lobbyHasRemoteMember, 1);
Log("[Net] Client received ACK from host %s:%d",
inet_ntoa(from.sin_addr), ntohs(from.sin_port));
break;
} else {
AtomicWrite(&g_peerConnected, 1);
AtomicWrite(&g_lobbyHasRemoteMember, 1);
Log("[Net] Client received data from host, assuming connected");
break;
}
}
Sleep(10);
}
if (AtomicRead(&g_helloAckReceived) || AtomicRead(&g_peerConnected)) break;
helloRetry++;
if (helloRetry > 40) {
Log("[Net] Client gave up waiting for host after %d retries", helloRetry);
break;
}
}
int eventRetry = 0;
while (eventRetry < 200) {
if (WaitForSingleObject(g_stopEvent, 0) == WAIT_OBJECT_0) break;
bool needRequest = AtomicRead(&g_connectionRequestFired) == 0;
bool needEstablished = AtomicRead(&g_establishedFired) == 0;
bool needMember = AtomicRead(&g_lobbyMemberJoinedFired) == 0;
if (!needRequest && !needEstablished && !needMember) {
Log("[Thread] Client all events fired successfully");
break;
}
if (needMember && g_memberStatusCb) {
FireLobbyMemberJoined(HostProductUserId());
}
if (needRequest && g_connectionRequestCb) {
FirePeerConnectionRequest(HostProductUserId());
}
if (needEstablished && g_connectionEstablishedCb) {
FirePeerConnectionEstablished(HostProductUserId());
}
Sleep(100);
eventRetry++;
}
}
Log("[Thread] PeerWatchThread exiting");
// Clear the thread handle under lock to prevent race with ResetConnectionState
EnterCriticalSection(&g_socketCs);
g_peerWatchThread = NULL;
LeaveCriticalSection(&g_socketCs);
return 0;
}
extern "C" {
__declspec(dllexport) EOS_EResult
EOS_P2P_SendPacket(EOS_HP2P Handle, const EOS_P2P_SendPacketOptions *Options) {
Log("[Proxy] >> EOS_P2P_SendPacket entry");
if (!Options) {
Log("[Proxy] !! EOS_P2P_SendPacket rejected: Options is NULL");
return EOS_InvalidParameters;
}
InitNetwork();
struct sockaddr_in sendAddr;
EnterCriticalSection(&g_socketCs);
sendAddr = targetAddr;
LeaveCriticalSection(&g_socketCs);
EnterCriticalSection(&g_socketCs);
int sent = sendto(udpSocket, (const char *)Options->Data, Options->DataLengthBytes,
0, (struct sockaddr *)&sendAddr, sizeof(sendAddr));
LeaveCriticalSection(&g_socketCs);
static LONG g_sendLogCount = 0;
if (InterlockedIncrement(&g_sendLogCount) <= 80 || sent == SOCKET_ERROR) {
Log("[Proxy] SendPacket: requested=%u sent=%d channel=%u remote=%p",
Options->DataLengthBytes, sent, Options->Channel, Options->RemoteUserId);
}
if (sent == SOCKET_ERROR) {
Log("[Proxy] !! sendto failed: WSA %d", WSAGetLastError());
} else {
DumpPacket("SEND", Options->Data, (uint32_t)sent, &sendAddr, Options->Channel);
}
Log("[Proxy] << EOS_P2P_SendPacket exit (result=Success)");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_GetNextReceivedPacketSize(
EOS_HP2P Handle, const EOS_P2P_GetNextReceivedPacketSizeOptions *Options,
uint32_t *OutPacketSizeBytes) {
InitNetwork();
if (!OutPacketSizeBytes) {
Log("[Proxy] !! GetNextReceivedPacketSize rejected: OutPacketSizeBytes is NULL");
return EOS_InvalidParameters;
}
EnterCriticalSection(&g_socketCs);
while (DiscardMitmHelloIfPresentLocked()) {}
char tempBuf[65536];
struct sockaddr_in from;
int fromLen = sizeof(from);
int peeked = recvfrom(udpSocket, tempBuf, sizeof(tempBuf), MSG_PEEK,
(struct sockaddr*)&from, &fromLen);
LeaveCriticalSection(&g_socketCs);
if (peeked > 0) {
*OutPacketSizeBytes = (uint32_t)peeked;
static LONG g_sizeLogCount = 0;
if (InterlockedIncrement(&g_sizeLogCount) <= 80) {
int requestedChannel = (Options && Options->RequestedChannel) ? (int)*Options->RequestedChannel : -1;
Log("[Proxy] GetNextReceivedPacketSize: %d bytes requestedChannel=%d",
peeked, requestedChannel);
}
return EOS_Success;
}
return EOS_NotFound;
}
__declspec(dllexport) EOS_EResult EOS_P2P_ReceivePacket(
EOS_HP2P Handle, const EOS_P2P_ReceivePacketOptions *Options,
void *OutPeerId, void *OutSocketId, uint8_t *OutChannel, void *OutData,
uint32_t *OutBytesWritten) {
Log("[Proxy] >> EOS_P2P_ReceivePacket entry");
InitNetwork();
if (!Options || !OutData || !OutBytesWritten) {
Log("[Proxy] !! ReceivePacket rejected: bad args (Options=%p OutData=%p OutBytesWritten=%p)",
(void*)Options, OutData, (void*)OutBytesWritten);
return EOS_InvalidParameters;
}
struct sockaddr_in from;
int fromLen = sizeof(from);
EnterCriticalSection(&g_socketCs);
while (DiscardMitmHelloIfPresentLocked()) {}
int ret = recvfrom(udpSocket, (char *)OutData, Options->MaxDataSizeBytes, 0,
(struct sockaddr *)&from, &fromLen);
LeaveCriticalSection(&g_socketCs);
if (ret > 0) {
// Only update host client address on first packet, without recursive locking
if (AtomicRead(&g_isHost) && AtomicRead(&g_hostHasClientAddr) == 0) {
HostOnFirstPacketReceivedNoLock(&from);
}
*OutBytesWritten = ret;
uint8_t channel = Options->RequestedChannel ? *Options->RequestedChannel : 0;
if (OutChannel) *OutChannel = channel;
if (OutPeerId) *(EOS_ProductUserId *)OutPeerId = RemoteProductUserId();
if (OutSocketId) memcpy(OutSocketId, &g_socketId, sizeof(g_socketId));
DumpPacket("RECV", OutData, (uint32_t)ret, &from, channel);
static LONG g_receiveLogCount = 0;
if (InterlockedIncrement(&g_receiveLogCount) <= 80) {
Log("[Proxy] ReceivePacket: %d bytes from %s:%d peer=%p channel=%u",
ret, inet_ntoa(from.sin_addr), ntohs(from.sin_port),
RemoteProductUserId(), channel);
}
Log("[Proxy] << EOS_P2P_ReceivePacket exit (success, %d bytes)", ret);
return EOS_Success;
}
Log("[Proxy] << EOS_P2P_ReceivePacket exit (NotFound)");
return EOS_NotFound;
}
__declspec(dllexport) EOS_EResult
EOS_P2P_AcceptConnection(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] >> EOS_P2P_AcceptConnection called");
AtomicWrite(&g_peerConnected, 1);
LogStateSnapshot("AcceptConnection");
QueueCallback([=]() {
FirePeerConnectionEstablished(RemoteProductUserId());
});
Log("[Proxy] << EOS_P2P_AcceptConnection exit");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_CloseConnection(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_CloseConnection called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_P2P_CloseConnections(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_CloseConnections called (resetting DirectIP state)");
ResetConnectionState();
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_P2P_QueryNATType(EOS_HP2P Handle, const void *Options, void *ClientData, void *CompletionDelegate) {
Log("[Proxy] EOS_P2P_QueryNATType called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_GetNATType(EOS_HP2P Handle, const void *Options, int32_t *OutNATType) {
Log("[Proxy] EOS_P2P_GetNATType called");
if (OutNATType) *OutNATType = 0;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_SetRelayControl(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_SetRelayControl called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_GetRelayControl(EOS_HP2P Handle, const void *Options, int32_t *OutRelayControl) {
Log("[Proxy] EOS_P2P_GetRelayControl called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_SetPortRange(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_SetPortRange called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_P2P_GetPortRange(EOS_HP2P Handle, const void *Options, uint16_t *OutPort, uint16_t *OutNumAdditionalPortsToTry) {
Log("[Proxy] EOS_P2P_GetPortRange called");
return EOS_Success;
}
__declspec(dllexport) uint64_t EOS_P2P_AddNotifyPeerConnectionRequest(
EOS_HP2P Handle, const void *Options, void *ClientData,
EOS_P2P_OnIncomingConnectionRequestCallback NotificationFn) {
static LONG g_addCount = 0;
if (InterlockedIncrement(&g_addCount) <= 2)
Log("[Proxy] EOS_P2P_AddNotifyPeerConnectionRequest called (count=%d)", (int)AtomicRead(&g_addCount));
g_connectionRequestCb = NotificationFn;
g_connectionRequestCbData = ClientData;
Log("[Proxy] Stored connection request callback=%p data=%p", (void*)NotificationFn, ClientData);
if (AtomicRead(&g_peerConnected) && NotificationFn) {
Log("[Proxy] Peer already connected, immediately firing request");
FirePeerConnectionRequest(RemoteProductUserId());
}
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_P2P_RemoveNotifyPeerConnectionRequest(EOS_HP2P Handle, uint64_t NotificationId) {
Log("[Proxy] EOS_P2P_RemoveNotifyPeerConnectionRequest id=%llu", (unsigned long long)NotificationId);
}
__declspec(dllexport) uint64_t EOS_P2P_AddNotifyPeerConnectionEstablished(
EOS_HP2P Handle, const void *Options, void *ClientData,
EOS_P2P_OnPeerConnectionEstablishedCallback NotificationFn) {
Log("[Proxy] EOS_P2P_AddNotifyPeerConnectionEstablished called cb=%p", (void*)NotificationFn);
g_connectionEstablishedCb = NotificationFn;
g_connectionEstablishedCbData = ClientData;
if (AtomicRead(&g_peerConnected) && NotificationFn) {
Log("[Proxy] Peer already connected, immediately firing established");
FirePeerConnectionEstablished(RemoteProductUserId());
}
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_P2P_RemoveNotifyPeerConnectionEstablished(EOS_HP2P Handle, uint64_t NotificationId) {
Log("[Proxy] EOS_P2P_RemoveNotifyPeerConnectionEstablished id=%llu", (unsigned long long)NotificationId);
}
__declspec(dllexport) uint64_t EOS_P2P_AddNotifyPeerConnectionInterrupted(
EOS_HP2P Handle, const void *Options, void *ClientData, void *NotificationFn) {
Log("[Proxy] EOS_P2P_AddNotifyPeerConnectionInterrupted called");
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_P2P_RemoveNotifyPeerConnectionInterrupted(EOS_HP2P Handle, uint64_t NotificationId) {
Log("[Proxy] EOS_P2P_RemoveNotifyPeerConnectionInterrupted id=%llu", (unsigned long long)NotificationId);
}
__declspec(dllexport) uint64_t EOS_P2P_AddNotifyIncomingPacketQueueFull(
EOS_HP2P Handle, const void *Options, void *ClientData, void *NotificationFn) {
Log("[Proxy] EOS_P2P_AddNotifyIncomingPacketQueueFull called");
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_P2P_RemoveNotifyIncomingPacketQueueFull(EOS_HP2P Handle, uint64_t NotificationId) {
Log("[Proxy] EOS_P2P_RemoveNotifyIncomingPacketQueueFull id=%llu", (unsigned long long)NotificationId);
}
__declspec(dllexport) EOS_EResult EOS_P2P_ClearPacketQueue(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_ClearPacketQueue called (ignored)");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_SetPacketQueueSize(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_SetPacketQueueSize called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_P2P_GetPacketQueueInfo(EOS_HP2P Handle, const void *Options, void *OutPacketQueueInfo) {
Log("[Proxy] EOS_P2P_GetPacketQueueInfo called");
if (OutPacketQueueInfo) memset(OutPacketQueueInfo, 0, 32);
return EOS_Success;
}
__declspec(dllexport) uint64_t
EOS_P2P_AddNotifyPeerConnectionClosed(EOS_HP2P Handle, const void *Options, void *ClientData, void *NotificationFn) {
Log("[Proxy] EOS_P2P_AddNotifyPeerConnectionClosed called");
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_P2P_RemoveNotifyPeerConnectionClosed(EOS_HP2P Handle, uint64_t NotificationId) {
Log("[Proxy] EOS_P2P_RemoveNotifyPeerConnectionClosed id=%llu", (unsigned long long)NotificationId);
}
// Auth and Connect Mocking
typedef void *EOS_HAuth;
struct EOS_Auth_LoginOptions {
int32_t ApiVersion;
const void *Credentials;
int32_t ScopeFlags;
};
struct EOS_Auth_LoginCallbackInfo {
int32_t ResultCode;
void *ClientData;
EOS_ProductUserId LocalUserId;
void *PinGrantInfo;
void *ContinuanceToken;
void *AccountFeatureRestrictedInfo;
EOS_ProductUserId SelectedAccountId;
};
typedef void (*EOS_Auth_OnLoginCallback)(const EOS_Auth_LoginCallbackInfo *Data);
typedef void *EOS_HConnect;
struct EOS_Connect_LoginOptions {
int32_t ApiVersion;
const void *Credentials;
const void *UserLoginInfo;
};
struct EOS_Connect_LoginCallbackInfo {
int32_t ResultCode;
void *ClientData;
EOS_ProductUserId LocalUserId;
void *ContinuanceToken;
};
typedef void (*EOS_Connect_OnLoginCallback)(const EOS_Connect_LoginCallbackInfo *Data);
struct EOS_Connect_CreateUserOptions {
int32_t ApiVersion;
void *ContinuanceToken;
};
struct EOS_Connect_CreateUserCallbackInfo {
int32_t ResultCode;
void *ClientData;
EOS_ProductUserId LocalUserId;
};
typedef void (*EOS_Connect_OnCreateUserCallback)(const EOS_Connect_CreateUserCallbackInfo *Data);
__declspec(dllexport) void
EOS_Auth_Login(EOS_HAuth Handle, const EOS_Auth_LoginOptions *Options,
void *ClientData, EOS_Auth_OnLoginCallback CompletionDelegate) {
Log("[Proxy] >> EOS_Auth_Login called (queued)");
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Auth_LoginCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LocalUserId = (EOS_ProductUserId)1;
info.SelectedAccountId = (EOS_ProductUserId)1;
Log("[Proxy] Invoking EOS_Auth_Login callback (async)");
CompletionDelegate(&info);
});
}
Log("[Proxy] << EOS_Auth_Login exit");
}
__declspec(dllexport) void
EOS_Connect_Login(EOS_HConnect Handle, const EOS_Connect_LoginOptions *Options,
void *ClientData, EOS_Connect_OnLoginCallback CompletionDelegate) {
static LONG g_connectLoginCount = 0;
if (InterlockedIncrement(&g_connectLoginCount) <= 3)
Log("[Proxy] EOS_Connect_Login called (call #%d, queued)", (int)AtomicRead(&g_connectLoginCount));
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Connect_LoginCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LocalUserId = LocalProductUserId();
Log("[Proxy] Invoking EOS_Connect_Login callback (async) localUser=%p", info.LocalUserId);
CompletionDelegate(&info);
});
}
}
__declspec(dllexport) void EOS_Connect_CreateUser(
EOS_HConnect Handle, const EOS_Connect_CreateUserOptions *Options,
void *ClientData, EOS_Connect_OnCreateUserCallback CompletionDelegate) {
Log("[Proxy] EOS_Connect_CreateUser called (queued)");
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Connect_CreateUserCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LocalUserId = LocalProductUserId();
CompletionDelegate(&info);
});
}
}
__declspec(dllexport) uint64_t
EOS_Connect_AddNotifyAuthExpiration(EOS_HConnect Handle, const void *Options,
void *ClientData, void *NotificationFn) {
Log("[Proxy] EOS_Connect_AddNotifyAuthExpiration called");
return 3;
}
__declspec(dllexport) EOS_ProductUserId
EOS_EpicAccountId_FromString(const char *AccountIdString) {
Log("[Proxy] EOS_EpicAccountId_FromString(%s)", AccountIdString ? AccountIdString : "NULL");
return (EOS_ProductUserId)1;
}
__declspec(dllexport) EOS_ProductUserId
EOS_ProductUserId_FromString(const char *AccountIdString) {
Log("[Proxy] EOS_ProductUserId_FromString(%s)", AccountIdString ? AccountIdString : "NULL");
if (AccountIdString && strstr(AccountIdString, "HOST")) return HostProductUserId();
if (AccountIdString && strstr(AccountIdString, "CLIENT")) return ClientProductUserId();
return LocalProductUserId();
}
__declspec(dllexport) int32_t
EOS_EpicAccountId_IsValid(EOS_ProductUserId AccountId) {
return 1;
}
__declspec(dllexport) int32_t
EOS_ProductUserId_IsValid(EOS_ProductUserId AccountId) {
int valid = (AccountId == HostProductUserId() || AccountId == ClientProductUserId());
Log("[Proxy] EOS_ProductUserId_IsValid(%p) -> %d", AccountId, valid);
return valid;
}
__declspec(dllexport) EOS_EResult
EOS_ProductUserId_ToString(EOS_ProductUserId AccountId, char *OutBuffer,
int32_t *InOutBufferLength) {
const char *value = nullptr;
if (AccountId == HostProductUserId()) value = "DIRECTIP_HOST_PUID";
else if (AccountId == ClientProductUserId()) value = "DIRECTIP_CLIENT_PUID";
else {
Log("[Proxy] !! EOS_ProductUserId_ToString unknown AccountId=%p", AccountId);
return EOS_InvalidParameters;
}
int32_t needed = (int32_t)strlen(value) + 1;
if (!InOutBufferLength) return EOS_InvalidParameters;
if (!OutBuffer || *InOutBufferLength < needed) {
*InOutBufferLength = needed;
return EOS_InvalidParameters;
}
memcpy(OutBuffer, value, needed);
*InOutBufferLength = needed;
Log("[Proxy] EOS_ProductUserId_ToString(%p) -> %s", AccountId, value);
return EOS_Success;
}
struct EOS_Connect_CopyIdTokenOptions {
int32_t ApiVersion;
EOS_ProductUserId LocalUserId;
};
struct EOS_Connect_IdToken {
int32_t ApiVersion;
EOS_ProductUserId ProductUserId;
const char* JsonWebToken;
};
__declspec(dllexport) EOS_EResult EOS_Connect_CopyIdToken(
EOS_HConnect Handle, const EOS_Connect_CopyIdTokenOptions* Options,
EOS_Connect_IdToken** OutIdToken) {
Log("[Proxy] EOS_Connect_CopyIdToken called");
if (!Options || !OutIdToken) return EOS_InvalidParameters;
EOS_Connect_IdToken* token = (EOS_Connect_IdToken*)calloc(1, sizeof(EOS_Connect_IdToken));
if (!token) return EOS_InvalidParameters;
token->ApiVersion = 1;
token->ProductUserId = Options->LocalUserId ? Options->LocalUserId : LocalProductUserId();
token->JsonWebToken = "eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZHVjdFVzZXJJZCI6IkRJUkVDVElQX0hPU1RfUFVJRCJ9.";
*OutIdToken = token;
Log("[Proxy] EOS_Connect_CopyIdToken -> success, token=%p", (void*)token);
return EOS_Success;
}
__declspec(dllexport) void EOS_Connect_IdToken_Release(EOS_Connect_IdToken* IdToken) {
Log("[Proxy] EOS_Connect_IdToken_Release called");
if (IdToken) {
free(IdToken);
}
}
struct EOS_Connect_VerifyIdTokenOptions {
int32_t ApiVersion;
const EOS_Connect_IdToken* IdToken;
};
struct EOS_Connect_VerifyIdTokenCallbackInfo {
int32_t ResultCode;
void* ClientData;
EOS_ProductUserId ProductUserId;
};
typedef void (*EOS_Connect_OnVerifyIdTokenCallback)(const EOS_Connect_VerifyIdTokenCallbackInfo* Data);
__declspec(dllexport) void EOS_Connect_VerifyIdToken(
EOS_HConnect Handle, const EOS_Connect_VerifyIdTokenOptions* Options,
void* ClientData, EOS_Connect_OnVerifyIdTokenCallback CompletionDelegate) {
Log("[Proxy] EOS_Connect_VerifyIdToken called (queued)");
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Connect_VerifyIdTokenCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.ProductUserId = (Options && Options->IdToken) ? Options->IdToken->ProductUserId : LocalProductUserId();
Log("[Proxy] Invoking EOS_Connect_VerifyIdToken callback -> Success");
CompletionDelegate(&info);
});
}
}
// Lobby Mocking
typedef void *EOS_HLobby;
typedef void *EOS_HLobbySearch;
typedef void *EOS_HLobbyDetails;
struct EOS_Lobby_CreateLobbyCallbackInfo {
int32_t ResultCode;
void *ClientData;
const char *LobbyId;
};
typedef void (*EOS_Lobby_OnCreateLobbyCallback)(const EOS_Lobby_CreateLobbyCallbackInfo *Data);
struct EOS_Lobby_JoinLobbyCallbackInfo {
int32_t ResultCode;
void *ClientData;
const char *LobbyId;
};
typedef void (*EOS_Lobby_OnJoinLobbyCallback)(const EOS_Lobby_JoinLobbyCallbackInfo *Data);
struct EOS_LobbySearch_FindCallbackInfo {
int32_t ResultCode;
void *ClientData;
};
typedef void (*EOS_LobbySearch_OnFindCallback)(const EOS_LobbySearch_FindCallbackInfo *Data);
struct EOS_LobbySearch_CopySearchResultByIndexOptions {
int32_t ApiVersion;
uint32_t LobbyIndex;
};
struct EOS_LobbyDetails_Info {
int32_t ApiVersion;
const char *LobbyId;
EOS_ProductUserId LobbyOwnerUserId;
uint32_t PermissionLevel;
uint32_t AvailableSlots;
uint32_t MaxMembers;
int32_t bAllowInvites;
const char *BucketId;
int32_t bAllowHostMigration;
int32_t bRTCRoomEnabled;
int32_t bAllowJoinById;
int32_t bRejoinAfterKickRequiresInvite;
};
__declspec(dllexport) void
EOS_Lobby_CreateLobby(EOS_HLobby Handle, const void *Options, void *ClientData,
EOS_Lobby_OnCreateLobbyCallback CompletionDelegate) {
Log("[Proxy] >> EOS_Lobby_CreateLobby called");
InitNetwork();
if (!AtomicRead(&g_isHost) && AtomicRead(&g_clientSawSearchResult)) {
Log("[Proxy] Client called CreateLobby after seeing search result; forcing DirectIP join behavior");
AtomicWrite(&g_lobbyHasRemoteMember, 1);
}
if (!g_peerWatchThread) {
ResetEvent(g_stopEvent);
g_peerWatchThread = CreateThread(NULL, 0, PeerWatchThread, NULL, 0, NULL);
Log("[Proxy] Started PeerWatchThread from CreateLobby");
}
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Lobby_CreateLobbyCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LobbyId = "DirectIP_Lobby";
Log("[Proxy] Invoking CreateLobby callback (async)");
CompletionDelegate(&info);
});
}
LogStateSnapshot("CreateLobby");
Log("[Proxy] << EOS_Lobby_CreateLobby exit");
}
__declspec(dllexport) void
EOS_Lobby_JoinLobby(EOS_HLobby Handle, const void *Options, void *ClientData,
EOS_Lobby_OnJoinLobbyCallback CompletionDelegate) {
Log("[Proxy] >> EOS_Lobby_JoinLobby called");
InitNetwork();
if (!AtomicRead(&g_isHost)) AtomicWrite(&g_lobbyHasRemoteMember, 1);
if (!g_peerWatchThread) {
ResetEvent(g_stopEvent);
g_peerWatchThread = CreateThread(NULL, 0, PeerWatchThread, NULL, 0, NULL);
Log("[Proxy] Started PeerWatchThread from JoinLobby");
}
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Lobby_JoinLobbyCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LobbyId = "DirectIP_Lobby";
Log("[Proxy] Invoking JoinLobby callback (async)");
CompletionDelegate(&info);
});
}
LogStateSnapshot("JoinLobby");
Log("[Proxy] << EOS_Lobby_JoinLobby exit");
}
__declspec(dllexport) EOS_EResult
EOS_Lobby_CreateLobbySearch(EOS_HLobby Handle, const void *Options,
EOS_HLobbySearch *OutLobbySearchHandle) {
Log("[Proxy] EOS_Lobby_CreateLobbySearch called");
if (OutLobbySearchHandle) *OutLobbySearchHandle = (EOS_HLobbySearch)3;
return EOS_Success;
}
__declspec(dllexport) void
EOS_LobbySearch_Find(EOS_HLobbySearch Handle, const void *Options,
void *ClientData,
EOS_LobbySearch_OnFindCallback CompletionDelegate) {
Log("[Proxy] >> EOS_LobbySearch_Find called");
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_LobbySearch_FindCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
Log("[Proxy] Invoking LobbySearch_Find callback (async)");
CompletionDelegate(&info);
});
}
Log("[Proxy] << EOS_LobbySearch_Find exit");
}
__declspec(dllexport) uint32_t EOS_LobbySearch_GetSearchResultCount(
EOS_HLobbySearch Handle, const void *Options) {
InitNetwork();
uint32_t count = AtomicRead(&g_isHost) ? 0 : 1;
Log("[Proxy] EOS_LobbySearch_GetSearchResultCount -> %u", count);
return count;
}
__declspec(dllexport) EOS_EResult EOS_LobbySearch_CopySearchResultByIndex(
EOS_HLobbySearch Handle,
const EOS_LobbySearch_CopySearchResultByIndexOptions *Options,
EOS_HLobbyDetails *OutLobbyDetailsHandle) {
Log("[Proxy] EOS_LobbySearch_CopySearchResultByIndex called");
AtomicWrite(&g_clientSawSearchResult, 1);
LogStateSnapshot("CopySearchResultByIndex");
if (OutLobbyDetailsHandle) *OutLobbyDetailsHandle = (EOS_HLobbyDetails)4;
return EOS_Success;
}
static EOS_LobbyDetails_Info dummyLobbyInfo = {
2, "DirectIP_Lobby", (EOS_ProductUserId)1,
0, 1, 4, 1, "DirectIP_Bucket",
0, 0, 1, 0
};
__declspec(dllexport) EOS_EResult
EOS_LobbyDetails_CopyInfo(EOS_HLobbyDetails Handle, const void *Options,
EOS_LobbyDetails_Info **OutLobbyDetailsInfo) {
static LONG g_copyInfoLogCount = 0;
dummyLobbyInfo.AvailableSlots = (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected)) ? 0 : 1;
dummyLobbyInfo.MaxMembers = g_maxMembers;
if (InterlockedIncrement(&g_copyInfoLogCount) <= 20) {
Log("[Proxy] EOS_LobbyDetails_CopyInfo owner=%p slots=%u/%u bucket=%s",
dummyLobbyInfo.LobbyOwnerUserId, dummyLobbyInfo.AvailableSlots,
dummyLobbyInfo.MaxMembers, dummyLobbyInfo.BucketId);
}
if (OutLobbyDetailsInfo) *OutLobbyDetailsInfo = &dummyLobbyInfo;
return EOS_Success;
}
__declspec(dllexport) EOS_ProductUserId
EOS_LobbyDetails_GetLobbyOwner(EOS_HLobbyDetails Handle, const void *Options) {
static LONG g_ownerLogCount = 0;
if (InterlockedIncrement(&g_ownerLogCount) <= 80) Log("[Proxy] EOS_LobbyDetails_GetLobbyOwner called");
return HostProductUserId();
}
__declspec(dllexport) void
EOS_LobbyDetails_Release(void* LobbyDetailsHandle) {
(void)LobbyDetailsHandle;
}
__declspec(dllexport) void
EOS_LobbySearch_Release(EOS_HLobbySearch LobbySearchHandle) {
Log("[Proxy] EOS_LobbySearch_Release called");
}
__declspec(dllexport) EOS_EResult
EOS_LobbySearch_RemoveParameter(EOS_HLobbySearch Handle, const void *Options) {
Log("[Proxy] EOS_LobbySearch_RemoveParameter called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_LobbySearch_SetLobbyId(EOS_HLobbySearch Handle, const void *Options) {
Log("[Proxy] EOS_LobbySearch_SetLobbyId called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_LobbySearch_SetMaxResults(EOS_HLobbySearch Handle, const void *Options) {
Log("[Proxy] EOS_LobbySearch_SetMaxResults called");
return EOS_Success;
}
struct EOS_Lobby_AttributeData {
int32_t ApiVersion;
const char* Key;
union { int64_t AsInt64; double AsDouble; int32_t AsBool; const char* AsUtf8; } Value;
int32_t ValueType;
};
struct EOS_LobbySearch_SetParameterOptions {
int32_t ApiVersion;
const EOS_Lobby_AttributeData* Parameter;
int32_t ComparisonOp;
};
__declspec(dllexport) EOS_EResult
EOS_LobbySearch_SetParameter(EOS_HLobbySearch Handle, const EOS_LobbySearch_SetParameterOptions *Options) {
if (Options && Options->Parameter && Options->Parameter->Key) {
if (Options->Parameter->ValueType == 3) {
Log("[Proxy] EOS_LobbySearch_SetParameter: %s = %s", Options->Parameter->Key, Options->Parameter->Value.AsUtf8);
if (Options->Parameter->Value.AsUtf8) {
if (strcmp(Options->Parameter->Key, "AppVersion") == 0) {
strncpy(g_searchAppVersion, Options->Parameter->Value.AsUtf8, sizeof(g_searchAppVersion) - 1);
g_searchAppVersion[sizeof(g_searchAppVersion) - 1] = '\0';
} else if (strcmp(Options->Parameter->Key, "OnlineRule") == 0) {
strncpy(g_searchOnlineRule, Options->Parameter->Value.AsUtf8, sizeof(g_searchOnlineRule) - 1);
g_searchOnlineRule[sizeof(g_searchOnlineRule) - 1] = '\0';
}
}
} else {
Log("[Proxy] EOS_LobbySearch_SetParameter: %s (Type: %d)", Options->Parameter->Key, Options->Parameter->ValueType);
}
} else {
Log("[Proxy] EOS_LobbySearch_SetParameter called (empty)");
}
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_LobbySearch_SetTargetUserId(EOS_HLobbySearch Handle, const void *Options) {
Log("[Proxy] EOS_LobbySearch_SetTargetUserId called");
return EOS_Success;
}
typedef void *EOS_HLobbyModification;
struct EOS_LobbyModification_AddAttributeOptions {
int32_t ApiVersion;
const EOS_Lobby_AttributeData* Attribute;
int32_t Visibility;
};
__declspec(dllexport) EOS_EResult EOS_LobbyModification_AddAttribute(
EOS_HLobbyModification Handle, const EOS_LobbyModification_AddAttributeOptions *Options) {
if (Options && Options->Attribute && Options->Attribute->Key) {
if (Options->Attribute->ValueType == 3) {
Log("[Proxy] EOS_LobbyModification_AddAttribute: %s = %s", Options->Attribute->Key, Options->Attribute->Value.AsUtf8);
if (Options->Attribute->Value.AsUtf8) {
if (strcmp(Options->Attribute->Key, "AppVersion") == 0) {
strncpy(g_lobbyAppVersion, Options->Attribute->Value.AsUtf8, sizeof(g_lobbyAppVersion) - 1);
g_lobbyAppVersion[sizeof(g_lobbyAppVersion) - 1] = '\0';
} else if (strcmp(Options->Attribute->Key, "OnlineRule") == 0) {
strncpy(g_lobbyOnlineRule, Options->Attribute->Value.AsUtf8, sizeof(g_lobbyOnlineRule) - 1);
g_lobbyOnlineRule[sizeof(g_lobbyOnlineRule) - 1] = '\0';
} else if (strcmp(Options->Attribute->Key, "Platform") == 0) {
strncpy(g_lobbyPlatform, Options->Attribute->Value.AsUtf8, sizeof(g_lobbyPlatform) - 1);
g_lobbyPlatform[sizeof(g_lobbyPlatform) - 1] = '\0';
} else if (strcmp(Options->Attribute->Key, "EosProductID") == 0) {
strncpy(g_lobbyEosProductId, Options->Attribute->Value.AsUtf8, sizeof(g_lobbyEosProductId) - 1);
g_lobbyEosProductId[sizeof(g_lobbyEosProductId) - 1] = '\0';
} else if (strcmp(Options->Attribute->Key, "IsCrossPlatform") == 0) {
g_lobbyIsCrossPlatform = _stricmp(Options->Attribute->Value.AsUtf8, "true") == 0 ||
strcmp(Options->Attribute->Value.AsUtf8, "1") == 0;
}
}
} else {
if (Options->Attribute->ValueType == 1) {
Log("[Proxy] EOS_LobbyModification_AddAttribute: %s = %lld (int64)",
Options->Attribute->Key, (long long)Options->Attribute->Value.AsInt64);
if (strcmp(Options->Attribute->Key, "WinRate") == 0) g_lobbyWinRate = Options->Attribute->Value.AsInt64;
} else if (Options->Attribute->ValueType == 0) {
Log("[Proxy] EOS_LobbyModification_AddAttribute: %s = %d (bool)",
Options->Attribute->Key, Options->Attribute->Value.AsBool);
} else {
Log("[Proxy] EOS_LobbyModification_AddAttribute: %s (Type: %d)", Options->Attribute->Key, Options->Attribute->ValueType);
}
}
} else {
Log("[Proxy] EOS_LobbyModification_AddAttribute called (empty)");
}
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_AddMemberAttribute(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_AddMemberAttribute called");
return EOS_Success;
}
__declspec(dllexport) void
EOS_LobbyModification_Release(EOS_HLobbyModification Handle) {
Log("[Proxy] EOS_LobbyModification_Release called");
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_RemoveAttribute(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_RemoveAttribute called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_RemoveMemberAttribute(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_RemoveMemberAttribute called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_SetAllowedPlatformIds(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_SetAllowedPlatformIds called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_SetBucketId(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_SetBucketId called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_SetInvitesAllowed(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_SetInvitesAllowed called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_SetMaxMembers(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_SetMaxMembers called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyModification_SetPermissionLevel(
EOS_HLobbyModification Handle, const void *Options) {
Log("[Proxy] EOS_LobbyModification_SetPermissionLevel called");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_Lobby_UpdateLobbyModification(
EOS_HLobby Handle, const void *Options,
EOS_HLobbyModification *OutLobbyModificationHandle) {
Log("[Proxy] EOS_Lobby_UpdateLobbyModification called");
if (OutLobbyModificationHandle) *OutLobbyModificationHandle = (EOS_HLobbyModification)5;
return EOS_Success;
}
__declspec(dllexport) uint64_t
EOS_Lobby_AddNotifyLobbyUpdateReceived(EOS_HLobby Handle, const void *Options,
void *ClientData, void *NotificationFn) {
Log("[Proxy] EOS_Lobby_AddNotifyLobbyUpdateReceived stored cb=%p", NotificationFn);
g_lobbyUpdateCb = (EOS_Lobby_OnLobbyUpdateReceivedCallback)NotificationFn;
g_lobbyUpdateCbData = ClientData;
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_Lobby_RemoveNotifyLobbyUpdateReceived(EOS_HLobby Handle, uint64_t InId) {
Log("[Proxy] EOS_Lobby_RemoveNotifyLobbyUpdateReceived id=%llu", (unsigned long long)InId);
}
struct EOS_Lobby_Attribute {
int32_t ApiVersion;
EOS_Lobby_AttributeData* Data;
int32_t Visibility;
};
static EOS_Lobby_AttributeData attrData1 = { 1, "AppVersion", {.AsUtf8 = "1.2.0"}, 3 };
static EOS_Lobby_Attribute attr1 = { 1, &attrData1, 0 };
static EOS_Lobby_AttributeData attrData2 = { 1, "OnlineRule", {.AsUtf8 = "Koikoi"}, 3 };
static EOS_Lobby_Attribute attr2 = { 1, &attrData2, 0 };
static EOS_Lobby_AttributeData attrData3 = { 1, "IsCrossPlatform", {.AsUtf8 = "true"}, 3 };
static EOS_Lobby_Attribute attr3 = { 1, &attrData3, 0 };
static EOS_Lobby_AttributeData attrData4 = { 1, "Platform", {.AsUtf8 = "Steam"}, 3 };
static EOS_Lobby_Attribute attr4 = { 1, &attrData4, 0 };
static EOS_Lobby_AttributeData attrData5 = { 1, "WinRate", {.AsInt64 = 0}, 1 };
static EOS_Lobby_Attribute attr5 = { 1, &attrData5, 0 };
static EOS_Lobby_AttributeData attrData6 = { 1, "EosProductID", {.AsUtf8 = ""}, 3 };
static EOS_Lobby_Attribute attr6 = { 1, &attrData6, 0 };
static EOS_Lobby_Attribute* LobbyAttributeByIndex(uint32_t index) {
attrData1.Value.AsUtf8 = g_searchAppVersion[0] ? g_searchAppVersion : g_lobbyAppVersion;
attrData2.Value.AsUtf8 = g_searchOnlineRule[0] ? g_searchOnlineRule : g_lobbyOnlineRule;
attrData3.Value.AsUtf8 = g_lobbyIsCrossPlatform ? "true" : "false";
attrData4.Value.AsUtf8 = g_lobbyPlatform;
attrData5.Value.AsInt64 = g_lobbyWinRate;
attrData6.Value.AsUtf8 = g_lobbyEosProductId[0] ? g_lobbyEosProductId : "DIRECTIP_HOST_PUID";
switch (index) {
case 0: return &attr1;
case 1: return &attr2;
case 2: return &attr3;
case 3: return &attr4;
case 4: return &attr5;
case 5: return &attr6;
default: return nullptr;
}
}
static EOS_Lobby_Attribute* LobbyAttributeByKey(const char* key) {
if (!key) return nullptr;
for (uint32_t i = 0; i < 6; ++i) {
EOS_Lobby_Attribute* attr = LobbyAttributeByIndex(i);
if (attr && attr->Data && strcmp(attr->Data->Key, key) == 0) return attr;
}
return nullptr;
}
__declspec(dllexport) uint32_t EOS_LobbyDetails_GetAttributeCount(
EOS_HLobbyDetails Handle, const void *Options) {
Log("[Proxy] EOS_LobbyDetails_GetAttributeCount called -> 6");
return 6;
}
__declspec(dllexport) EOS_EResult EOS_LobbyDetails_CopyAttributeByIndex(EOS_HLobbyDetails Handle, const void* Options, EOS_Lobby_Attribute** OutAttribute) {
if (!Options || !OutAttribute) return EOS_InvalidParameters;
uint32_t index = ((const uint32_t*)Options)[1];
EOS_Lobby_Attribute* attr = LobbyAttributeByIndex(index);
if (!attr) return EOS_NotFound;
if (attr->Data->ValueType == 3) {
Log("[Proxy] CopyAttributeByIndex %u -> %s = %s", index, attr->Data->Key, attr->Data->Value.AsUtf8);
} else if (attr->Data->ValueType == 1) {
Log("[Proxy] CopyAttributeByIndex %u -> %s = %lld", index, attr->Data->Key, (long long)attr->Data->Value.AsInt64);
} else {
Log("[Proxy] CopyAttributeByIndex %u -> %s type=%d", index, attr->Data->Key, attr->Data->ValueType);
}
*OutAttribute = attr;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_LobbyDetails_CopyAttributeByKey(EOS_HLobbyDetails Handle, const void* Options, EOS_Lobby_Attribute** OutAttribute) {
if (!Options || !OutAttribute) return EOS_InvalidParameters;
const char* key = ((const char* const*)Options)[1];
EOS_Lobby_Attribute* attr = LobbyAttributeByKey(key);
if (!attr) {
Log("[Proxy] CopyAttributeByKey(%s) -> NotFound", key ? key : "NULL");
return EOS_NotFound;
}
if (attr->Data->ValueType == 3) {
Log("[Proxy] CopyAttributeByKey %s = %s", key, attr->Data->Value.AsUtf8);
} else if (attr->Data->ValueType == 1) {
Log("[Proxy] CopyAttributeByKey %s = %lld", key, (long long)attr->Data->Value.AsInt64);
} else {
Log("[Proxy] CopyAttributeByKey %s type=%d", key, attr->Data->ValueType);
}
*OutAttribute = attr;
return EOS_Success;
}
__declspec(dllexport) void EOS_Lobby_Attribute_Release(EOS_Lobby_Attribute* Attribute) {
(void)Attribute;
}
struct EOS_LobbyDetails_CopyMemberInfoOptions {
int32_t ApiVersion;
EOS_ProductUserId TargetUserId;
};
struct EOS_LobbyDetails_MemberInfo {
int32_t ApiVersion;
EOS_ProductUserId ProductUserId;
};
struct EOS_LobbyDetails_GetMemberByIndexOptions {
int32_t ApiVersion;
uint32_t MemberIndex;
};
__declspec(dllexport) EOS_EResult EOS_LobbyDetails_CopyMemberAttributeByIndex(EOS_HLobbyDetails Handle, const void* Options, void** OutAttribute) {
Log("[Proxy] EOS_LobbyDetails_CopyMemberAttributeByIndex called");
if(OutAttribute) *OutAttribute = nullptr;
return EOS_NotFound;
}
__declspec(dllexport) EOS_EResult EOS_LobbyDetails_CopyMemberAttributeByKey(EOS_HLobbyDetails Handle, const void* Options, void** OutAttribute) {
Log("[Proxy] EOS_LobbyDetails_CopyMemberAttributeByKey called");
if(OutAttribute) *OutAttribute = nullptr;
return EOS_NotFound;
}
__declspec(dllexport) EOS_EResult EOS_LobbyDetails_CopyMemberInfo(
EOS_HLobbyDetails Handle, const void* Options, void** OutMemberInfo) {
static EOS_LobbyDetails_MemberInfo hostInfo = {1, (EOS_ProductUserId)1};
static EOS_LobbyDetails_MemberInfo clientInfo = {1, (EOS_ProductUserId)2};
EOS_ProductUserId targetUserId = LocalProductUserId();
if (Options) {
const EOS_LobbyDetails_CopyMemberInfoOptions* opts = (const EOS_LobbyDetails_CopyMemberInfoOptions*)Options;
if (opts->TargetUserId) targetUserId = opts->TargetUserId;
}
EOS_LobbyDetails_MemberInfo* info = nullptr;
if (targetUserId == HostProductUserId()) info = &hostInfo;
else if (targetUserId == ClientProductUserId() && (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected))) info = &clientInfo;
static LONG g_memberInfoLogCount = 0;
if (InterlockedIncrement(&g_memberInfoLogCount) <= 60)
Log("[Proxy] CopyMemberInfo target=%p result=%s", targetUserId, info ? "success" : "notfound");
if (!info) { if (OutMemberInfo) *OutMemberInfo = nullptr; return EOS_NotFound; }
if (OutMemberInfo) *OutMemberInfo = info;
return EOS_Success;
}
__declspec(dllexport) uint32_t EOS_LobbyDetails_GetMemberAttributeCount(
EOS_HLobbyDetails Handle, const void *Options) {
return 0;
}
__declspec(dllexport) EOS_ProductUserId EOS_LobbyDetails_GetMemberByIndex(
EOS_HLobbyDetails Handle, const void *Options) {
uint32_t memberIndex = 0;
if (Options) {
const EOS_LobbyDetails_GetMemberByIndexOptions* opts = (const EOS_LobbyDetails_GetMemberByIndexOptions*)Options;
memberIndex = opts->MemberIndex;
}
EOS_ProductUserId member = nullptr;
if (memberIndex == 0) member = HostProductUserId();
else if (memberIndex == 1 && (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected))) member = ClientProductUserId();
static LONG g_memberByIndexLogCount = 0;
if (InterlockedIncrement(&g_memberByIndexLogCount) <= 80)
Log("[Proxy] GetMemberByIndex index=%u -> %p", memberIndex, member);
return member;
}
__declspec(dllexport) uint32_t
EOS_LobbyDetails_GetMemberCount(EOS_HLobbyDetails Handle, const void *Options) {
uint32_t count = (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected)) ? 2 : 1;
static LONG g_memberCountLogCount = 0;
if (InterlockedIncrement(&g_memberCountLogCount) <= 80) Log("[Proxy] GetMemberCount -> %u", count);
return count;
}
__declspec(dllexport) void
EOS_LobbyDetails_Info_Release(void *LobbyDetailsInfo) {
(void)LobbyDetailsInfo;
}
__declspec(dllexport) void
EOS_LobbyDetails_MemberInfo_Release(void *LobbyDetailsMemberInfo) {
(void)LobbyDetailsMemberInfo;
}
__declspec(dllexport) EOS_EResult
EOS_Lobby_CopyLobbyDetailsHandle(EOS_HLobby Handle, const void *Options,
EOS_HLobbyDetails *OutLobbyDetailsHandle) {
Log("[Proxy] EOS_Lobby_CopyLobbyDetailsHandle called");
if (OutLobbyDetailsHandle) *OutLobbyDetailsHandle = (EOS_HLobbyDetails)4;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_Lobby_CopyLobbyDetailsHandleByInviteId(
EOS_HLobby Handle, const void *Options,
EOS_HLobbyDetails *OutLobbyDetailsHandle) {
Log("[Proxy] EOS_Lobby_CopyLobbyDetailsHandleByInviteId called");
if (OutLobbyDetailsHandle) *OutLobbyDetailsHandle = (EOS_HLobbyDetails)4;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_Lobby_CopyLobbyDetailsHandleByUiEventId(
EOS_HLobby Handle, const void *Options,
EOS_HLobbyDetails *OutLobbyDetailsHandle) {
Log("[Proxy] EOS_Lobby_CopyLobbyDetailsHandleByUiEventId called");
if (OutLobbyDetailsHandle) *OutLobbyDetailsHandle = (EOS_HLobbyDetails)4;
return EOS_Success;
}
struct EOS_Lobby_UpdateLobbyCallbackInfo {
int32_t ResultCode;
void *ClientData;
const char *LobbyId;
};
typedef void (*EOS_Lobby_OnUpdateLobbyCallback)(const EOS_Lobby_UpdateLobbyCallbackInfo *Data);
__declspec(dllexport) void
EOS_Lobby_UpdateLobby(EOS_HLobby Handle, const void *Options, void *ClientData,
EOS_Lobby_OnUpdateLobbyCallback CompletionDelegate) {
Log("[Proxy] >> EOS_Lobby_UpdateLobby called");
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Lobby_UpdateLobbyCallbackInfo info = {EOS_Success, ClientData, "DirectIP_Lobby"};
CompletionDelegate(&info);
});
}
if (g_lobbyUpdateCb) {
QueueCallback([=]() {
EOS_Lobby_LobbyUpdateReceivedCallbackInfo notif = {0};
notif.ClientData = g_lobbyUpdateCbData;
notif.LobbyId = "DirectIP_Lobby";
g_lobbyUpdateCb(&notif);
});
}
if (g_memberUpdateCb) {
QueueCallback([=]() {
EOS_Lobby_LobbyMemberUpdateReceivedCallbackInfo notif = {0};
notif.ClientData = g_memberUpdateCbData;
notif.LobbyId = "DirectIP_Lobby";
notif.TargetUserId = RemoteProductUserId();
g_memberUpdateCb(&notif);
});
}
Log("[Proxy] << EOS_Lobby_UpdateLobby exit");
}
struct EOS_Lobby_DestroyLobbyCallbackInfo {
int32_t ResultCode;
void *ClientData;
const char *LobbyId;
};
typedef void (*EOS_Lobby_OnDestroyLobbyCallback)(const EOS_Lobby_DestroyLobbyCallbackInfo *Data);
__declspec(dllexport) void
EOS_Lobby_DestroyLobby(EOS_HLobby Handle, const void *Options, void *ClientData,
EOS_Lobby_OnDestroyLobbyCallback CompletionDelegate) {
Log("[Proxy] EOS_Lobby_DestroyLobby called");
ResetConnectionState();
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Lobby_DestroyLobbyCallbackInfo info = {EOS_Success, ClientData, "DirectIP_Lobby"};
CompletionDelegate(&info);
});
}
}
struct EOS_Lobby_LeaveLobbyCallbackInfo {
int32_t ResultCode;
void *ClientData;
const char *LobbyId;
};
typedef void (*EOS_Lobby_OnLeaveLobbyCallback)(const EOS_Lobby_LeaveLobbyCallbackInfo *Data);
__declspec(dllexport) void
EOS_Lobby_LeaveLobby(EOS_HLobby Handle, const void *Options, void *ClientData,
EOS_Lobby_OnLeaveLobbyCallback CompletionDelegate) {
Log("[Proxy] EOS_Lobby_LeaveLobby called");
ResetConnectionState();
if (CompletionDelegate) {
QueueCallback([=]() {
EOS_Lobby_LeaveLobbyCallbackInfo info = {EOS_Success, ClientData, "DirectIP_Lobby"};
CompletionDelegate(&info);
});
}
}
__declspec(dllexport) uint64_t EOS_Lobby_AddNotifyLobbyMemberStatusReceived(
EOS_HLobby Handle, const void* Options, void* ClientData,
EOS_Lobby_OnLobbyMemberStatusReceivedCallback NotificationFn) {
Log("[Proxy] EOS_Lobby_AddNotifyLobbyMemberStatusReceived called cb=%p", (void*)NotificationFn);
g_memberStatusCb = NotificationFn;
g_memberStatusCbData = ClientData;
if (AtomicRead(&g_lobbyHasRemoteMember) && NotificationFn) {
Log("[Proxy] Remote member already present, immediately firing status");
FireLobbyMemberJoined(RemoteProductUserId());
}
return g_notifIdCounter++;
}
__declspec(dllexport) void EOS_Lobby_RemoveNotifyLobbyMemberStatusReceived(EOS_HLobby Handle, uint64_t InId) {
Log("[Proxy] EOS_Lobby_RemoveNotifyLobbyMemberStatusReceived id=%llu", (unsigned long long)InId);
}
__declspec(dllexport) uint64_t EOS_Lobby_AddNotifyLobbyMemberUpdateReceived(
EOS_HLobby Handle, const void *Options, void *ClientData,
void *NotificationFn) {
Log("[Proxy] EOS_Lobby_AddNotifyLobbyMemberUpdateReceived stored cb=%p", NotificationFn);
g_memberUpdateCb = (EOS_Lobby_OnLobbyMemberUpdateReceivedCallback)NotificationFn;
g_memberUpdateCbData = ClientData;
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_Lobby_RemoveNotifyLobbyMemberUpdateReceived(EOS_HLobby Handle, uint64_t InId) {
Log("[Proxy] EOS_Lobby_RemoveNotifyLobbyMemberUpdateReceived id=%llu", (unsigned long long)InId);
}
__declspec(dllexport) int32_t EOS_Lobby_IsRTCRoomConnected(
EOS_HLobby Handle, const void *Options, bool *bIsRTCRoomConnected) {
if (bIsRTCRoomConnected) *bIsRTCRoomConnected = false;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_Lobby_GetRTCRoomName(EOS_HLobby Handle, const void *Options,
char *OutBuffer, uint32_t *InOutBufferLength) {
Log("[Proxy] EOS_Lobby_GetRTCRoomName called");
if (!OutBuffer || !InOutBufferLength) return EOS_InvalidParameters;
uint32_t needed = 1;
if (*InOutBufferLength < needed) {
*InOutBufferLength = needed;
return EOS_InvalidParameters;
}
OutBuffer[0] = '\0';
*InOutBufferLength = needed;
return EOS_Success;
}
// some lobby functions
__declspec(dllexport) EOS_EResult EOS_Lobby_IsLobbyOwner(EOS_HLobby Handle, const void* Options, int32_t* bIsLobbyOwner) {
Log("[Proxy] EOS_Lobby_IsLobbyOwner called");
if (bIsLobbyOwner) *bIsLobbyOwner = AtomicRead(&g_isHost) ? 1 : 0;
return EOS_Success;
}
__declspec(dllexport) uint32_t EOS_Lobby_GetInviteCount(EOS_HLobby Handle, const void* Options) {
Log("[Proxy] EOS_Lobby_GetInviteCount called -> 0");
return 0;
}
__declspec(dllexport) EOS_EResult EOS_Lobby_CopyInviteIdByIndex(EOS_HLobby Handle, const void* Options, char* OutBuffer, int32_t* InOutBufferLength) {
Log("[Proxy] EOS_Lobby_CopyInviteIdByIndex called");
return EOS_NotFound;
}
__declspec(dllexport) EOS_EResult EOS_Lobby_GetConnectString(EOS_HLobby Handle, const void* Options, char* OutBuffer, uint32_t* InOutBufferLength) {
Log("[Proxy] EOS_Lobby_GetConnectString called");
if (!OutBuffer || !InOutBufferLength) return EOS_InvalidParameters;
const char* connectStr = "DirectIP_Lobby_Connect";
uint32_t needed = (uint32_t)strlen(connectStr) + 1;
if (*InOutBufferLength < needed) {
*InOutBufferLength = needed;
return EOS_InvalidParameters;
}
memcpy(OutBuffer, connectStr, needed);
*InOutBufferLength = needed;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_Lobby_ParseConnectString(EOS_HLobby Handle, const void* Options, char* OutLobbyId, uint32_t* InOutBufferLength) {
Log("[Proxy] EOS_Lobby_ParseConnectString called");
if (!OutLobbyId || !InOutBufferLength) return EOS_InvalidParameters;
const char* lobbyId = "DirectIP_Lobby";
uint32_t needed = (uint32_t)strlen(lobbyId) + 1;
if (*InOutBufferLength < needed) {
*InOutBufferLength = needed;
return EOS_InvalidParameters;
}
memcpy(OutLobbyId, lobbyId, needed);
*InOutBufferLength = needed;
return EOS_Success;
}
// Platform mocks
typedef void* EOS_HPlatform;
__declspec(dllexport) EOS_EResult EOS_Platform_GetActiveCountryCode(EOS_HPlatform Handle, const void* Options, char* OutBuffer, int32_t* InOutBufferLength) {
if (!OutBuffer || !InOutBufferLength) return EOS_InvalidParameters;
const char* country = "US";
int32_t needed = (int32_t)strlen(country) + 1;
if (*InOutBufferLength < needed) {
*InOutBufferLength = needed;
return EOS_InvalidParameters;
}
memcpy(OutBuffer, country, needed);
*InOutBufferLength = needed;
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_Platform_GetActiveLocaleCode(EOS_HPlatform Handle, const void* Options, char* OutBuffer, int32_t* InOutBufferLength) {
if (!OutBuffer || !InOutBufferLength) return EOS_InvalidParameters;
const char* locale = "en";
int32_t needed = (int32_t)strlen(locale) + 1;
if (*InOutBufferLength < needed) {
*InOutBufferLength = needed;
return EOS_InvalidParameters;
}
memcpy(OutBuffer, locale, needed);
*InOutBufferLength = needed;
return EOS_Success;
}
// EOS_Platform_Tick
typedef void (*EOS_Platform_Tick_Fn)(void* Handle);
static EOS_Platform_Tick_Fn Original_EOS_Platform_Tick = nullptr;
__declspec(dllexport) void EOS_Platform_Tick(void* Handle) {
if (Original_EOS_Platform_Tick) {
Original_EOS_Platform_Tick(Handle);
}
DrainCallbackQueue();
}
} // extern "C"
#include "proxy_pointers.h"
HMODULE hOriginalDll = NULL;
void InitLogFiles() {
InitializeCriticalSection(&g_packetCs);
char path[MAX_PATH];
GetModuleFileNameA(NULL, path, MAX_PATH);
char *lastSlash = strrchr(path, '\\');
if (!lastSlash) lastSlash = strrchr(path, '/');
if (lastSlash) *(lastSlash + 1) = '\0';
else path[0] = '\0';
char logPath[MAX_PATH];
char pktPath[MAX_PATH];
strcpy(logPath, path); strcat(logPath, "proxy_p2p.log");
strcpy(pktPath, path); strcat(pktPath, "proxy_packets.log");
logFile = fopen(logPath, "a");
packetFile = fopen(pktPath, "a");
if (logFile) {
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(logFile, "\n=========================================\n");
fprintf(logFile, "Proxy DLL attached at %02u:%02u:%02u\n", st.wHour, st.wMinute, st.wSecond);
fflush(logFile);
}
if (packetFile) {
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(packetFile, "\n=========================================\n");
fprintf(packetFile, "Packet dump started at %02u:%02u:%02u\n", st.wHour, st.wMinute, st.wSecond);
fflush(packetFile);
}
}
void CloseLogFiles() {
if (logFile) { fclose(logFile); logFile = NULL; }
if (packetFile) { fclose(packetFile); packetFile = NULL; }
DeleteCriticalSection(&g_packetCs);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
DisableThreadLibraryCalls(hModule);
InitializeCriticalSection(&g_hostEventCs);
InitializeCriticalSection(&g_socketCs);
InitCallbackQueue();
g_stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
char path[MAX_PATH];
GetModuleFileNameA(hModule, path, MAX_PATH);
strncpy(g_dllDir, path, MAX_PATH);
char *dirSlash = strrchr(g_dllDir, '\\');
if (!dirSlash) dirSlash = strrchr(g_dllDir, '/');
if (dirSlash) *(dirSlash + 1) = '\0';
else g_dllDir[0] = '\0';
InitLogFiles();
char *lastSlash = strrchr(path, '\\');
if (!lastSlash) lastSlash = strrchr(path, '/');
if (lastSlash) {
*(lastSlash + 1) = '\0';
strcat(path, "EOSSDK-Win64-Shipping_orig.dll");
} else {
strcpy(path, "EOSSDK-Win64-Shipping_orig.dll");
}
Log("[Proxy] DLL Loaded. Redirecting P2P to UDP.");
Log("[Proxy] DLL directory: %s", g_dllDir);
Log("[Proxy] Trying to load original: %s", path);
hOriginalDll = LoadLibraryA(path);
if (hOriginalDll) {
LoadOriginalFunctions(hOriginalDll);
Original_EOS_Platform_Tick = (EOS_Platform_Tick_Fn)GetProcAddress(hOriginalDll, "EOS_Platform_Tick");
Log("[Proxy] Original DLL loaded successfully.");
} else {
Log("[Proxy] Failed to load original DLL! Error: %d", GetLastError());
}
break;
}
case DLL_PROCESS_DETACH:
if (AtomicRead(&g_initialized)) WSACleanup();
Log("[Proxy] DLL detaching. Cleaning up.");
if (g_stopEvent) {
SetEvent(g_stopEvent);
CloseHandle(g_stopEvent);
g_stopEvent = NULL;
}
ShutdownCallbackQueue();
DeleteCriticalSection(&g_hostEventCs);
DeleteCriticalSection(&g_socketCs);
CloseLogFiles();
if (hOriginalDll) FreeLibrary(hOriginalDll);
break;
}
return TRUE;
}