Skip to content

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_H
  • AOWL_BE_SCM_SENTINEL
  • AOWL_BE_SVC_SENTINEL

Functions

SignatureLine
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

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.