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