diff --git a/gen_stubs.py b/gen_stubs.py index 5f4fa84..9c86197 100644 --- a/gen_stubs.py +++ b/gen_stubs.py @@ -120,7 +120,6 @@ def main(): "EOS_Sanctions_QueryActivePlayerSanctions", "EOS_Sanctions_GetPlayerSanctionCount", "EOS_Sanctions_CopyPlayerSanctionByIndex", - ] funcs = [] diff --git a/main.cpp b/main.cpp index 2674305..e15d6c9 100644 --- a/main.cpp +++ b/main.cpp @@ -48,6 +48,7 @@ static void DrainCallbackQueue() { // logging system FILE *logFile = NULL; FILE *packetFile = NULL; +FILE *telemetryFile = NULL; static CRITICAL_SECTION g_packetCs; void InitLogFiles(); @@ -65,6 +66,27 @@ void Log(const char *format, ...) { 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]); @@ -242,6 +264,12 @@ 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), @@ -2369,10 +2397,31 @@ void ProxySdkLogCallback(const EOS_LogMessage* Message) { if (Message) { InterlockedIncrement(&g_sdkLogCount); - Log("[SDK/%s] %s: %s", - Message->Category ? Message->Category : "?", - LogLevelToStr(Message->Level), - Message->Message ? Message->Message : ""); + 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); } @@ -2448,10 +2497,13 @@ void InitLogFiles() { 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); @@ -2468,14 +2520,135 @@ void InitLogFiles() { 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: { @@ -2492,6 +2665,25 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv 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) { @@ -2525,6 +2717,16 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv } 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: