From 8c33c7ae99790a1c0dc311d9a2c48eb5b9b5f7b5 Mon Sep 17 00:00:00 2001 From: xingcheng1423-cmyk <257154580+xingcheng1423-cmyk@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:40:33 +0800 Subject: [PATCH 1/2] fix(DenuvoAuth): always scan the main executable, not just modules >=80MB ProtectionScan applied kMinPackedModuleBytes (80MB) to every module including the game .exe, so Denuvo titles whose main executable is under 80MB (e.g. Sniper Elite 4 / appid 312660, 71MB) were skipped entirely and Denuvo was never detected -> auth never engaged. Exempt the executable from the size floor; keep it for DLLs. See #120. --- src/Pipe/Features/DenuvoAuth/ProtectionScan.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Pipe/Features/DenuvoAuth/ProtectionScan.cpp b/src/Pipe/Features/DenuvoAuth/ProtectionScan.cpp index bb46a6e..9c80d09 100644 --- a/src/Pipe/Features/DenuvoAuth/ProtectionScan.cpp +++ b/src/Pipe/Features/DenuvoAuth/ProtectionScan.cpp @@ -252,7 +252,12 @@ namespace { const bool executable = EndsWithInsensitive(module.path, ".exe"); const bool dll = EndsWithInsensitive(module.path, ".dll"); if (!executable && !dll) continue; - if (module.size < kMinPackedModuleBytes) { + // [PATCH #117] Always scan the main game executable regardless of size. + // Denuvo packs the .exe itself, and some titles (e.g. Sniper Elite 4 + // appid 312660, 71MB) fall under the 80MB floor and were silently + // skipped -> denuvo never detected -> auth never engaged -> 8850000A. + // Keep the size floor only for DLLs (the original perf optimization). + if (!executable && module.size < kMinPackedModuleBytes) { LOG_PIPE_TRACE("DenuvoAuth: module skipped below packed size floor path={} size={} ({:.2f} MB) min={} ({:.2f} MB)", module.path, module.size, From 2bd8a1efab606060cd4d4b6ca98957d22a98c983 Mon Sep 17 00:00:00 2001 From: xingcheng1423-cmyk <257154580+xingcheng1423-cmyk@users.noreply.github.com> Date: Mon, 15 Jun 2026 20:04:39 +0800 Subject: [PATCH 2/2] feat(DenuvoAuth): fall back to injected eticket when signature scan misses When ScanProtection does not recognize a Denuvo build (some titles slip both the OEP DODENUVO pattern and the legacy DENUVO-string check), still engage the auth path if an EncryptedAppTicket was injected for the app. Per the README, setAppTicket/setETicket are only used for ticket-gated (Denuvo) games, so an injected eticket is a reliable 'user intends Denuvo auth' signal. See #120. --- src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp b/src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp index cabeab9..a7bc8b4 100644 --- a/src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp +++ b/src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp @@ -151,7 +151,7 @@ namespace { return authIt == g_processAuth.end() ? nullptr : &authIt->second; } - void EnsureScanned(ProcessAuth& auth, const ProcessKey& process) { + void EnsureScanned(ProcessAuth& auth, const ProcessKey& process, AppId_t appId) { if (auth.scanned) { LOG_PIPE_TRACE("DenuvoAuth: reusing cached protection result {} denuvo={}", process.DebugString(), auth.denuvo); @@ -160,6 +160,16 @@ namespace { auth.scanned = true; auth.denuvo = ScanProtection(process.pid).denuvoDetected; + // Fallback when signature detection misses a real Denuvo title (some builds + // slip both the OEP and legacy-string heuristics — see linked issue). Per the + // README, setAppTicket/setETicket are only used for ticket-gated (Denuvo) + // games, so an injected EncryptedAppTicket for this app is a strong "the user + // intends Denuvo auth" signal; engage the auth path instead of giving up. + if (!auth.denuvo && + !AppTicket::GetEncryptedTicketFromCredentialStore(appId).empty()) { + LOG_PIPE_INFO("DenuvoAuth: scan missed but injected eticket present; treating as Denuvo appid={}", appId); + auth.denuvo = true; + } if (!auth.denuvo) auth.stage = Stage::None; } @@ -174,7 +184,7 @@ void Apply(const PipeContext& ctx) { ProcessAuth& auth = g_processAuth[ctx.process]; g_pipeProcess[pipeKey] = ctx.process; - EnsureScanned(auth, ctx.process); + EnsureScanned(auth, ctx.process, ctx.appId); auth.OnHandshake(ctx, pipeKey); }