Skip to content

API: let a mod know whether it loaded at process start or was injected into a running process #1022

Description

@mazany

Summary

The wiki already documents an exact way for a mod to tell whether Wh_ModInit
ran before the target began executing or was loaded into a running process —
Development tips → Avoid mod loading on process
startup
.
It works, and it is not a heuristic. But it is a hardcoded, per-architecture
offset into an undocumented TEB field, presented as a development-time tip to be
stripped before release.

I'd like the same fact exposed as a supported call — e.g.
BOOL Wh_IsInitialProcessLoad(), or a value the engine sets before
Wh_ModInit — so that mods needing it in released code don't each ship their
own copy of the magic numbers.

Why

Wh_ModInit runs in two very different situations, and for a whole class of
mods they differ in whether the mod can work at all.

Any mod hooking something the process consumes once, early — the command line,
the environment block, CRT initialisation, startup-only registry reads — is
effective only when loaded at process start. Injected into a running process,
the hook installs successfully, reports success, and accomplishes nothing,
because the thing it intercepts was already read.

This isn't a rare edge. It happens on every reinstall: mod install --file
reloads the mod into already-running targets, which is exactly the situation
where such hooks are inert. The mod reads as installed and enabled while
silently doing nothing until the app restarts — a confusing state to debug,
especially for a user who didn't perform the install themselves.

Concretely: my mod hooks GetCommandLineW to add
--remote-debugging-port=<port> so Chromium opens a DevTools port it can then
talk to. On a fresh process start this works. Reloaded into a running app, the
hook is installed but Chromium parsed its command line long ago — so the mod
picks a port, announces it, waits for an endpoint that will never exist, and
eventually gives up. Anything reading that announcement finds a dead port.

Knowing which case it's in, a mod can log an accurate "restart the target for
this to take effect", return FALSE cleanly instead of half-initialising, and
skip setup it knows cannot work.

What the wiki tip already gives, and what it doesn't

The documented check reads SameTebFlags (0x17EE on x64, 0x0FCA on x86) and
tests the 0x0400 initial-thread bit. Under the callback-thread model in
Mod lifetime
startup load runs on the target's main thread, runtime load runs on the
Windhawk engine thread — that bit answers the question exactly, with no
threshold and no timing assumption.

So this request is not "there's no way to detect this". It's narrower:

  • It's undocumented OS internals. The offset is a per-architecture constant
    with no contract behind it, and the inference also depends on Windhawk's
    current injection mechanics. Both are reasonable for a dev-time guard; both
    are uncomfortable in a mod shipped to users.
  • The wiki frames it as dev-only, with the opposite polarity. There it means
    "bail out on startup load so a crash can't loop". The case above needs the
    same check at release time, inverted — bail out on runtime load. Same fact,
    opposite use, and the tip explicitly isn't meant to survive to release.
  • It's only meaningful on the thread that runs Wh_ModInit. Elsewhere — a
    hook body, Wh_ModSettingsChanged, a worker thread — it tells you nothing, so
    it has to be latched into a global at init. Workable, but a supported call
    could be a queryable per-load fact.
  • Unclear whether it's defined for tool mods at all. WhTool_ModInit runs in
    a Windhawk-hosted process with a different lifecycle; I haven't tested what
    the initial-thread bit means there. An engine-provided answer wouldn't have
    the ambiguity.

Suggested shape

Either of:

BOOL Wh_IsInitialProcessLoad();   // TRUE when ModInit runs before the process executes

or a value the engine sets before calling Wh_ModInit, alongside the existing
WH_MOD_ID / WH_MOD_VERSION constants.

If this is considered adequately covered by the wiki tip, a reasonable smaller
outcome would be documenting the technique as release-safe (rather than
dev-only) and noting the inverted-polarity use — that alone would remove most of
the hesitation about shipping it.

Related

Pairs naturally with a $requiresRestart annotation on settings (separate
request) — same underlying reality, one at the mod level and one at the
individual-setting level. That one is independent of this: it's a settings-UI
affordance the engine must render, which no amount of mod-side detection can
substitute for.

Tested on 2.0.0-alpha.2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions