EOSSDK-Holos/main.cpp
2026-08-06 00:16:06 -03:00

2748 lines
No EOL
104 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;
FILE *telemetryFile = 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");
va_end(args);
}
void LogTelemetry(const char *format, ...) {
va_list args;
va_start(args, format);
SYSTEMTIME st;
GetLocalTime(&st);
if (logFile) {
fprintf(logFile, "[%02u:%02u:%02u.%03u] ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
vfprintf(logFile, format, args);
fprintf(logFile, "\n");
}
va_end(args);
va_start(args, format);
if (telemetryFile) {
fprintf(telemetryFile, "[%02u:%02u:%02u.%03u] ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
vfprintf(telemetryFile, format, args);
fprintf(telemetryFile, "\n");
fflush(telemetryFile);
}
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';
}
typedef USHORT(WINAPI *RtlCaptureStackBackTraceFn)(ULONG, ULONG, PVOID *, PULONG);
static RtlCaptureStackBackTraceFn pRtlCaptureStackBackTrace = nullptr;
static void LogCallStack(const char *context) {
if (!pRtlCaptureStackBackTrace) {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
if (ntdll) {
pRtlCaptureStackBackTrace =
(RtlCaptureStackBackTraceFn)GetProcAddress(ntdll, "RtlCaptureStackBackTrace");
}
if (!pRtlCaptureStackBackTrace) {
Log("[Stack/%s] !! RtlCaptureStackBackTrace unavailable", context);
return;
}
}
void *frames[16] = {0};
USHORT count = pRtlCaptureStackBackTrace(1, 16, frames, nullptr);
char buf[2048] = {0};
int pos = 0;
for (USHORT i = 0; i < count && pos < (int)sizeof(buf) - 128; i++) {
HMODULE mod = nullptr;
if (GetModuleHandleExA(0x4 /*FROM_ADDRESS*/ | 0x2 /*UNCHANGED_REFCOUNT*/,
(LPCSTR)frames[i], &mod) && mod) {
char modPath[MAX_PATH] = {0};
GetModuleFileNameA(mod, modPath, MAX_PATH);
const char *modName = strrchr(modPath, '\\');
modName = modName ? modName + 1 : modPath;
pos += sprintf_s(buf + pos, sizeof(buf) - pos, "%s+0x%llx ", modName,
(unsigned long long)((uintptr_t)frames[i] - (uintptr_t)mod));
} else {
pos += sprintf_s(buf + pos, sizeof(buf) - pos, "0x%p ", frames[i]);
}
}
Log("[Stack/%s] %s", context, buf);
}
static bool StackContainsModule(const char *targetModule) {
if (!pRtlCaptureStackBackTrace) {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
if (ntdll) {
pRtlCaptureStackBackTrace =
(RtlCaptureStackBackTraceFn)GetProcAddress(ntdll, "RtlCaptureStackBackTrace");
}
if (!pRtlCaptureStackBackTrace) return true;
}
void *frames[16] = {0};
USHORT count = pRtlCaptureStackBackTrace(1, 16, frames, nullptr);
for (USHORT i = 0; i < count; i++) {
HMODULE mod = nullptr;
if (GetModuleHandleExA(0x4 /*FROM_ADDRESS*/ | 0x2 /*UNCHANGED_REFCOUNT*/,
(LPCSTR)frames[i], &mod) && mod) {
char modPath[MAX_PATH] = {0};
GetModuleFileNameA(mod, modPath, MAX_PATH);
const char *modName = strrchr(modPath, '\\');
modName = modName ? modName + 1 : modPath;
if (_stricmp(modName, targetModule) == 0) return true;
}
}
return false;
}
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);
void TickRetryPendingEvents();
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";
// Per-session log throttle counters (reset in ResetConnectionState)
static LONG g_sendLogCount = 0;
static LONG g_receiveLogCount = 0;
static LONG g_sizeLogCount = 0;
static LONG g_copyInfoLogCount = 0;
static LONG g_ownerLogCount = 0;
static LONG g_memberByIndexLogCount = 0;
static LONG g_memberCountLogCount = 0;
static LONG g_memberInfoLogCount = 0;
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;
typedef void (*EOS_Connect_OnLoginStatusChangedCallback)(const void* Data);
static EOS_Connect_OnLoginStatusChangedCallback g_loginStatusChangedCb = nullptr;
static void* g_loginStatusChangedCbData = nullptr;
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;
static LONG g_watchThreadGeneration = 0;
// Rate limiting for ResetConnectionState
static DWORD g_lastResetTime = 0;
static volatile DWORD g_lastGoodPacketTime = 0; // GetTickCount() of last successful send/recv, for CloseConnections health-gating bodge
static LONG g_resetCount = 0;
static LONG g_resetInProgress = 0;
static const DWORD kAutoTeardownSuppressWindowMs = 100;
static LONG g_autoTeardownSuppressed = 0;
static volatile DWORD g_autoTeardownSuppressTime = 0;
// Telemetry blocking config
static bool g_telemetryBlock = false;
static bool g_dumpTraffic = false;
static char g_blockedDomains[1024] = "api.epicgames.dev";
static char g_blockedIPs[1024] = "104.18.125.108";
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
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() {
DWORD now = GetTickCount();
bool nothingToReset = (udpSocket == INVALID_SOCKET) &&
!g_peerWatchThread &&
AtomicRead(&g_initialized) == 0 &&
AtomicRead(&g_peerConnected) == 0 &&
AtomicRead(&g_lobbyHasRemoteMember) == 0;
if (nothingToReset && (now - g_lastResetTime) < 50) {
Log("[Net] ResetConnectionState: already clean, skipping redundant call");
return;
}
g_lastResetTime = now;
LogCallStack("ResetConnectionState");
// Clear auto-teardown suppression: any real reset cancels the window
AtomicWrite(&g_autoTeardownSuppressed, 0);
AtomicWrite(&g_resetInProgress, 1);
Log("[Net] Resetting connection state for new session");
if (g_peerWatchThread) {
InterlockedIncrement(&g_watchThreadGeneration);
if (g_stopEvent) SetEvent(g_stopEvent);
WaitForSingleObject(g_peerWatchThread, 50); // best-effort; not relied upon below
EnterCriticalSection(&g_socketCs);
CloseHandle(g_peerWatchThread);
g_peerWatchThread = NULL;
LeaveCriticalSection(&g_socketCs);
}
// Reset per-session log throttle counters so sessions 4+ remain visible.
AtomicWrite(&g_sendLogCount, 0);
AtomicWrite(&g_receiveLogCount, 0);
AtomicWrite(&g_sizeLogCount, 0);
AtomicWrite(&g_copyInfoLogCount, 0);
AtomicWrite(&g_ownerLogCount, 0);
AtomicWrite(&g_memberByIndexLogCount, 0);
AtomicWrite(&g_memberCountLogCount, 0);
AtomicWrite(&g_memberInfoLogCount, 0);
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;
AtomicWrite(&g_resetInProgress, 0);
}
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
}
}
void TickRetryPendingEvents() {
bool memberPending = AtomicRead(&g_lobbyHasRemoteMember) &&
AtomicRead(&g_lobbyMemberJoinedFired) == 0 &&
g_memberStatusCb;
bool requestPending = AtomicRead(&g_peerConnected) &&
AtomicRead(&g_connectionRequestFired) == 0 &&
g_connectionRequestCb;
if (!memberPending && !requestPending) return;
EOS_ProductUserId id = RemoteProductUserId();
if (memberPending) FireLobbyMemberJoined(id);
if (requestPending) FirePeerConnectionRequest(id);
}
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 param) {
LONG myGeneration = (LONG)(intptr_t)param;
InitNetwork();
Log("[Thread] PeerWatchThread started. isHost=%d generation=%ld",
(int)AtomicRead(&g_isHost), (long)myGeneration);
if (AtomicRead(&g_isHost)) {
int loopCount = 0;
while (loopCount < 400) {
if (WaitForSingleObject(g_stopEvent, 0) == WAIT_OBJECT_0) break;
if (AtomicRead(&g_watchThreadGeneration) != myGeneration) {
Log("[Thread] Host: stale generation, exiting");
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());
if (AtomicRead(&g_peerConnected) && AtomicRead(&g_lobbyHasRemoteMember)) {
Log("[Thread] Host bootstrap complete, exiting watch thread");
break;
}
} else if (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected)) {
TryFireHostEvents(ClientProductUserId());
if (AtomicRead(&g_peerConnected) && AtomicRead(&g_lobbyHasRemoteMember)) {
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;
if (AtomicRead(&g_watchThreadGeneration) != myGeneration) {
Log("[Thread] Client hello: stale generation, exiting");
goto thread_exit;
}
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;
if (AtomicRead(&g_watchThreadGeneration) != myGeneration) {
Log("[Thread] Client events: stale generation, exiting");
goto thread_exit;
}
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++;
}
}
thread_exit:
Log("[Thread] PeerWatchThread exiting (generation=%ld)", (long)myGeneration);
EnterCriticalSection(&g_socketCs);
if (AtomicRead(&g_watchThreadGeneration) == myGeneration) {
if (g_peerWatchThread) CloseHandle(g_peerWatchThread);
g_peerWatchThread = NULL;
}
LeaveCriticalSection(&g_socketCs);
return 0;
}
static bool ShouldSuppressAutoTeardown() {
if (!AtomicRead(&g_autoTeardownSuppressed))
return false;
DWORD now = GetTickCount();
DWORD elapsed = now - g_autoTeardownSuppressTime;
if (elapsed < kAutoTeardownSuppressWindowMs) {
Log("[Proxy] ShouldSuppressAutoTeardown: active (%lu ms elapsed), "
"deferring reset", (unsigned long)elapsed);
return true;
}
Log("[Proxy] ShouldSuppressAutoTeardown: window expired (%lu ms elapsed), "
"clearing flag", (unsigned long)elapsed);
AtomicWrite(&g_autoTeardownSuppressed, 0);
return false;
}
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);
if (InterlockedIncrement(&g_sendLogCount) <= 80 || sent == SOCKET_ERROR) {
LogCallStack("EOS_P2P_SendPacket");
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 {
g_lastGoodPacketTime = GetTickCount();
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) {
if (AtomicRead(&g_resetInProgress)) return EOS_NotFound;
InitNetwork();
if (!AtomicRead(&g_initialized)) return EOS_NotFound;
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;
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");
if (AtomicRead(&g_resetInProgress)) {
Log("[Proxy] << EOS_P2P_ReceivePacket exit (reset in progress, NotFound)");
return EOS_NotFound;
}
InitNetwork();
if (!AtomicRead(&g_initialized)) {
Log("[Proxy] << EOS_P2P_ReceivePacket exit (not initialized, NotFound)");
return EOS_NotFound;
}
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) {
g_lastGoodPacketTime = GetTickCount();
// 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);
if (InterlockedIncrement(&g_receiveLogCount) <= 80) {
LogCallStack("EOS_P2P_ReceivePacket");
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");
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");
LogCallStack("EOS_P2P_CloseConnection");
return EOS_Success;
}
__declspec(dllexport) EOS_EResult
EOS_P2P_CloseConnections(EOS_HP2P Handle, const void *Options) {
Log("[Proxy] EOS_P2P_CloseConnections called");
LogCallStack("EOS_P2P_CloseConnections");
// This is a bodge, this isnt a best practice at all, but it works
bool userInitiated = StackContainsModule("USER32.dll");
bool socketHealthy = (udpSocket != INVALID_SOCKET) &&
(GetTickCount() - g_lastGoodPacketTime) < 4000;
static LONG s_suppressedCount = 0;
if (!userInitiated && socketHealthy && s_suppressedCount < 20) {
LONG n = InterlockedIncrement(&s_suppressedCount);
// Set the shared suppression flag so LeaveLobby and DestroyLobby
// (which fire in the same tick) also skip their reset.
AtomicWrite(&g_autoTeardownSuppressed, 1);
g_autoTeardownSuppressTime = GetTickCount();
Log("[Proxy] EOS_P2P_CloseConnections SUPPRESSED (auto-teardown, socket healthy, "
"suppressed=%ld, msSinceLastGoodPacket=%lu, suppression window started)",
n, (unsigned long)(GetTickCount() - g_lastGoodPacketTime));
return EOS_Success; // tell the game it happened; don't actually reset anything
}
s_suppressedCount = 0;
AtomicWrite(&g_autoTeardownSuppressed, 0);
Log("[Proxy] EOS_P2P_CloseConnections executing real reset "
"(userInitiated=%d socketHealthy=%d msSinceLastGoodPacket=%lu)",
(int)userInitiated, (int)socketHealthy,
(unsigned long)(GetTickCount() - g_lastGoodPacketTime));
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");
if (OutPort) *OutPort = 7777;
if (OutNumAdditionalPortsToTry) *OutNumAdditionalPortsToTry = 0;
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;
Log("[Proxy] EOS_P2P_AddNotifyPeerConnectionRequest called (count=%d)", (int)InterlockedIncrement(&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)");
LogCallStack("EOS_P2P_ClearPacketQueue");
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");
if (CompletionDelegate) {
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 with Success");
CompletionDelegate(&info);
}
Log("[Proxy] << EOS_Auth_Login exit");
}
struct EOS_Connect_LoginStatusChangedCallbackInfo {
void* ClientData;
EOS_ProductUserId LocalUserId;
int32_t PreviousStatus; // EOS_ELoginStatus
int32_t CurrentStatus; // EOS_ELoginStatus
};
__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;
Log("[Proxy] EOS_Connect_Login called (call #%d)", (int)InterlockedIncrement(&g_connectLoginCount));
EOS_ProductUserId localUser = LocalProductUserId();
if (CompletionDelegate) {
EOS_Connect_LoginCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LocalUserId = localUser;
Log("[Proxy] Invoking EOS_Connect_Login callback localUser=%p", info.LocalUserId);
CompletionDelegate(&info);
}
if (g_loginStatusChangedCb) {
EOS_Connect_LoginStatusChangedCallbackInfo statusInfo = {0};
statusInfo.ClientData = g_loginStatusChangedCbData;
statusInfo.LocalUserId = localUser;
statusInfo.PreviousStatus = 0; // EOS_LS_NotLoggedIn
statusInfo.CurrentStatus = 2; // EOS_LS_LoggedIn
Log("[Proxy] Invoking LoginStatusChanged callback -> LoggedIn");
g_loginStatusChangedCb(&statusInfo);
}
}
__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");
if (CompletionDelegate) {
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 g_notifIdCounter++;
}
// EOS_ELoginStatus: 0=NotLoggedIn, 1=UsingLocalProfile, 2=LoggedIn
__declspec(dllexport) int32_t
EOS_Connect_GetLoginStatus(EOS_HConnect Handle, EOS_ProductUserId LocalUserId) {
Log("[Proxy] EOS_Connect_GetLoginStatus called -> LoggedIn (2)");
return 2; // EOS_LS_LoggedIn
}
__declspec(dllexport) uint64_t
EOS_Connect_AddNotifyLoginStatusChanged(EOS_HConnect Handle, const void *Options,
void *ClientData,
EOS_Connect_OnLoginStatusChangedCallback NotificationFn) {
Log("[Proxy] EOS_Connect_AddNotifyLoginStatusChanged called cb=%p", (void*)NotificationFn);
g_loginStatusChangedCb = NotificationFn;
g_loginStatusChangedCbData = ClientData;
return g_notifIdCounter++;
}
__declspec(dllexport) void
EOS_Connect_RemoveNotifyLoginStatusChanged(EOS_HConnect Handle, uint64_t InId) {
Log("[Proxy] EOS_Connect_RemoveNotifyLoginStatusChanged id=%llu", (unsigned long long)InId);
g_loginStatusChangedCb = nullptr;
g_loginStatusChangedCbData = nullptr;
}
__declspec(dllexport) int32_t
EOS_Auth_GetLoginStatus(EOS_HAuth Handle, EOS_ProductUserId LocalUserId) {
Log("[Proxy] EOS_Auth_GetLoginStatus called -> LoggedIn (2)");
return 2;
}
__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");
if (CompletionDelegate) {
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_Lobby_JoinLobbyByIdOptions {
int32_t ApiVersion;
const char* LobbyId;
EOS_ProductUserId LocalUserId;
};
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");
LogCallStack("EOS_Lobby_CreateLobby");
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);
}
EnterCriticalSection(&g_socketCs);
if (!g_peerWatchThread) {
ResetEvent(g_stopEvent);
LONG gen = InterlockedIncrement(&g_watchThreadGeneration);
g_peerWatchThread = CreateThread(NULL, 0, PeerWatchThread, (LPVOID)(intptr_t)gen, 0, NULL);
Log("[Proxy] Started PeerWatchThread from CreateLobby (generation=%ld)", (long)gen);
} else {
Log("[Proxy] CreateLobby: PeerWatchThread already running, not spawning a second one");
}
LeaveCriticalSection(&g_socketCs);
if (CompletionDelegate) {
EOS_Lobby_CreateLobbyCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LobbyId = "DirectIP_Lobby";
Log("[Proxy] Invoking CreateLobby callback");
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");
LogCallStack("EOS_Lobby_JoinLobby");
InitNetwork();
if (!AtomicRead(&g_isHost)) AtomicWrite(&g_lobbyHasRemoteMember, 1);
EnterCriticalSection(&g_socketCs);
if (!g_peerWatchThread) {
ResetEvent(g_stopEvent);
LONG gen = InterlockedIncrement(&g_watchThreadGeneration);
g_peerWatchThread = CreateThread(NULL, 0, PeerWatchThread, (LPVOID)(intptr_t)gen, 0, NULL);
Log("[Proxy] Started PeerWatchThread from JoinLobby (generation=%ld)", (long)gen);
} else {
Log("[Proxy] JoinLobby: PeerWatchThread already running, not spawning a second one");
}
LeaveCriticalSection(&g_socketCs);
if (CompletionDelegate) {
EOS_Lobby_JoinLobbyCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LobbyId = "DirectIP_Lobby";
Log("[Proxy] Invoking JoinLobby callback");
CompletionDelegate(&info);
}
LogStateSnapshot("JoinLobby");
Log("[Proxy] << EOS_Lobby_JoinLobby exit");
}
__declspec(dllexport) void
EOS_Lobby_JoinLobbyById(EOS_HLobby Handle, const EOS_Lobby_JoinLobbyByIdOptions* Options,
void* ClientData, EOS_Lobby_OnJoinLobbyCallback CompletionDelegate) {
Log("[Proxy] >> EOS_Lobby_JoinLobbyById called id=%s", Options ? Options->LobbyId : "NULL");
LogCallStack("EOS_Lobby_JoinLobbyById");
InitNetwork();
if (!AtomicRead(&g_isHost)) AtomicWrite(&g_lobbyHasRemoteMember, 1);
EnterCriticalSection(&g_socketCs);
if (!g_peerWatchThread) {
ResetEvent(g_stopEvent);
LONG gen = InterlockedIncrement(&g_watchThreadGeneration);
g_peerWatchThread = CreateThread(NULL, 0, PeerWatchThread, (LPVOID)(intptr_t)gen, 0, NULL);
Log("[Proxy] Started PeerWatchThread from JoinLobbyById (generation=%ld)", (long)gen);
} else {
Log("[Proxy] JoinLobbyById: PeerWatchThread already running, not spawning a second one");
}
LeaveCriticalSection(&g_socketCs);
if (CompletionDelegate) {
EOS_Lobby_JoinLobbyCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
info.LobbyId = "DirectIP_Lobby";
Log("[Proxy] Invoking JoinLobbyById callback");
CompletionDelegate(&info);
}
LogStateSnapshot("JoinLobbyById");
Log("[Proxy] << EOS_Lobby_JoinLobbyById 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) {
EOS_LobbySearch_FindCallbackInfo info = {0};
info.ResultCode = EOS_Success;
info.ClientData = ClientData;
Log("[Proxy] Invoking LobbySearch_Find callback");
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) {
uint32_t currentMembers = (AtomicRead(&g_lobbyHasRemoteMember) || AtomicRead(&g_peerConnected)) ? 2 : 1;
dummyLobbyInfo.AvailableSlots = g_maxMembers - currentMembers;
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) {
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;
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();
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;
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) {
EOS_Lobby_UpdateLobbyCallbackInfo info = {EOS_Success, ClientData, "DirectIP_Lobby"};
CompletionDelegate(&info);
}
if (g_lobbyUpdateCb) {
EOS_Lobby_LobbyUpdateReceivedCallbackInfo notif = {0};
notif.ClientData = g_lobbyUpdateCbData;
notif.LobbyId = "DirectIP_Lobby";
g_lobbyUpdateCb(&notif);
}
if (g_memberUpdateCb) {
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");
LogCallStack("EOS_Lobby_DestroyLobby");
if (!ShouldSuppressAutoTeardown()) {
ResetConnectionState();
}
if (CompletionDelegate) {
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");
LogCallStack("EOS_Lobby_LeaveLobby");
if (!ShouldSuppressAutoTeardown()) {
ResetConnectionState();
}
if (CompletionDelegate) {
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;
}
typedef void* EOS_HPlayerDataStorage;
struct EOS_PlayerDataStorage_QueryFileOptions {
int32_t ApiVersion;
EOS_ProductUserId LocalUserId;
const char* Filename;
};
struct EOS_PlayerDataStorage_QueryFileCallbackInfo {
EOS_EResult ResultCode;
void* ClientData;
EOS_ProductUserId LocalUserId;
const char* Filename;
};
typedef void (*EOS_PlayerDataStorage_OnQueryFileCompleteCallback)(const EOS_PlayerDataStorage_QueryFileCallbackInfo* Data);
__declspec(dllexport) void EOS_PlayerDataStorage_QueryFile(
EOS_HPlayerDataStorage Handle, const EOS_PlayerDataStorage_QueryFileOptions* Options,
void* ClientData, const EOS_PlayerDataStorage_OnQueryFileCompleteCallback CompletionCallback) {
const char* fname = (Options && Options->Filename) ? Options->Filename : "(null)";
Log("[Proxy] EOS_PlayerDataStorage_QueryFile called file=%s", fname);
if (CompletionCallback) {
EOS_PlayerDataStorage_QueryFileCallbackInfo info = {0};
info.ResultCode = EOS_NotFound; // no real cloud backend -- behave like an empty save slot
info.ClientData = ClientData;
info.LocalUserId = Options ? Options->LocalUserId : LocalProductUserId();
info.Filename = fname;
CompletionCallback(&info);
}
}
typedef void* EOS_HSanctions;
struct EOS_Sanctions_QueryActivePlayerSanctionsOptions {
int32_t ApiVersion;
EOS_ProductUserId LocalUserId;
EOS_ProductUserId TargetUserId;
};
struct EOS_Sanctions_QueryActivePlayerSanctionsCallbackInfo {
EOS_EResult ResultCode;
void* ClientData;
EOS_ProductUserId LocalUserId;
EOS_ProductUserId TargetUserId;
};
typedef void (*EOS_Sanctions_OnQueryActivePlayerSanctionsCallback)(const EOS_Sanctions_QueryActivePlayerSanctionsCallbackInfo* Data);
__declspec(dllexport) void EOS_Sanctions_QueryActivePlayerSanctions(
EOS_HSanctions Handle, const EOS_Sanctions_QueryActivePlayerSanctionsOptions* Options,
void* ClientData, const EOS_Sanctions_OnQueryActivePlayerSanctionsCallback CompletionDelegate) {
Log("[Proxy] EOS_Sanctions_QueryActivePlayerSanctions called (answered locally, not forwarded to real SDK)");
if (CompletionDelegate) {
EOS_Sanctions_QueryActivePlayerSanctionsCallbackInfo info = {0};
info.ResultCode = EOS_Success; // no sanctions to report
info.ClientData = ClientData;
info.LocalUserId = Options ? Options->LocalUserId : LocalProductUserId();
info.TargetUserId = Options ? Options->TargetUserId : LocalProductUserId();
CompletionDelegate(&info);
}
}
struct EOS_Sanctions_GetPlayerSanctionCountOptions {
int32_t ApiVersion;
EOS_ProductUserId TargetUserId;
};
__declspec(dllexport) uint32_t EOS_Sanctions_GetPlayerSanctionCount(
EOS_HSanctions Handle, const EOS_Sanctions_GetPlayerSanctionCountOptions* Options) {
Log("[Proxy] EOS_Sanctions_GetPlayerSanctionCount called -> 0");
return 0;
}
struct EOS_Sanctions_CopyPlayerSanctionByIndexOptions {
int32_t ApiVersion;
EOS_ProductUserId TargetUserId;
uint32_t SanctionIndex;
};
__declspec(dllexport) EOS_EResult EOS_Sanctions_CopyPlayerSanctionByIndex(
EOS_HSanctions Handle, const EOS_Sanctions_CopyPlayerSanctionByIndexOptions* Options, void** OutSanction) {
Log("[Proxy] EOS_Sanctions_CopyPlayerSanctionByIndex called -> NotFound");
if (OutSanction) *OutSanction = nullptr;
return EOS_NotFound;
}
struct EOS_LogMessage {
const char* Category;
const char* Message;
int32_t Level; // EOS_ELogLevel
};
typedef void (*EOS_LogMessageFunc)(const EOS_LogMessage* Message);
// EOS_ELogLevel
#define EOS_LOG_Off 0
#define EOS_LOG_Fatal 1
#define EOS_LOG_Error 2
#define EOS_LOG_Warning 3
#define EOS_LOG_Info 4
#define EOS_LOG_Verbose 5
#define EOS_LOG_VeryVerbose 6
// EOS_ELogCategory
#define EOS_LC_ALL_CATEGORIES 0x7FFFFFFF
typedef EOS_EResult (*EOS_Logging_SetCallback_Fn)(EOS_LogMessageFunc Callback);
typedef EOS_EResult (*EOS_Logging_SetLogLevel_Fn)(int32_t LogCategory, int32_t LogLevel);
static EOS_Logging_SetCallback_Fn Original_EOS_Logging_SetCallback = nullptr;
static EOS_Logging_SetLogLevel_Fn Original_EOS_Logging_SetLogLevel = nullptr;
static EOS_LogMessageFunc g_gameLogCallback = nullptr;
static LONG g_sdkLogCount = 0;
static const char* LogLevelToStr(int32_t level) {
switch (level) {
case EOS_LOG_Fatal: return "FATAL";
case EOS_LOG_Error: return "ERROR";
case EOS_LOG_Warning: return "WARN";
case EOS_LOG_Info: return "INFO";
case EOS_LOG_Verbose: return "VERBOSE";
case EOS_LOG_VeryVerbose: return "VVERBOSE";
default: return "?";
}
}
void ProxySdkLogCallback(const EOS_LogMessage* Message) {
if (Message) {
InterlockedIncrement(&g_sdkLogCount);
bool isTelemetry = Message->Message &&
(strstr(Message->Message, "api.epicgames.dev") ||
strstr(Message->Message, "0.0.0.0") ||
strstr(Message->Message, "Couldn't connect to server") ||
strstr(Message->Message, "Connection refused") ||
strstr(Message->Message, "Failed to connect to the backend") ||
strstr(Message->Message, "EOS_NoConnection"));
if (isTelemetry) {
if (telemetryFile) {
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(telemetryFile, "[%02u:%02u:%02u.%03u] [SDK/%s] %s: %s\n",
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
Message->Category ? Message->Category : "?",
LogLevelToStr(Message->Level),
Message->Message ? Message->Message : "");
fflush(telemetryFile);
}
} else {
Log("[SDK/%s] %s: %s",
Message->Category ? Message->Category : "?",
LogLevelToStr(Message->Level),
Message->Message ? Message->Message : "");
}
}
if (g_gameLogCallback) g_gameLogCallback(Message);
}
__declspec(dllexport) EOS_EResult EOS_Logging_SetCallback(EOS_LogMessageFunc Callback) {
Log("[Proxy] EOS_Logging_SetCallback called by game, cb=%p (chaining through proxy sink)", (void*)Callback);
g_gameLogCallback = Callback;
if (Original_EOS_Logging_SetCallback) {
return Original_EOS_Logging_SetCallback(ProxySdkLogCallback);
}
return EOS_Success;
}
__declspec(dllexport) EOS_EResult EOS_Logging_SetLogLevel(int32_t LogCategory, int32_t LogLevel) {
Log("[Proxy] EOS_Logging_SetLogLevel called by game: category=%d level=%d (forwarding as-is)",
LogCategory, LogLevel);
if (Original_EOS_Logging_SetLogLevel) {
return Original_EOS_Logging_SetLogLevel(LogCategory, LogLevel);
}
return EOS_Success;
}
// EOS_Platform_Tick
typedef void (*EOS_Platform_Tick_Fn)(void* Handle);
static EOS_Platform_Tick_Fn Original_EOS_Platform_Tick = nullptr;
static LONG g_tickCount = 0;
__declspec(dllexport) void EOS_Platform_Tick(void* Handle) {
if (Original_EOS_Platform_Tick) {
Original_EOS_Platform_Tick(Handle);
}
LONG tick = InterlockedIncrement(&g_tickCount);
size_t pending = 0;
{
EnterCriticalSection(&g_callbackQueueCs);
pending = g_callbackQueue.size();
LeaveCriticalSection(&g_callbackQueueCs);
}
if (pending > 0) {
Log("[Tick] #%ld: %zu callback(s) pending, draining now", (long)tick, pending);
}
if (tick % 300 == 1) {
DWORD now = GetTickCount();
DWORD msSinceGoodPkt = (g_lastGoodPacketTime && g_lastGoodPacketTime != -1)
? (now - g_lastGoodPacketTime) : (DWORD)-1;
Log("[Tick] heartbeat #%ld (isHost=%d initialized=%d peerConnected=%d "
"lobbyHasRemoteMember=%d supp=%lu msSinceGoodPkt=%lu socket=%s)",
(long)tick, (int)AtomicRead(&g_isHost), (int)AtomicRead(&g_initialized),
(int)AtomicRead(&g_peerConnected), (int)AtomicRead(&g_lobbyHasRemoteMember),
(unsigned long)AtomicRead(&g_autoTeardownSuppressed),
(unsigned long)msSinceGoodPkt,
(udpSocket != INVALID_SOCKET) ? "open" : "closed");
}
DrainCallbackQueue();
TickRetryPendingEvents();
}
} // 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];
char telPath[MAX_PATH];
strcpy(logPath, path); strcat(logPath, "proxy_p2p.log");
strcpy(pktPath, path); strcat(pktPath, "proxy_packets.log");
strcpy(telPath, path); strcat(telPath, "telemetry_logs.log");
logFile = fopen(logPath, "a");
packetFile = fopen(pktPath, "a");
telemetryFile = fopen(telPath, "a");
if (logFile) {
setvbuf(logFile, NULL, _IOFBF, 1 << 16);
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); // flush this one banner line immediately so attach is visible even if it crashes right after
}
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);
}
if (telemetryFile) {
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(telemetryFile, "\n========== Telemetry Log %02u:%02u:%02u ==========\n",
st.wHour, st.wMinute, st.wSecond);
fflush(telemetryFile);
}
}
void CloseLogFiles() {
if (logFile) { fclose(logFile); logFile = NULL; }
if (packetFile) { fclose(packetFile); packetFile = NULL; }
if (telemetryFile) { fclose(telemetryFile); telemetryFile = NULL; }
DeleteCriticalSection(&g_packetCs);
}
// === WinSock Telemetry Blocking Hooks ===
typedef int (WSAAPI *GetAddrInfoFn)(PCSTR, PCSTR, const ADDRINFOA *, PADDRINFOA *);
typedef int (WSAAPI *ConnectFn)(SOCKET, const struct sockaddr *, int);
static GetAddrInfoFn Real_GetAddrInfo = NULL;
static ConnectFn Real_Connect = NULL;
static bool PatchIat(HMODULE module, const char *sourceDll, const char *funcName,
WORD ordinal, void *hook, void **orig) {
if (!module) return false;
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)module;
if (dos->e_magic != 0x5A4D) return false;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((char*)module + dos->e_lfanew);
if (nt->Signature != 0x00004550) return false;
PIMAGE_DATA_DIRECTORY importDir = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (!importDir->VirtualAddress || !importDir->Size) return false;
static const uintptr_t kOrdFlag = 0x8000000000000000ULL;
PIMAGE_IMPORT_DESCRIPTOR imports = (PIMAGE_IMPORT_DESCRIPTOR)((char*)module + importDir->VirtualAddress);
for (; imports->Name; imports++) {
char *dllName = (char*)module + imports->Name;
if (_stricmp(dllName, sourceDll) != 0) continue;
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((char*)module + imports->FirstThunk);
PIMAGE_THUNK_DATA origThunk = (PIMAGE_THUNK_DATA)((char*)module + imports->OriginalFirstThunk);
for (; thunk->u1.Function; thunk++, origThunk++) {
bool match = false;
if (funcName) {
if (origThunk->u1.Ordinal & kOrdFlag) continue;
PIMAGE_IMPORT_BY_NAME imp = (PIMAGE_IMPORT_BY_NAME)((char*)module + origThunk->u1.AddressOfData);
if (strcmp(imp->Name, funcName) == 0) match = true;
} else {
if (!(origThunk->u1.Ordinal & kOrdFlag)) continue;
if ((origThunk->u1.Ordinal & 0xFFFF) == ordinal) match = true;
}
if (!match) continue;
if (orig) *orig = (void*)(uintptr_t)thunk->u1.Function;
DWORD old;
if (VirtualProtect(&thunk->u1.Function, sizeof(void*), PAGE_READWRITE, &old)) {
thunk->u1.Function = (uintptr_t)(LONG_PTR)hook;
VirtualProtect(&thunk->u1.Function, sizeof(void*), old, &old);
return true;
}
return false;
}
}
return false;
}
static int my_tolower(int c) {
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
}
static bool Listed(const char *value, const char *list) {
if (!value || !list || !list[0]) return false;
const char *p = list;
size_t vlen = strlen(value);
while (*p) {
while (*p == ' ' || *p == ',' || *p == ';') p++;
if (!*p) break;
const char *start = p;
while (*p && *p != ' ' && *p != ',' && *p != ';') p++;
size_t len = p - start;
if (len != vlen) continue;
bool match = true;
for (size_t i = 0; i < len; i++) {
if (my_tolower((unsigned char)value[i]) != my_tolower((unsigned char)start[i])) {
match = false;
break;
}
}
if (match) return true;
}
return false;
}
static int WSAAPI Hook_GetAddrInfo(PCSTR name, PCSTR svc, const ADDRINFOA *hints, PADDRINFOA *res) {
int ret = Real_GetAddrInfo(name, svc, hints, res);
if (ret != 0) return ret;
if (name && Listed(name, g_blockedDomains)) {
if (g_telemetryBlock) {
LogTelemetry("[Telemetry] Redirected DNS: %s -> 0.0.0.0 (block via connect)", name);
ADDRINFOA *cur = *res;
while (cur) {
if (cur->ai_family == AF_INET && cur->ai_addr) {
((struct sockaddr_in *)cur->ai_addr)->sin_addr.s_addr = htonl(INADDR_ANY);
}
cur = cur->ai_next;
}
} else if (g_dumpTraffic) {
LogTelemetry("[Telemetry] Traffic: DNS lookup %s (ALLOWED, dump mode)", name);
}
}
return ret;
}
static int WSAAPI Hook_Connect(SOCKET s, const struct sockaddr *addr, int addrlen) {
if (addr && addr->sa_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
char ip[16];
strcpy(ip, inet_ntoa(sin->sin_addr));
bool isBlocked = g_telemetryBlock && Listed(ip, g_blockedIPs);
if (isBlocked) {
LogTelemetry("[Telemetry] Blocked connect to %s:%d", ip, ntohs(sin->sin_port));
WSASetLastError(WSAECONNREFUSED);
return SOCKET_ERROR;
}
if (g_dumpTraffic && Listed(ip, g_blockedIPs)) {
LogTelemetry("[Telemetry] Traffic: connect to %s:%d (ALLOWED, dump mode)", ip, ntohs(sin->sin_port));
}
}
return Real_Connect(s, addr, addrlen);
}
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();
// Load telemetry config
{
char iniPath2[MAX_PATH];
snprintf(iniPath2, sizeof(iniPath2), "%sdirectip.ini", g_dllDir);
char telemetryStr[8] = "true";
GetPrivateProfileStringA("Network", "Telemetry", "true", telemetryStr, sizeof(telemetryStr), iniPath2);
g_telemetryBlock = (_stricmp(telemetryStr, "false") == 0 || strcmp(telemetryStr, "0") == 0);
char dumpStr[8] = "false";
GetPrivateProfileStringA("Telemetry", "DumpTraffic", "false", dumpStr, sizeof(dumpStr), iniPath2);
g_dumpTraffic = (_stricmp(dumpStr, "true") == 0 || strcmp(dumpStr, "1") == 0);
GetPrivateProfileStringA("Telemetry", "BlockDomains", "api.epicgames.dev", g_blockedDomains, sizeof(g_blockedDomains), iniPath2);
GetPrivateProfileStringA("Telemetry", "BlockIPs", "104.18.125.108", g_blockedIPs, sizeof(g_blockedIPs), iniPath2);
// Do NOT add 0.0.0.0 to the block list — let connect fail naturally
// so the EOS SDK's HttpManagerThread doesn't crash on synthetic errors.
LogTelemetry("[Telemetry] Config: block=%d dump=%d domains=%s ips=%s",
(int)g_telemetryBlock, (int)g_dumpTraffic, g_blockedDomains, g_blockedIPs);
}
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");
Original_EOS_Logging_SetCallback = (EOS_Logging_SetCallback_Fn)GetProcAddress(hOriginalDll, "EOS_Logging_SetCallback");
Original_EOS_Logging_SetLogLevel = (EOS_Logging_SetLogLevel_Fn)GetProcAddress(hOriginalDll, "EOS_Logging_SetLogLevel");
if (Original_EOS_Logging_SetCallback) {
EOS_EResult r = Original_EOS_Logging_SetCallback(ProxySdkLogCallback);
Log("[Proxy] Registered proxy SDK log sink, result=%d", r);
} else {
Log("[Proxy] !! EOS_Logging_SetCallback not found in original DLL -- no internal SDK logs available");
}
if (Original_EOS_Logging_SetLogLevel) {
EOS_EResult r = Original_EOS_Logging_SetLogLevel(EOS_LC_ALL_CATEGORIES, EOS_LOG_VeryVerbose);
Log("[Proxy] Forced SDK log level to VeryVerbose (all categories), result=%d", r);
} else {
Log("[Proxy] !! EOS_Logging_SetLogLevel not found in original DLL -- log verbosity left at default");
}
Log("[Proxy] Original DLL loaded successfully.");
} else {
Log("[Proxy] Failed to load original DLL! Error: %d", GetLastError());
}
// Patch EOS SDK IAT for ws2_32 telemetry hooks
bool okGa = PatchIat(hOriginalDll, "ws2_32.dll", "getaddrinfo", 0,
(void*)Hook_GetAddrInfo, (void**)&Real_GetAddrInfo);
bool okCo = PatchIat(hOriginalDll, "ws2_32.dll", NULL, 4,
(void*)Hook_Connect, (void**)&Real_Connect);
LogTelemetry("[Telemetry] IAT hooks: getaddrinfo=%s(%p) connect=%s(%p)",
okGa ? "OK" : "FAIL", (void*)Real_GetAddrInfo,
okCo ? "OK" : "FAIL", (void*)Real_Connect);
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;
}