mirror of
https://codeberg.org/Nixietab/EOSSDK-Holos.git
synced 2026-08-21 20:23:28 -04:00
Compare commits
2 commits
98af2b4034
...
7b7cf916ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b7cf916ba | ||
|
|
47f8eb112e |
3 changed files with 338 additions and 5 deletions
|
|
@ -120,7 +120,6 @@ def main():
|
|||
"EOS_Sanctions_QueryActivePlayerSanctions",
|
||||
"EOS_Sanctions_GetPlayerSanctionCount",
|
||||
"EOS_Sanctions_CopyPlayerSanctionByIndex",
|
||||
|
||||
]
|
||||
|
||||
funcs = []
|
||||
|
|
|
|||
132
keyDumper/encrypt_bundles.py
Normal file
132
keyDumper/encrypt_bundles.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os, sys, struct, argparse
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
KEY_DEFAULT = b"rK7CcATuZk7LAmhqqU4iBLNmAq8QbK3s"
|
||||
KEY_ALTERNATE = b"x4DZmD6D2HhkzT6qD8HpKeZdgM9HCmXP"
|
||||
|
||||
|
||||
def xor_crypt(data, key):
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
out = bytearray(len(data))
|
||||
for off in range(0, len(data), 16):
|
||||
ctr = off // 16 + 1
|
||||
ks = cipher.encrypt(struct.pack('<q', ctr) + b'\x00' * 8)
|
||||
for j, b in enumerate(data[off:off+16]):
|
||||
out[off+j] = b ^ ks[j]
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def process_file(inpath, outpath, key, dry_run=False):
|
||||
try:
|
||||
with open(inpath, 'rb') as f:
|
||||
data = f.read()
|
||||
if data[:7] != b'UnityFS':
|
||||
print(f" Warning: '{os.path.basename(inpath)}' does not start with UnityFS magic")
|
||||
if dry_run:
|
||||
print(f" Would encrypt: {os.path.basename(inpath)} -> {os.path.basename(outpath)}")
|
||||
return True
|
||||
encrypted = xor_crypt(data, key)
|
||||
os.makedirs(os.path.dirname(outpath) or '.', exist_ok=True)
|
||||
with open(outpath, 'wb') as f:
|
||||
f.write(encrypted)
|
||||
print(f" {os.path.basename(inpath)} -> {os.path.basename(outpath)} ({len(data)} bytes)")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ERROR {os.path.basename(inpath)}: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Re-encrypt decrypted Unity bundles for Holo's Hanafuda")
|
||||
parser.add_argument('input', help='Input .unity3d file or directory of .unity3d files')
|
||||
parser.add_argument('-o', '--output', help='Output file or directory')
|
||||
parser.add_argument('--key-alternate', action='store_true',
|
||||
help='Use alternate bundle key candidate (x4DZmD6D2HhkzT6qD8HpKeZdgM9HCmXP)')
|
||||
parser.add_argument('--key-hex', metavar='HEX',
|
||||
help='64-char hex AES-256 key (overrides defaults)')
|
||||
parser.add_argument('--in-place', action='store_true',
|
||||
help='Write output next to the original .bundle in StandaloneWindows64/')
|
||||
parser.add_argument('--dry-run', action='store_true',
|
||||
help='List what would be encrypted without writing')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.key_hex:
|
||||
key = bytes.fromhex(args.key_hex)
|
||||
if len(key) != 32:
|
||||
print("Error: --key-hex must be exactly 64 hex chars (32 bytes)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif args.key_alternate:
|
||||
key = KEY_ALTERNATE
|
||||
else:
|
||||
key = KEY_DEFAULT
|
||||
|
||||
inpath = args.input
|
||||
|
||||
if os.path.isdir(inpath):
|
||||
files = sorted(f for f in os.listdir(inpath) if f.endswith('.unity3d'))
|
||||
if not files:
|
||||
print(f"No .unity3d files found in '{inpath}'")
|
||||
return
|
||||
|
||||
if args.output:
|
||||
outdir = args.output
|
||||
elif args.in_place:
|
||||
outdir = os.path.normpath(os.path.join(inpath, '..', '..', 'StandaloneWindows64'))
|
||||
else:
|
||||
outdir = os.path.normpath(os.path.join(inpath, '..', 'reencrypted'))
|
||||
|
||||
if not args.dry_run:
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
|
||||
ok = errs = 0
|
||||
for fname in files:
|
||||
fpath = os.path.join(inpath, fname)
|
||||
base = fname.removesuffix('.unity3d')
|
||||
outpath = os.path.join(outdir, base + '.bundle')
|
||||
if process_file(fpath, outpath, key, args.dry_run):
|
||||
ok += 1
|
||||
else:
|
||||
errs += 1
|
||||
|
||||
total = len(files)
|
||||
print(f"\n{ok} OK, {errs} errors of {total}")
|
||||
if not args.dry_run:
|
||||
print(f"Output directory: {outdir}/")
|
||||
|
||||
else:
|
||||
if not os.path.exists(inpath):
|
||||
print(f"Error: '{inpath}' not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if args.output:
|
||||
outpath = args.output
|
||||
elif args.in_place:
|
||||
base = os.path.splitext(os.path.basename(inpath))[0]
|
||||
bundle_name = base + '.bundle'
|
||||
candidates = [
|
||||
os.path.join(os.path.dirname(inpath), bundle_name),
|
||||
os.path.normpath(os.path.join(
|
||||
os.path.dirname(inpath), '..', '..', 'StandaloneWindows64', bundle_name)),
|
||||
]
|
||||
outpath = None
|
||||
for c in candidates:
|
||||
if os.path.exists(c):
|
||||
outpath = c
|
||||
break
|
||||
if not outpath:
|
||||
print(f"Error: --in-place but no existing .bundle found for '{inpath}'",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
basedir = os.path.dirname(inpath) or '.'
|
||||
basename = os.path.splitext(os.path.basename(inpath))[0] + '.bundle'
|
||||
outpath = os.path.join(basedir, basename)
|
||||
|
||||
process_file(inpath, outpath, key, args.dry_run)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
210
main.cpp
210
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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue