Appearance
aowlspt_beguard.h
Source: abi/aowlspt_beguard.h — 226 lines, 12 file-scope functions.
What this header owns
Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.
text
aowlspt_beguard.h — letting a post-1.0 client boot without BattlEye.
Post-1.0 `EscapeFromTarkov.exe` will not start unless the BattlEye service
is running. That is not BattlEye refusing; it is BSG's own code, in a
modified `UnityPlayer.dll`, and it is checked before Unity brings IL2CPP up.
The evidence is in the binary itself:
BEService
The required BattlEye service does not exist.
The required BattlEye service is not running.
.?AV?$_Fake_no_copy_callable_adapter@P8BattlEyeService@Guard@BSG@@...
and in its import table, which stock Unity does not have:
ADVAPI32.dll OpenSCManagerA, OpenServiceA,
QueryServiceStatusEx, CloseServiceHandle
`BSG::Guard::BattlEyeService` opens the service manager, opens `BEService`,
asks for its status, and refuses to continue unless it reads
`SERVICE_RUNNING`. Both of its failure messages were observed on this
machine on 2026-08-19: "does not exist" against a target with no `BattlEye\`
directory, and "is not running" once the directory was copied in.
## What this does, and what it deliberately does not
It answers that one question, for that one caller, and nothing else. The
modded client plays against a backend on `127.0.0.1` and never contacts
BSG, so there is no live service being deceived here -- the alternative to
this file is not "BattlEye protects something", it is "the game does not
start". What it replaces is the *other* way to get a post-1.0 client to
boot, which is to install and run BattlEye for real, let it load into a
process with an injected DLL in it, and let it report that to BattlEye's
infrastructure keyed to the machine. This avoids all of that: BattlEye is
never installed, never started, and never loaded.
## Why the import table and not the functions
Two reasons, and the second is the one that decides it.
Patching `QueryServiceStatusEx` itself would change the answer for every
caller in the process. Patching `UnityPlayer.dll`'s IAT changes it for
`UnityPlayer.dll` alone, which is the only module in the install that
imports those four symbols at all. A mod, the overlay or the CRT asking the
service manager a real question still gets a real answer.
And this has to run in the constructor, under the loader lock, which rules
out the detour engine and rules out `GetProcAddress`. `QueryServiceStatusEx`
is a *forwarded* export on modern Windows -- advapi32 forwards it to
sechost.dll -- and resolving a forwarder can make the loader map a module,
which under its own lock is a deadlock. Reading an already-mapped PE's
import descriptors and writing one pointer through `VirtualProtect` touches
the loader not at all.
It has to be the constructor rather than the host's boot thread because of
how injection is ordered (`aowlspt_inject.h`): the injector waits for
`LoadLibrary` to return before it resumes the game's main thread, so
anything done here is done before the guard can run. The boot thread does
not start until the loader lock is released, which is a race with that
resume -- and the guard would win it.
## Why there is no configuration switch
The condition is its own switch. If the client in front of it does not
import those symbols into `UnityPlayer.dll` -- any Unity build that is not
BSG's -- there is nothing to patch and nothing is patched. Arming is
evidence that the guard is there.
Nothing is logged from here: the host's log does not exist yet. Every
outcome is recorded in the counters below and `aowlhost.nim` reports them
once there is somewhere to report to. A guard that patched nothing and a
guard that was never reached must not look alike in that log, so "not
armed" and "no such import" are different states rather than one absence.Constants
AOWLSPT_BEGUARD_HAOWL_BE_SCM_SENTINELAOWL_BE_SVC_SENTINEL
Functions
| Signature | Line |
|---|---|
SC_HANDLE WINAPI aowl_be_OpenSCManagerA(LPCSTR machine, LPCSTR db, DWORD access) | 120 |
SC_HANDLE WINAPI aowl_be_OpenServiceA(SC_HANDLE scm, LPCSTR name, DWORD access) | 131 |
BOOL WINAPI aowl_be_QueryServiceStatusEx(SC_HANDLE h, SC_STATUS_TYPE lvl, LPBYTE buf, DWORD cb, LPDWORD needed) | 150 |
BOOL WINAPI aowl_be_CloseServiceHandle(SC_HANDLE h) | 186 |
void aowl_be_guard_arm(void) | 209 |
int32_t aowl_be_guard_armed(void) | 217 |
int32_t aowl_be_guard_module(void) | 218 |
int32_t aowl_be_guard_looked(void) | 219 |
int32_t aowl_be_guard_answered(void) | 220 |
int32_t aowl_be_guard_scm(void) | 221 |
int32_t aowl_be_guard_opened(void) | 222 |
int32_t aowl_be_guard_passed(void) | 223 |

