using System.Collections.Generic;
using VanadiumGuard.Protocol;
namespace VanadiumGuard.Server.Policy
{
///
/// What each detection is worth, and whether it may ever act on its own.
///
/// This table is the whole reason the redesign exists. In the old build every signal was
/// worth the same thing - immediate death - so a Cheat Engine window and someone having
/// a file named injector.exe in Downloads had identical consequences. Here a signal has
/// a weight, and only a handful of signals are trusted enough to end a session by
/// themselves.
///
///
public static class DetectionCatalog
{
public sealed class Entry
{
/// Score contribution at 100 confidence. Scores accumulate toward 100.
public double Weight { get; init; }
///
/// Whether this code alone, at high confidence, justifies removing the player.
/// Reserved for direct evidence of code modification inside the game process.
///
public bool Decisive { get; init; }
/// Human-readable, used in review tooling and audit logs.
public string Description { get; init; } = string.Empty;
}
private static readonly Dictionary Entries = new()
{
// Modules. Loading a known cheat framework into the process is strong, but the
// name match is still a name match, so it is not decisive on its own.
[DetectionCode.BlacklistedModuleLoaded] = new Entry
{
Weight = 55, Decisive = false, Description = "Known cheat or hooking library mapped into the game",
},
[DetectionCode.ModuleUnbackedByDisk] = new Entry
{
Weight = 45, Decisive = false, Description = "Module present without a loader notification (manual map)",
},
[DetectionCode.ModuleFromVolatilePath] = new Entry
{
Weight = 8, Decisive = false, Description = "Module loaded from a temp or downloads directory",
},
[DetectionCode.ModuleFromUntrustedPath] = new Entry
{
Weight = 5, Decisive = false, Description = "Module loaded from outside the trusted roots",
},
// External environment. Weak by construction: what is open on someone's second
// monitor is not evidence of what is happening inside the game.
[DetectionCode.BlacklistedProcess] = new Entry
{
Weight = 12, Decisive = false, Description = "A listed tool is running on the machine",
},
[DetectionCode.BlacklistedWindow] = new Entry
{
Weight = 10, Decisive = false, Description = "A listed tool has a visible window",
},
[DetectionCode.DebuggerAttached] = new Entry
{
Weight = 60, Decisive = false, Description = "A debugger is attached to the game process",
},
// Code integrity. This is the evidence worth acting on: something modified
// executable memory inside the process, which nothing legitimate does here.
[DetectionCode.NtExportPrologueModified] = new Entry
{
Weight = 70, Decisive = false, Description = "Syscall stub prologue rewritten as a detour",
},
[DetectionCode.GameCodeModified] = new Entry
{
Weight = 85, Decisive = true, Description = "Game code section modified in memory",
},
[DetectionCode.Il2CppMethodHooked] = new Entry
{
Weight = 90, Decisive = true, Description = "Inline hook on a watched gameplay method",
},
// Managed integrity.
[DetectionCode.ForeignHarmonyPatch] = new Entry
{
Weight = 6, Decisive = false, Description = "Harmony patch from an unrecognised mod",
},
[DetectionCode.EnforcementMethodPatched] = new Entry
{
Weight = 95, Decisive = true, Description = "The guard or process exit path was patched",
},
// Guard integrity, raised server-side. Tampering with the guard is treated as
// seriously as tampering with the game, because it is done for the same reason.
[DetectionCode.ChallengeFailed] = new Entry
{
Weight = 90, Decisive = true, Description = "Memory challenge answered with the wrong hash",
},
[DetectionCode.ChallengeUnreadable] = new Entry
{
Weight = 20, Decisive = false, Description = "Client could not read a challenged region",
},
[DetectionCode.GuardSilent] = new Entry
{
Weight = 50, Decisive = false, Description = "Guard stopped heartbeating while the match was live",
},
[DetectionCode.SignatureInvalid] = new Entry
{
Weight = 70, Decisive = false, Description = "Request signature did not verify",
},
[DetectionCode.SequenceReplay] = new Entry
{
Weight = 70, Decisive = false, Description = "Replayed or reordered request sequence",
},
[DetectionCode.PolicyRefused] = new Entry
{
Weight = 15, Decisive = false, Description = "Client refused to apply the issued policy",
},
// Hardware bans. These are recorded for the audit trail, not to move a score:
// a machine ban is a decision that was already made by a person, and running it
// back through the scoring path would let a threshold second-guess them.
[DetectionCode.MachineBanned] = new Entry
{
Weight = 0, Decisive = false, Description = "Session opened from a banned machine",
},
[DetectionCode.MachineLinkedToBan] = new Entry
{
Weight = 0, Decisive = false, Description = "Machine shares hardware with a banned machine",
},
[DetectionCode.MachineUnidentifiable] = new Entry
{
Weight = 0, Decisive = false, Description = "Client could not produce a machine identity",
},
// Device attestation. A session that proved it held a device key and then stopped
// signing with it is claiming to be somewhere it is not, which is the one thing
// the HMAC alone can never catch.
[DetectionCode.AttestationInvalid] = new Entry
{
Weight = 70, Decisive = false, Description = "Request not signed by the bound device key",
},
[DetectionCode.AttestationMissing] = new Entry
{
Weight = 0, Decisive = false, Description = "Session opened without a hardware-backed key",
},
};
public static Entry For(DetectionCode code)
{
return Entries.TryGetValue(code, out Entry? entry)
? entry
: new Entry { Weight = 0, Decisive = false, Description = "Unknown detection code" };
}
}
}