Skip to lesson
Game Hacking Academy
Preparing lesson text…
Exit reader mode
Voice and speed

Preparing read-aloud controls…

Lesson 12.3 Listening edition

Dynamic Windows A P I, application programming interface, Resolution

Runtime export lookup turns an optional Windows API into a checked capability with an explicit ABI and fallback.

About 29 minutes

Section: Import tables are not the only way to call an A P I.

Normally the Windows loader resolves a program’s imports before main. Sometimes a tool supports several Windows versions and wants to use a newer A P I only when it exists. Then it can:

obtain a handle to the system D L L, dynamic link library;

look up the named export;

convert the address to one proven function-pointer type;

call it or choose a fallback.

This is useful compatibility work. It is not a reason to hide imports or replace well-supported APIs with undocumented ones.

Look up an optional export. GetProcAddress returns an address.

Section: Detect the capability you need.

Do not infer one A P I’s presence only from a Windows version string. Compatibility settings, servicing changes, and alternative implementations can make version checks lie about the capability that matters. Look up the documented export, validate the result, and take the fallback when it is absent.

Presence still does not guarantee that one call will succeed. The handle may lack rights, the buffer may be too small, or the target may exit. Feature detection chooses a code path; normal error handling still applies inside that path.

In brief: Finding an optional export chooses a capability path; the chosen call still needs ordinary error handling.

Section: Define the contract from documentation and assembly.

For a known A P I, the Windows SDK declaration is the primary contract. Static analysis can confirm what the compiled caller does: argument registers, stack space, result checks, and error handling.

Keep the type alias beside the resolver:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

The pointer type says more than “address of a function.” It records A B I, application binary interface,, argument order, mutability, and result width.

Alternative: A typed function pointer is a delivery contract, naming where arguments go and how results return—not merely an address to jump to.

extern "system" names a different calling convention on each of the two targets this course builds, so the same alias arranges the same four values in two different ways. On 32-bit x86 it means stdcall: the caller pushes the arguments from last to first, so when the function starts they sit just above the return address, one per 4-byte stack slot, and the function removes them itself before returning.

Labelled memory cells

On x86-64, Windows passes the first four integer or pointer arguments in registers, in order, and returns an integer result in e a x, the low 32 bits of r a x:

Labelled memory cells

That is why a “close enough” signature fails in ways that have nothing to do with pointer size. Declare three parameters instead of four, and a 32-bit caller pushes 12 bytes while the function still removes 16 with ret hexadecimal 1 0, leaving the stack pointer 4 bytes away from where the caller expects it. On x86-64 the function reads a fourth argument from r 9 anyway, whatever that register happens to hold, and writes the result length through it as if it were a pointer.

Section: Resolve from an already loaded system module.

The resolver starts with kernel32.dll in the current process. It looks up QueryFullProcessImageNameW by its exported name, returns None if that name is absent, and otherwise converts the address to the A B I-checked function type established above. The caller can then choose the fallback rather than calling an unknown address.

Code example. The code’s comments explain: SAFETY: kernel32.dll is a NUL-terminated static string. 🔎 SAFETY: the export name is a NUL-terminated static byte string. SAFETY: the SDK declaration and export name establish this system A B I.

The Option is intentional. “Export absent” is a supported compatibility state, not necessarily a fatal error.

Cache the typed result only when module lifetime makes that safe. An address obtained from a module becomes invalid if that module can unload. System modules used for the process lifetime are simpler, but a plug-in resolver should keep module ownership and function-pointer lifetime together.

Section: Call with a retryable buffer.

Windows text APIs often accept a buffer plus an in/out length. Treat the length as untrusted after every call:

Code example. The code’s comments explain: SAFETY: the buffer is writable for `length` UTF-16 units, and the handle remains valid for the call. Windows writes the resulting unit count.

length means two different things on the two sides of the call. Suppose the process is Wesnoth at C:\Games\Wesnoth 1.14.9\wesnoth.exe, a 35-character path:

Labelled memory cells

Use an RAII-owned process handle from the earlier Windows chapters. The dynamic call does not change ownership rules.

Section: Design the fallback before lookup.

An optional A P I is useful only if the missing case has a defined behavior:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

If the full-path A P I is unavailable, a ToolHelp module snapshot may supply the main module’s path through MODULEENTRY32W.szExePath. It is a different observation: enumeration can fail across architecture or protection boundaries, and the module can change after the snapshot. Label the source and its limits instead of claiming that every fallback is simply a shorter path.

Narration: A fallback may provide a different kind of observation with different limits. Label its source so later code does not silently assume the stronger A P I succeeded.

Lesson diagram

Notice that a failed call does not automatically choose the fallback. The A P I can exist while the handle, buffer, or target state is invalid. Preserve that difference so compatibility logic does not hide an ordinary error.

Section: Detect bad assumptions.

Test the resolver with:

the correct export name;

one deliberately misspelled name;

32-bit and 64-bit builds;

a closed or insufficient-rights process handle;

a buffer whose returned length reaches its bound;

an ordinary imported call, then compare results.

Never call an unresolved address. Never replace None with address zero. Never choose a “close enough” function signature because two pointer sizes happen to match.

Section: Why this belongs in reversing.

When you inspect a game or tool, dynamic resolution explains a common pattern: a string names an A P I, GetProcAddress returns a pointer, and a later indirect call uses it. Reconstructing that chain turns anonymous control flow into a documented contract.

The responsible end state is clear: one typed resolver, one fallback, visible errors, and no hidden capability. That completes the D L L and A P I path. The next lesson follows a device request through the driver stack: which kernel component receives it, which device owns the work, and where the result returns.