Log Subsystem¶
Binary:
MW4Mercs.exe| Classified VAs: 192 | Exhaustive index: MW4Mercs.exe
Logging, assertions, error reporting, and diagnostic output.
Role and boundary¶
The EXE-side log subsystem is the game's diagnostic and error-reporting layer.
It is separate from ZlogLib.dll: the EXE does not load ZlogLib, and the
-zlogname/-zlogstyle switches feed this in-EXE formatter. The two systems
have different buffers, formatting, and file handling.
The subsystem covers three related jobs:
- Emission: append formatted text and numbers to a growable buffer and forward the result to registered log targets.
- Assertions: format an assertion message, optionally force a crash, and dispatch it to the error reporter.
- Error reporting: build a file/line-prefixed message, show it through the error dialog/OutputDebugString path, and handle the user's response.
The code is concentrated in 0x006F9000-0x006FB000, with the general error
entry at 0x006BFA50. The band also contains FPU diagnostic checks and
background file-loading helpers that share the same module data; not every VA
in the range is a log emitter.
Log targets¶
The subsystem keeps a linked list of log targets at 0x008493C8. The target
objects are vtable-based and receive different notifications:
| Function | Behavior |
|---|---|
FUN_006F9000 |
Walks the list and calls vtable +0x20 on each node; returns true only if every call returns true |
FUN_006F9050 |
Repeatedly calls vtable +0x08 on the head until the list is empty; returns 1 |
FUN_006F9070 |
Sets 0x007B6ECC, walks the list calling vtable +0x14, clears the flag if any call fails, then calls FUN_006F90E0 |
FUN_006F90E0 |
Stores its argument in 0x008493CC and calls vtable +0x24 on every node |
FUN_006F90D0 |
Returns the dword at 0x008493CC |
A second list at 0x00849EC0 holds registered log/source nodes.
FUN_006FA970 builds a 0x18-byte key, searches the list with repe cmpsb,
and inserts a new node when no match exists. FUN_006FAA00 walks the list and
frees each node; FUN_006FAC50 frees both this list and the allocation list at
0x00849EBC.
Buffer append primitives¶
The formatter builds its report in a custom string buffer with this layout:
| Offset | Meaning |
|---|---|
+0x00 |
capacity |
+0x04 |
cursor (write offset) |
+0x08 |
buffer base |
FUN_006FA880 appends a NUL-terminated string. It computes strlen(src),
clamps the copy to the remaining capacity, copies strlen + 1 bytes (including
the source terminator), writes a terminator, and advances the cursor to
cursor + copied - 1. A NULL source leaves the buffer unchanged.
FUN_006FA8F0 converts an integer to decimal with _itoa(value, buf, 10) and
appends the resulting string through the same clamped-copy logic. Both
functions return the buffer pointer in EAX and are the primitive writers used
by the large formatter.
The main error/log formatter¶
FUN_006F91D0 is the 5,797-byte output function. It is __stdcall with two
parameters (RET 0x8): a mode/flags word and the message string. It builds a
report containing:
- the caller's message;
- a stack walk / return-address listing;
- system and process details;
- formatting and diagnostic text.
It then presents the report through MessageBoxA and/or
OutputDebugStringA, depending on the mode and the current diagnostic state.
The user's response selects continue, exit, or debugger attachment. The function
returns an integer result; callers use that result to decide whether to break
into the debugger or abort.
The two main callers are:
FUN_006FAB90- the assertion helper. It formats a message into a 256-byte stack buffer with_vsnprintf, optionally writes to address 0 when the global at0x00849EA8is set, then callsFUN_006F91D0with mode0x183.FUN_006BFA50- the general error-log entry. It builds afile(line) :prefix when source information is available, orUnknown File :otherwise, appends the formatted message, and callsFUN_006F91D0with mode0x201. If the formatter returns non-zero, it executesINT3.
FUN_006BFA50 chooses the prefix from 0x0082F338, 0x0082F330, and the
source path/line globals. When the path is available it calls
GetFullPathNameA (FUN_006FBC60) and extracts the filename after the last
backslash before formatting %s(%d) :.
Assertion path¶
FUN_006FAB90 is the assertion/error utility used by vtable assertions such as
FUN_00415B70. It reads the format string and varargs from the caller's stack,
formats into a 256-byte buffer, and checks the global 0x00849EA8:
- when non-zero, it deliberately dereferences NULL to force a crash;
- otherwise it calls the main formatter with mode
0x183.
The formatted buffer is always NUL-terminated at buffer[0xFF], so a
truncated assertion is still a valid string.
Diagnostic and loading helpers¶
The same address band contains diagnostics and file loading that support the log/error path:
FUN_006FAA30sets the FPU control word and the global at0x00849EAC;FUN_006FAAB0validates the control word and raises an assertion if math exceptions or precision have been reset.FUN_006FACA0opens a file, reads it into a heap buffer, and returns the buffer/size through out-parameters. It normalizes UNC paths and supports the callback at0x00858A44.FUN_006FAE50is a background file-loader thread: it waits on two events, opens/maps each pending node in0x00856710, reads it, and marks it loaded.
These are not text-formatting functions, but they share the module's globals and are part of the diagnostic/file-support band.
Error and log flow¶
The typical flow is:
assert / error call
-> format message into a local buffer
-> FUN_006FAB90 (assert) or FUN_006BFA50 (error_log)
-> add source prefix (error_log only)
-> FUN_006F91D0 (mode 0x183 or 0x201)
-> append message + stack/system details to the report buffer
-> show dialog / OutputDebugString
-> return continue/exit/debugger result
The log-target list is used for registered observers, not for the primary error
dialog. FUN_006F9000/FUN_006F9050/FUN_006F9070/FUN_006F90E0 notify
those targets through their vtable slots; the formatter's dialog path is
independent.
Invariants and failure modes¶
- The buffer append clamps to capacity but always copies the source terminator;
the cursor update is
cursor + copied - 1, notcursor + copied. - A NULL string source is a no-op; an integer append always formats.
FUN_006F91D0has two stack parameters and returnsRET 0x8; callers must use the__stdcallconvention.- The assertion helper deliberately crashes when
0x00849EA8is non-zero; this is an intentional failure path, not memory corruption. FUN_006BFA50executesINT3when the formatter returns non-zero, so a debugger sees the error at the source site.- The log-target list and the source-node list are separate; clearing one does not clear the other.
- The FPU checks are diagnostics, not logging; they raise assertions when the control word no longer matches the expected state.
Key entry points¶
| VA | Detail | What it does |
|---|---|---|
0x006F9000 |
targets | notify log targets via vtable +0x20 |
0x006F9050 |
targets | drain targets via vtable +0x08 |
0x006F9070 |
targets | notify targets via vtable +0x14 |
0x006F90E0 |
targets | store state and notify via vtable +0x24 |
0x006F91D0 |
formatter | build and present the error/log report |
0x006FA880 |
buffer | append a string |
0x006FA8F0 |
buffer | append an integer |
0x006FA970 |
targets | insert a 0x18-byte source node |
0x006FAA00 |
targets | free the source-node list |
0x006FAB90 |
assert | format and dispatch an assertion |
0x006BFA50 |
error | build the file/line prefix and dispatch an error |
0x006FACA0 |
file | open/read a file into a heap buffer |
0x006FAE50 |
file | background file-loader thread |
0x006FAA30 |
diagnostic | set the FPU control word |
0x006FAAB0 |
diagnostic | validate the FPU control word |
Details¶
| Detail | VAs | Responsibility |
|---|---|---|
| (general) | 192 | Logging, assertions, and diagnostic output. |
Coverage¶
All 192 classified VAs are in the MW4Mercs.exe index, which lists role, size, source, and evidence.