Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}

Expand All @@ -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);
}

Expand Down
7 changes: 6 additions & 1 deletion src/Pipe/Features/DenuvoAuth/ProtectionScan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down