BloodStrike X - a bootstrap-injected internal, and the engine reversed to make it work
A bootstrapper drops the DLL, injects it into the game, and everything else runs inside the process. A companion proxy-SDK shim sits alongside NetEase's usermode anti-cheat modules and takes the whole reporting surface offline before it can fire — the detection interface, the per-frame tick, the five worker threads, four telemetry endpoints, the hardware-ID binding, the Python fingerprint scanner, and the plugin-registration scanner, all quiet, on their own terms, without any one layer relying on another for cover. A signed kernel driver handles cross-boundary memory ops and hardware mouse. The camera comes out of the GPU pipeline instead of engine memory. Aimbot, no-recoil, and the kill-aura patch the game's own Python runtime — not native hooks. The whole Messiah engine was reversed cold to make any of this possible: 3,900-line offsets header, every field proven from an IDA instruction. Finished project — build and source stay private.
Not an overlay. A DLL inside the process.
The primary build is an internal cheat. A separate bootstrapper — a small standalone Loader.exe with the payload DLL embedded as a byte array — waits for the game, drops the DLL to a random temp path, injects it via CreateRemoteThread(LoadLibraryW), then scrubs the temp file with zeros before deleting it. The DLL runs in-process from that point on: hooks D3D11's Present to draw, subclasses the game's WndProc for input, and patches the game's own Python runtime for the features the anti-cheat cares most about.
LoadLibrary is a classical anti-cheat tripwire — a module notification callback catches the load and the AC reports on it. It doesn't matter here, because a companion proxy-SDK variant sits alongside the game's own anti-cheat modules and neutralises the reporting surface end to end before it can fire. That surface is broad (its own section below); the codebase is one static library with three build modes gated by BSX_INTERNAL: an external variant with its own D3D11 window (for engine RE and debugging without touching the game), the bootstrap-injected DLL above, and a proxy-SDK variant that links the same static lib into a shim that co-resides with the game's usermode anti-cheat modules. The mechanism stays private throughout this page; the surface — what is defeated — does not.
One console binary, no files left on disk.
The loader is deliberately boring. Poll for the target process by name via ToolHelp; open with PROCESS_ALL_ACCESS; refuse if the process is 32-bit (belt and braces, the game is x64); enumerate loaded modules and bail if our DLL is already there (a double-inject installs two Present hooks and immediately crashes); drop the embedded bytes to %TEMP%\BSX_<random>.dll; call CreateRemoteThread pointing at LoadLibraryW with our path as the argument (the canonical trick — LoadLibraryW's signature is one LPCWSTR, which matches LPTHREAD_START_ROUTINE, and kernel32 is at the same address in every process on the same boot); wait synchronously so we can free the argument buffer once DllMain returns; then overwrite the temp file with zeros and delete it.
The DLL itself doesn't need the file after that — LoadLibraryW mapped it into the game as an independent copy. Scrubbing before delete means nothing recognisable stays on disk for a forensic scan of the temp directory to find later.
// Loader.cpp - the injection, minus the safety fluff. // LoadLibraryW happens to take one LPCWSTR argument, i.e. the same signature // as LPTHREAD_START_ROUTINE. That's the whole trick. auto loadLib = (LPTHREAD_START_ROUTINE)GetProcAddress ( GetModuleHandleW (L"kernel32.dll"), "LoadLibraryW"); void* remote = VirtualAllocEx (hProc, nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); WriteProcessMemory (hProc, remote, dllPath, bytes, &wrote); HANDLE hT = CreateRemoteThread (hProc, nullptr, 0, loadLib, remote, 0, nullptr); WaitForSingleObject (hT, 20000); // DllMain runs synchronously on this thread CloseHandle (hT); VirtualFreeEx (hProc, remote, 0, MEM_RELEASE);
Memory ops and mouse, out of the process's own hands.
Even from inside the game, memory reads and writes go through a signed kernel driver — BSXDriver.sys, its own project — over an IOCTL device at \\.\BSXKernel. The driver exposes six operations: base-address lookup, DTB (CR3) resolve, read, write, kernel-side pattern scan, and mouse input. Reads and writes walk PML4 with the target's cached CR3 so the game process never sees its own handle opened by the payload, and the pattern scanner runs entirely in kernel space (page-fault-safe, MmIsAddressValid-guarded) instead of copying whole regions to userland and scanning there.
Mouse input is emitted at the MouseClassServiceCallback level — the same layer RawInput comes in on — so the game sees indistinguishable HID events instead of a process posting messages to itself. The DLL loads the driver on first launch: mapper and driver bytes are also embedded, dropped to %TEMP% with random names, the mapper is spawned, we wait, then both files are deleted. On subsequent launches within the same boot the driver is already mapped and the DLL just opens the device.
// KernelComm.cpp - the whole surface between DLL and driver. // Six IOCTLs, natural-alignment structs matching the driver exactly, // device path XOR-obfuscated so "\\.\BSXKernel" never appears in .rdata. constexpr ULONG IOCTL_BASE = CTL_CODE (FILE_DEVICE_UNKNOWN, 0x2B2, ...); constexpr ULONG IOCTL_CR3 = CTL_CODE (FILE_DEVICE_UNKNOWN, 0x2B3, ...); constexpr ULONG IOCTL_READ = CTL_CODE (FILE_DEVICE_UNKNOWN, 0x2B4, ...); constexpr ULONG IOCTL_WRITE = CTL_CODE (FILE_DEVICE_UNKNOWN, 0x2B5, ...); constexpr ULONG IOCTL_MOUSE = CTL_CODE (FILE_DEVICE_UNKNOWN, 0x3B1, ...); constexpr ULONG IOCTL_PATTERN = CTL_CODE (FILE_DEVICE_UNKNOWN, 0x2B6, ...); auto devPath = XORW (L"\\\\.\\BSXKernel"); g_hDriver = CreateFileW (devPath.c_str (), GENERIC_READ | GENERIC_WRITE, ...);
Steal the vtable, then never touch the backbuffer twice.
The DLL's worker thread creates a throwaway D3D11 device and swap chain against a hidden window, reads slot 8 of the swap chain's vtable (Present), swaps in our own function, and releases the throwaway. Because the vtable is shared across every IDXGISwapChain in the process, the game's presents now come through us — as does every other hooked chain, which is where the multi-swap-chain ledger comes in (more on that in a moment).
An older version of this hook held a persistent ID3D11RenderTargetView on the backbuffer across frames. That caused the game to freeze forever the first time the player alt-tabbed or toggled windowed / fullscreen: DXGI cannot destroy the backbuffer for a resize while any RTV still references it, so ResizeBuffers deadlocks. The fix is per-frame acquisition — grab a fresh backbuffer texture, create the RTV, use it, release it before Present returns. No reference survives a frame, resize always succeeds, no hkResizeBuffers is needed.
The Discord overlay taught the second lesson. Its own swap chain lives in the same process, its Present calls also come through our hook, and the earlier "one draw per frame" check didn't catch the problem because both counters lived inside the hook function and doubled together. The fix is a small ledger that records the first four swap chains it sees and their present counts — the game's chain is always slot 0, since it presents long before Discord's overlay attaches — and a toggle to draw only into that chain.
The render matrix isn't in engine memory. Take it off the vertex shader.
The engine has two cameras. The aim camera at ICamera+0x7C — the one every classic external reads — is not the render camera in third person, and no combination of offsets recovers it: the cached view matrix at +0x41C is rebuilt from +0x7C, so p = -R·t hands back the same point. Two builds tried to reconstruct a third-person render origin from placer offsets, and both broke enemy boxes — which is dramatically worse than the bug they were chasing.
But the GPU is drawing the frame from the real pose, every frame, because the engine hands it a view-projection matrix. So take it from the source. Three slots on ID3D11DeviceContext's vtable get hooked: VSSetConstantBuffers to learn which buffers feed the vertex stage, Map to catch the pointer the engine is about to write into (constant buffers are written with MAP_WRITE_DISCARD, which orphans the previous allocation — by the time Present runs the memory holds whatever the UI pass put there, not the world matrix), and Unmap to read the freshly-written 64 bytes before handing the call on.
Identifying which buffer and which 64-byte window is the world view-projection is done not by matching the shape of a matrix — those tests are blind to the projection half, so the viewmodel camera passes every one of them and lands the ESP a quarter of a screen off — but by agreement. The reversed ICamera::ProjectInner algorithm is correct in first person (that's how the ESP shipped originally). So a candidate window is the world VP if and only if it projects a spread of probe points to the same pixels the reversed algorithm does, three frames in a row. Lock in first person; the pair (buffer, offset) stays valid in both first and third because the engine writes the same slot either way.
Measured live: pixel error at lock 0.04 px, then 0.05 px against the reversed algorithm in first person and 261.28 px in third off the same slot. The 261-pixel number is the old external-style projection being wrong by a quarter of the screen for the whole life of the project — nobody noticed because it was the only answer available.
The features the anti-cheat cares about live in the game's own Python.
Messiah has a Python scripting layer wired in via NetEase's Asyncore binding framework, and a surprising amount of gameplay logic lives there rather than in the C++ engine. Recoil, weapon attributes, the fire path, spread, damage, target choice for the game's own bot AI, avatar rebuilds on respawn — all Python. The internal ships an in-process Python execution helper and installs the aim, aura, no-recoil, and weapon-mod features as wrappers on the game's own methods. Three details are worth calling out because each of them cost a rebuild to figure out.
No-recoil isn't a native hook. The obvious target is StateRecoil — the recoil state class. Wrapping it patches nothing, because two mechanisms in the engine's framework move the methods elsewhere before the game runs: @StateWrapper renames OnEnter / OnExit to OnRecoilEnter / OnRecoilExit and deletes the originals, and @Components is not inheritance — it walks inspect.getmembers and does setattr(placer_class, name, im_func(func)). Every state method is copied into the placer class's own __dict__ at import time; the placer never consults StateRecoil again. So the target is the placer classes, not the state class, and both FpsPlacer and ALSTpsPlacer get patched independently.
And there's a trap on top: the game's own qsec.py reads StateRecoil.OnRecoilEnter.__code__.co_consts, checks that the 0.01 deg→rad literal is still present, and reports to the server if it isn't. Rebinding that method would be a self-report. Patching the placer copies leaves the fingerprinted code object untouched.
Aim redirection is a wrapper on SpellWorker.GetShootDir. The wrapper always calls through first (the original reads is_real_ads, shoot_idx, and consumes the spread RNG — skipping it leaves the weapon a step behind), then decides whether to return the game's direction or a redirected one. Target choice happens entirely in Python — the same layer already has the entity list, camera, our avatar, the team ID, and the game's own physics raycast (space.RawRaycast). An earlier version chose the target in C++ and published its world position for Python to re-join by nearest distance; that join failed for four rebuilds before it became clear the join was the surface every real bug hid behind. Deleting it deleted the whole class of bug. C++ now passes settings across and nothing else.
The kill-aura is registered as a bound method on StoryTick. The game's own plugin scanner (story_tick.py's doAdd) walks callbacks and calls inspect.isfunction on them; if the function name matches a blacklist ('AIMUpdate', 'EspUpdate') or is otherwise a plain function, it fires SALogToServer('AntiPlugin'). A bound method returns False from inspect.isfunction, so the entry is skipped before the name is ever read — the aura's OnUpdate(delta) is _BsxSaRelay().OnUpdate, and it never trips the scan. Same reasoning covers Chams' per-frame tick.
# The no-recoil self-proxy. `self.cameraEasing = shim` would leave the game's # camera permanently owned by our shim on any exception - so nothing is # mutated. The original is called with a PROXY self that answers # .cameraEasing with our shim and forwards everything else to the real placer. # Attribute writes go to the real object; self.OnRecoilUpCallback binds on the # real object; the FSM chain is untouched. class _BsxPlacerProxy(object): def __init__(self, real, shim): object.__setattr__(self, '_real', real) object.__setattr__(self, '_shim', shim) def __getattr__(self, name): if name == 'cameraEasing': return self._shim return getattr(self._real, name) def __setattr__(self, name, val): setattr(self._real, name, val) # writes flow through to the real placer # The shim's RotateEasing zeros yaw+pitch, keeps duration+easingType. Arg # order is (yaw, pitch, time, easingType) - byte-exact against the .pyc and # the native store order; the struct field name says "PitchYaw" and lies.
A compiled pixel shader, hanging off the existing hook.
Chams is a per-actor material override driven by a custom compiled pixel shader — a small D3D11 setup that registers as an observer with the draw hook the view-matrix module already owns. It never patches a vtable itself. This is deliberate: the view-matrix module lives on device-context slots 12, 13, 20, 21, and a second module patching the same slots would fight it silently — whichever installed second would just stop seeing the traffic. Registering as an observer, called immediately before the original draw on the immediate context only (deferred draws are recorded into command lists that replay later — re-issuing geometry from one lands somewhere the code can't reason about), sidesteps the whole conflict.
Enabling / disabling and material choice go through Python — the material swap happens on the game's own StoryTick — and per-frame counters live in the same tick relay, not in the draw hook that runs thousands of times per frame.
3,900 lines of offsets, every field with a proof.
Nothing above works without reversing the engine first. BloodStrike runs on NetEase's Messiah engine (internal codename NewSpike) — proprietary, no dumper, no SDK, no public offsets, a 140 MB stripped PE. Every offset was pulled out of IDA by hand and lives in a single Config.h that grew to 3,962 lines, each field annotated with the RVA of the subroutine it was proven from and the exact instruction that proves it. When the game updates, the diff is a re-verify against those anchors, not a re-derivation.
The parts that carried the most weight:
Actor enumeration via the engine's own linked list. Players use Character::Actor, a separate class from IEntity (which is for static world geometry). The engine keeps every live ActorComponent in a circular doubly-linked list plus a hash table at a fixed global 0x8A8E810 — the "Actor Manager". Found the GetAllActors() Python binding, decompiled it, walked the same list. No heap scan, no false positives. The list also incidentally solved a second problem: ActorComponent inherits from SkeletonComponent, so the same pointer is also the entry into that actor's bone cache. The "which skeleton belongs to which actor" matching problem never had to exist.
The skeleton has two transforms per bone, at different offsets, and the engine's own accessor reads the wrong one for gameplay purposes. Each 160-byte bone entry carries a bind-pose matrix at +0x30 (written once at asset load, static) and a live animated one at +0x60 (rewritten every frame by the animation solver). The engine's GetBoneWorldTransform returns the bind pose, so an ESP that copies its behaviour draws anatomically-correct skeletons that never move with the character. Live translation is at +0x7C. Then bones are stored in a left-handed frame inverted from the actor's transform, so X and Z have to be negated before the rotation matrix multiplies through — miss it and the skeleton renders mirrored.
Bone-name lookup is three pointer hops deep and matches an exact instruction sequence in the engine. poseEntry+0x88 → sourceBone+0x58 → NameHandle → NameEntry+0x18. The instruction sequence at 0x140EF0D71 is mov rcx,[rax+88h]; add rcx,58h; call sub_140470190. Reproducing the walk verbatim is what made bone-name matching survive across patches.
Wall raycasting has two implementations, and Python's is faster. A read-only walker for NVIDIA PhysX 3.4.3 was reversed end to end (NpScene → NpRigidStatic → NpShape → ShapeCore → Gu::TriangleMesh, verified against the debug serializers the engine leaves compiled in) — this is what an external build uses. The internal ended up using the game's own space.RawRaycast through the Python bridge, because we're in-process and the call is right there.
Direct syscalls, VMProtect, hashed exports, hidden threads.
Independent of anything AC-specific, the DLL is hardened against the class of usermode inspection a modded game or a hostile debugger might do to it. A few pieces worth naming:
Direct syscall stubs. Window enumeration would normally go through EnumWindows / GetWindowThreadProcessId, which live in user32.dll and are trivial to hook. Instead, the DLL reads win32u.dll from disk (the loaded image can be hooked; the on-disk file cannot be modified without file-integrity alerts), extracts syscall numbers for NtUserBuildHwndList and NtUserQueryWindow, and generates its own stubs at random offsets inside NOP-padded pages that are allocated RW, written, then flipped to RX — no RWX page ever exists.
VMProtect scoped to hot paths. Full VIRTUALIZE markers around init and post-init hardening; MUTATE around per-frame paths (reads and writes go through the kernel driver on every frame — full virtualisation is a 50–100× overhead on the hot path). String obfuscation via xorstr on every meaningful literal. Config file encryption with a per-build salt derived from __TIME__ / __DATE__, so a config saved against an older build cannot silently deserialise against newer offsets.
Lazy imports. A hashed PEB-walk export resolver (Stealth::GetModuleBase(hash), Stealth::GetExportAddress(base, hash)) replaces the IAT for anything the runtime touches. Function names never appear in the import table.
Hidden security thread. The main render loop only reads one atomic flag — SecurityManager::g_compromised. The actual sweep (IAT and inline hook detection, process enumeration, etc.) runs on a background thread; earlier versions did the sweep inline and produced a 300 ms+ frame spike every 7 seconds. On compromise the loop drops out cleanly with no popup and no log.
PE header erasure post-init. Once every DLL policy is locked down and no further module lookups need the DOS/NT headers, they get zeroed. A memory scan for the classic MZ...PE signature finds nothing where our module lives.
CR3-based reads even in-process. Memory reads always go through the kernel driver's IOCTL_READ, even for the game's own memory. The driver walks page tables directly rather than using the process's handle table, which means anything the game does to trip on its own memory being accessed sees nothing.
A lot of moving parts, quiet at every layer.
The anti-cheat isn't one thing. BloodStrike's usermode stack (NtUniSdkBase.dll, NtUniSdkRoostX.dll, libenvsdk.dll, plus a server-streamed Python layer that arrives at login) has more than a dozen independent moving parts that report back to NetEase — detection callbacks, per-frame scans, five named worker threads, four telemetry endpoints, hardware-ID binding, a Python fingerprint scanner, a plugin-registration scanner, and more. Each of them is a separate ban vector; each of them is off in this build. The scope is what makes the LoadLibrary point above moot — the surface below is what would otherwise catch the injection and act on it.
What stays private is how. What follows is the surface — the layers that were mapped, understood, and defeated. If it's on this list, this build is quiet on it.
The AC SDK's own machinery. The detection interface itself. The per-frame tick that runs its scans. The dispatch path that would forward a triggered detection to the network. The five named worker threads (main, log, DRPF, client-log, IO) that carry out reporting. The initialisation sequence hooked so nothing arms at all if we want it not to. The interface pointer resolved by signature scan rather than a hardcoded address, so an SDK rebuild doesn't move the target.
Every telemetry endpoint the SDK talks to. DRPF (Data Reporting Platform Framework — the primary detection-carrying channel). MCount analytics ingest (device model, OS, MAC, SDK version). AppLog Matrix (client-side event logging). ACSM (the Python-initiated AC channel added in a later patch). TPSL (class upload). optsdk / envsdk moderation. And whoami, which is the endpoint that binds a hardware ID to an account server-side — the primary way a ban reaches a specific machine on the next install. All blocked with real DNS-failure semantics so libcurl aborts cleanly rather than retrying. Payment, auth, patching, and content-config endpoints are deliberately left open, because they'd break login.
Arm timing. The block layer is installed early but only activates after login completes — init and the login handshake get full network access, so nothing that gates the game's boot ever fails. From login onward, every telemetry DNS lookup returns WSAHOST_NOT_FOUND and the AC's outbound traffic simply stops.
Watchdogs on our own protections. The SDK re-initialises during re-login and can rebuild the worker thread and re-enable the detection interface. Per-tick idempotent re-arm covers both, so nothing we disabled comes back on quietly.
The Python anti-cheat. The game's qsec.py fingerprints StateRecoil.OnRecoilEnter.__code__.co_consts for the 0.01 deg→rad literal and reports on missing values — no-recoil is routed around this by patching the placer classes and leaving the fingerprinted code object alone. The story_tick.py plugin scanner walks registered callbacks with inspect.isfunction and blacklists names like 'AIMUpdate' / 'EspUpdate', calling SALogToServer('AntiPlugin') on a hit — our tick relays are registered as bound methods, so the scan skips them before the name is ever read. Recoil-value integrity is likely fingerprinted too — our shim passes zeros to the camera but never touches the recoil data tables, so there's no zeroed-recoil signature to find.
Server-streamed AC scripts. NetEase streams new Python AC modules from the server at login, not from the local Patch.mpk. Both major CPython entry points (module import and code exec) are instrumented with lock-free ring buffers + a background flush thread, so any new AC module pushed by NetEase is visible on disk before it runs. Nothing about that instrumentation is on the hot path — it's queue-and-return, and CPython's own dump machinery runs on a separate GIL-owning thread with dedup so a hot module gets marshaled once and never again.
Kernel-side ban vectors. The AC is fully usermode; the memory-access surface is not. Reads and writes go through the driver's PML4 walk with the target's cached CR3 rather than through a process handle, so anything the game does to trip on its own memory being accessed with a handle sees nothing. Mouse input comes in at MouseClassServiceCallback level, indistinguishable from real HID.
Static signatures. Every meaningful string in the payload is XOR-obfuscated with per-string keys; the device path (\\.\BSXKernel) never appears in .rdata; config files are encrypted with a per-build salt from __TIME__ / __DATE__; the patched vtable copy lives on a dedicated VirtualAlloc page flipped to PAGE_READONLY after writing, so it matches the memory protection profile of a legitimate .rdata vtable rather than a writable one in .data; imports are resolved via hashed PEB walk instead of the IAT; PE headers are zeroed post-init so a memory scan for MZ...PE finds nothing.
The rule for the whole surface: never leave one layer relying on another for cover. Each is off on its own terms, so if any single defence downstream is patched by an SDK update, the others are still quiet.
A full feature surface, organised.
Sidebar navigation on the left, header strip with a large icon and title, two-column card grid for content. INSERT opens the menu, F9 opens a separate debug overlay (independent — both can be open at once), END exits cleanly. Everything below runs on the internal build.
- Aimbot — driver-mouse input humanised through jitter, reaction min / max, deadzone, max speed, headshot cap; target-mode blend (nearest / crosshair / smart), per-limb selection, FOV, smoothing, team check, visible-only via the game's own
RawRaycast - Silent Aim — a wrapper on
SpellWorker.GetShootDir. Target picked in Python (entity list, camera, team ID, physics raycast all right there); C++ passes settings across and nothing else. Uses the game'sSpellStrikeknowledge that the first three metres of the round travel from the muzzle along the TPS direction and everything past that travels from the camera along ours — aiming camera-to-target is the correct answer end to end - Kill Aura — auto-fire, ray-confirmed only ("blind" picks aim but do not fire), velocity-predicted lead (no entity in the dump exposes a velocity attribute — checked; derived by differencing
entity.positionbetween ticks, EMA-smoothed, capped at 60 m/s), knocked-target deprioritised not excluded, weapon range read from the game's ownGetWeaponAttrValue('damage_range'), single-shot weapons pulse-fired viaIsGunNeedOperateFireClick - Spinbot — view-model spin with configurable rate and axis
- No-Recoil — placer-class patch with the self-proxy pattern above; touches neither
StateRecoilnor the recoil data tables (both fingerprinted), and leaves duration and easing type intact so the animation still looks natural
- ESP — boxes, healthbars, name / distance / weapon, snaplines, off-screen indicators; every world→screen projection goes through the GPU-lifted view-projection, so the ESP is always drawing to the same pixels the game is
- Skeleton — 58 animated bones per actor from the live pose transform at
+0x7C, handedness-corrected, parent-chain validated - Chams — custom pixel shader hanging off the existing draw hook as an observer, per-team, occluded pass
- Native wallhack — engine's own
IsOutlinedrender pass toggled viaIEntity+0x131 bit 7andIsThermalVisibleat+0x132 bit 0. The engine draws these itself; we just set the bits
- Weapon skins, character skins, melee skins, character models — driven off the engine's reflection-property registry (property ID 79 for entity lists, cached descriptors at fixed globals), so new items dropped by patches surface without a rebuild
- Weapon mods — fast reload / rapid fire, hooked into the game's own
GetWeaponAttrValue; the attribute hook runs inside the game's function, not in our per-frame loop - World tools — fog / TOD / grade through the env volume, with a slow re-tick because world-field transitions clobber env writes
- Movement — modifies the character controller; re-applied on a slow tick because respawn rebuilds the
CharCtrl - Config manager — versioned, per-build encrypted; stale configs never load against wrong offsets
- Bootstrap loader — embedded DLL bytes, random temp path,
CreateRemoteThread+LoadLibraryW, temp file scrubbed with zeros before delete - Kernel driver — IOCTL device
\\.\BSXKernel, PML4-walked reads and writes via the target's cached CR3, kernel-side pattern scan, mouse input atMouseClassServiceCallbacklevel - Present hook — vtable steal via throwaway device / swap chain, per-frame RTV lifecycle (no persistent backbuffer refs, so DXGI resize always succeeds), multi-swap-chain guard (game is slot 0)
- GPU-pipeline camera — view-projection lifted at
VSSetConstantBuffers/Map/Unmap, world-pass identified by agreement with the reversed projection algorithm rather than by pattern-matching the matrix - Python bridge — in-process helper; wrappers register as bound methods so the game's own plugin scanner (
inspect.isfunction) skips them - Own hardening — direct syscalls from disk-mapped
win32u.dll, VMProtect-scoped hot paths, hashed export lookups, per-build config salt, hidden background security thread, PE header erasure - Frame profiler —
BSX_PROFslot timers across every subsystem, surfaced on the F9 debug overlay
"The most useful sentence I wrote across the whole project was in a comment: this offset was proven from this instruction, in this function, at this address. When the game updates, that sentence turns a re-derivation into a five-minute re-verify."
A menu that reads like a product.
Custom ImGui theme, left icon-and-label sidebar with a sliding accent indicator that smooths between tabs, header strip with a large tab icon and title, and a two-column card grid. Material-style click ripples on nav items, per-widget hover / active state tweened with an exponential-decay smoother, and a crossfade-plus-slide on tab switches. Combat and Visuals both use a sub-tab strip so no card ever gets crushed — dropping a fifth sub-tab in doesn't need another layout argument.
Configs hot-swap at runtime and nothing needs a restart to verify. The debug overlay (F9) is a separate ImGui window with per-subsystem frame timings, the GPU-lifted view-projection's lock status and pixel error, kernel driver RPM / WPM counters, swap-chain ledger, and the Python bridge's own instrumentation for every wrapper it installed.
From here.
BloodStrike X is finished and closed. Build private, source private, no further updates planned. The RE notes and the 3,900-line offsets header live in the repo as a reference for the next Messiah-engine project, if that ever happens. The pieces worth carrying forward — GPU-pipeline camera capture, the Python-runtime patching approach for scripted systems, the bound-method trick against Python plugin scanners, the offset-provenance discipline — are portable to any NetEase title on the same engine, and the D3D11 present / view-matrix hook stack is portable to any DX11 game full stop.
If you want to talk about the RE work in more depth, Discord is fastest.