11 KiB
source/game/cheats.obj — MATCHING
Status: byte-exact. All fourteen functions, both data objects, the whole object.
Lane: claude/libcmt-stream · Object status in config/config.json: Matching
Toolchain: XDK 3911 CL.Exe 13.00.9254.1 · Flags: the game set, /O2 /Oy- /DDEBUG /Dxbox
Start state: 2 of 14 functions (cheats_dispose, cheats_dispose_from_old_map — both
empty bodies), 2 of 1420 bytes. Everything else was a skeleton.
| symbol | target | ours | exact |
|---|---|---|---|
_cheats_initialize |
32 B / 2 rel | 32 B / 2 rel | yes |
_cheats_dispose |
16 B / 0 rel | 16 B / 0 rel | yes |
_cheats_dispose_from_old_map |
16 B / 0 rel | 16 B / 0 rel | yes |
_cheats_update |
176 B / 8 rel | 176 B / 8 rel | yes |
_cheats_load |
144 B / 10 rel | 144 B / 10 rel | yes |
_cheat_active_camouflage_local_player |
96 B / 4 rel | 96 B / 4 rel | yes |
_code_00094e30 |
96 B / 4 rel | 96 B / 4 rel | yes |
_cheats_initialize_for_new_map |
16 B / 1 rel | 16 B / 1 rel | yes |
_cheat_teleport_to_camera |
176 B / 13 rel | 176 B / 13 rel | yes |
_cheat_active_camouflage |
80 B / 4 rel | 80 B / 4 rel | yes |
_code_00094fa0 |
336 B / 14 rel | 336 B / 14 rel | yes |
_cheat_all_weapons |
208 B / 9 rel | 208 B / 9 rel | yes |
_cheat_all_powerups |
80 B / 4 rel | 80 B / 4 rel | yes |
_cheat_all_vehicles |
80 B / 4 rel | 80 B / 4 rel | yes |
all 5 .rdata strings + __real@3ec90fdb |
— | — | yes |
_bss_0043d808 |
3201 B / 0 rel | 3201 B / 0 rel | yes |
section_infos_equal passes for every one. A whole-object sweep pairs all 21
target sections by owner symbol and finds 21 equal, 0 differing.
Our object additionally emits eight COMDATs that the original link credited to
other objects, so csplit left them undefined here: the __inline out-of-line
bodies _sine / _cosine / _arctangent from real_math.h, the shared literals
"r" and "result", and __real@40c90fdb / __real@3fc00000 / __real@3f4ccccd.
That is ordinary COMDAT attribution, not a difference in this object's content.
1. Source order came free from the section table
The object is compiled with function-level COMDATs, so .text section order is
source order. That fixed the file layout before a line was written:
cheats_initialize, cheats_dispose, cheats_dispose_from_old_map, cheats_update,
cheats_load, cheat_active_camouflage_local_player, code_00094e30,
cheats_initialize_for_new_map, cheat_teleport_to_camera, cheat_active_camouflage,
code_00094fa0, cheat_all_weapons, cheat_all_powerups, cheat_all_vehicles
The two statics are interleaved with the public functions rather than collected in a trailing "private code" block — and in exactly that order every callee precedes its callers, so the file needs no forward prototypes at all. That self-consistency is good evidence the recovered order is the original one.
2. The headers already had everything
No invented structures were needed. Every offset in the disassembly was confirmed
by compiling an offsetof probe and reading the resulting .data:
| disassembly | header | value |
|---|---|---|
[eax+0x1b4] |
unit_datum.unit.flags |
0x1b4 |
[eax+0x32c] |
unit_datum.unit.active_camouflage |
0x32c |
[eax+0xcc] |
unit_datum.object.parent_object_index |
0xcc |
[esi+0x34] |
player_datum.unit_index |
0x34 |
[esi+2] |
player_datum.local_player_index |
0x2 |
push 3 |
_object_mask_unit |
3 |
[edi+0x10] |
observer_result.location.cluster_index |
0x10 |
[ebp-8] of a 16-byte local |
data_iterator.datum_index |
0x8 |
globals+0x14c/+0x158/+0x164 |
game_globals.weapon_list / .cheat_powerups / .multiplayer_information |
— |
element 0xa0, block at +0x20 |
game_globals_multiplayer_information.vehicles |
0x20 |
stride 0x10, tag at +0xc |
tag_reference.index |
0xc |
The gamepad decoding is what makes the object legible: NUMBER_OF_GAMEPAD_ANALOG_BUTTONS
is 8, so gamepad+0x10 is buttons[0] and the guard at [eax+0x1d] is
buttons[_gamepad_binary_button_back]. There are NUMBER_OF_GAMEPAD_BUTTONS = 16
cheats, one per button — which is also why cheats_load rejects entries 13 and 12
with "Cannot execute cheats attached to the back or start button".
3. Source-shape findings
cheatis not defined here._cheatis an undefined external in the target; the split image gives it tosource/linker_common.obj. The old skeleton definedstruct cheat_globals cheat;, which was wrong. (No link rule exists inbuild.ninja, so removing it affects nothing else.)- Index the array at every use, do not cache it in a
char *. This was the single highest-value finding and it fixed two functions:cheats_update— with a cachedchar *cheat_string, MSVC created the two induction variables in the opposite order, emittinglea edi,[eax+0x10]beforemov esi,bssandadd esi,0xc8beforeinc edi. Referencingbss_0043d808[button_index]directly at each of its four uses flips both.cheats_load— the cached pointer producedadd esi, <bss>where January haslea esi,[esi+<bss>]. Eleven rewrites of the address expression (&a[i][0],(char *)a+i*200,i*200+(char *)a,*(a+i),sizeof-derived bounds,const/register, …) all still producedadd. Only removing the local and indexing in place producedlea.
- Split the callee out of the argument.
unit_get(player_get(i)->unit_index)as one expression pushes_object_mask_unitbefore callingdatum_get, and cleans each call separately. January pushes it after and does a singleadd esp,0x10. Two statements —player = player_get(i);thenunit_get(player->unit_index)— reproduce it. NUMBEROFmakes a bound test unsigned.cheat_all_weaponscomparescmp si,0x10 / jae. A plainshort < 16gives signedjge;sizeofyieldssize_t, soreference_count<NUMBEROF(references)converts the comparison to unsigned whilereferences[reference_count]keeps the sign-extendingmovsx.code_00094e30holdsNONEin a local. January keeps-1inesi(or esi,0xffffffff,mov eax,esion the not-found path) and pays apush/pop esifor it. Writingreturn NONE;at the tail materialises the constant inline instead. Along player_index = NONE;with abreakand a singlereturn player_index;produces January's register form, +16 bytes and all.- The
.bssreally is 3201 bytes.csmemset(…, 0xc80)pins the array atchar[16][200]= 3200, so the object owns one further byte. It has to be explicitly zero-initialised and declared after the array: an uninitialisedbooleanbecomes COMMON and is dropped (3200), and putting it first pads the array to 4 (3208). Nothing anywhere in the image references its address (0x0043E488), so it is a dead file-local flag —static boolean bss_0043e488 = FALSE;.This was checked, not assumed:
source/memory/crc.c(1025) andsource/main/console.c(2501) are existingMatchingobjects whose odd.bsssizes rebuild exactly from real source, so csplit is not padding these.console.cuses the same idiom — a large object followed by a one-byte initialisedboolean. console_printftakesboolean clearfirst, not a colour:console_printf(FALSE, cheat_string).- The assert names its variable.
#exprstringification means the target's"result"literal fixes the local name inmatch_assert("c:\\halo\\SOURCE\\game\\cheats.c", 256, result).
4. code_00094fa0 — the spawn ring
The 336-byte static went exact on the first build. It arranges reference_count
objects on an arc in front of the player:
real angle_separation = CEILING(2*_pi/reference_count, _pi/8);
real angle = arctangent(forward.i, forward.j)+
(reference_index-reference_count/2)*angle_separation;
2*_pi and _pi/8 fold to exactly __real@40c90fdb and __real@3ec90fdb because
_pi is already (real)M_PI; a double spelling would have emitted qword
constants. CEILING from cseries.h is what produces the
fcom / fnstsw / test ah,0x41 clamp, and /Oi (implied by /O2) turns
arctangent / cosine / sine into fpatan / fcos / fsin — no __ftol
appears anywhere in this object, so the /QIfist problem that parks
player_rumble does not arise here.
The unused struct unit_datum *unit = unit_get(player->unit_index); is real: the
call is emitted with its result discarded, and player->unit_index is re-loaded
for the two calls that follow.
5. Blind alleys worth not repeating
- Arch tuning is not the
lea/addlever./G6,/G7and/GBare all identical to the default here (12/14 at the time);/G5drops it to 6/14. - Compiler variance is not a factor. Consistent with this lane's earlier finding across all three local VC7 suites.
- An early "inline the array" attempt on
cheats_loadlooked like a failure — but only because the shell heredoc had mangled"\r\n\t;"into real control characters, which renamed the string COMDAT.section_infos_equalcompares relocation symbol names, so it reported a mismatch for an unrelated reason. The same trap explains why the array had to be non-staticand namedbss_0043d808: astaticgets storage class STATIC, which defeats_defined_noncode_destinationand changes the relocation descriptor.
6. Current-campaign independent revalidation (2026-08-13)
The completed source was transplanted onto campaign commit a489a651 and
rebuilt from scratch through Ninja with XDK 3911 CL.Exe 13.00.9254.1 and the
configured /O2 /Oy- /DDEBUG /Dxbox flags. This supersedes the historical
note about Ninja being unable to launch the compiler in the older worktree.
The hardened comparator again reports all fourteen functions exact. It also
reports exact size, normalized payload, and relocation identity/addend for all
six target-owned read-only sections and the full 3,201-byte _bss_0043d808
section. In plain English: the executable instructions, strings, floating
constant, zero-initialized storage, calls, and global references all reproduce
the January object; this is not an objdiff-percentage or local-label credit.
The full 466-object halobetacache_build completed successfully after the
transplant. Candidate-only inline COMDATs remain the ordinary csplit
attribution described above; every target-owned section has an exact partner.
House/Berth audit
- Function parameters are declared one per line. Every no-argument function
uses
voidon its own line, and everyvoidfunction ends inreturn;. - Control flow uses one final return wherever January's code permits it.
- Player, unit, object, tag, and tag-block access uses the typed project interfaces and macros; no raw address-offset implementation was retained.
- No assembly,
volatile, forced inline, undefined behavior, byte patching, gratuitous alignment directive, compiler-flag exception, or comparator relaxation is present. - The one-byte dead flag after the cheat-string array is retained because its existence and placement are proven by the target BSS size. It is documented as dead state rather than assigned an invented semantic name.