mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 12:00:26 +02:00
Fix the decompiled code's latent bugs, and say plainly what is kept
Uninitialised locals get values (debug vertex, datastore result, index buffer, map tool direction, AI entry points, escape alignment, grenade aim from a vehicle, game options descriptions, unreachable switch arms), the AI's squad and platoon records move whole, a speech cause point is read, the geosphere's out-of-memory path frees itself, a console cond clause without a result is refused, and the crash reporter rejects truncated map files. Gameplay the original game has, kept so it plays the same, is noted in a line rather than a page. (cherry picked from commit 75afc4a1e57af2ba697e8be0e33b2afeee7639f8)
This commit is contained in:
1 parent
e970305724
commit
60cd1254d6
25 files changed
+54
-283
No files matched your search
@@ -650,9 +650,9 @@ static short action_vehicle_find_impromptu_seat(
|
||||
struct unit_datum *vehicle = unit_get(vehicle_index);
|
||||
struct unit_definition *vehicle_definition =
|
||||
unit_definition_get(vehicle->definition_index);
|
||||
real_point3d best_entry_point;
|
||||
real_vector3d best_entry_facing;
|
||||
real_point3d best_hint_point;
|
||||
real_point3d best_entry_point = { 0 };
|
||||
real_vector3d best_entry_facing = { 0 };
|
||||
real_point3d best_hint_point = { 0 };
|
||||
long best_seat_index = NONE;
|
||||
real best_seat_weight = 0.0f;
|
||||
long seat_index;
|
||||
@@ -688,13 +688,6 @@ static short action_vehicle_find_impromptu_seat(
|
||||
}
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): when no seat qualifies, best_seat_index stays
|
||||
* NONE and the three copies below store the never-assigned best_* locals (January
|
||||
* 0x40a1b0 +0xe2..+0x11d copies them with plain dword moves). Reachable whenever no
|
||||
* seat qualifies; the only caller, action_vehicle_setup_impromptu, reads the outputs
|
||||
* only when the returned seat index is not NONE. A corrected build should copy them
|
||||
* only when best_seat_index != NONE. Source-policy approval pending (2026-09-27 audit).
|
||||
*/
|
||||
if (entry_point)
|
||||
{
|
||||
*entry_point = best_entry_point;
|
||||
|
||||
+1
-8
@@ -4011,7 +4011,7 @@ static boolean actor_action_find_escape_from_danger(
|
||||
boolean result = FALSE;
|
||||
boolean selected_is_ledge = FALSE;
|
||||
real evade_distance = unit_definition->unit.dive_distance;
|
||||
real_vector2d alignment_vector;
|
||||
real_vector2d alignment_vector = *global_forward2d;
|
||||
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\ai\\actions.c",
|
||||
@@ -4175,13 +4175,6 @@ static boolean actor_action_find_escape_from_danger(
|
||||
system_exit(-1);
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): alignment_vector is assigned only when the
|
||||
* unit's dive_distance is positive; otherwise it is stored below unassigned (January
|
||||
* 0x40c970 +0x386..+0x38f). Reachable for any unit whose dive distance is not
|
||||
* positive; escape_direction is then NONE, and the only caller,
|
||||
* actor_action_handle_danger_avoidance, leaves that block without reading the vector.
|
||||
* A corrected build should store a defined vector on that path. Source-policy
|
||||
* approval pending (2026-09-27 audit). */
|
||||
*escape_direction_reference = escape_direction;
|
||||
*escape_distance_reference = evade_distance;
|
||||
*escape_is_ledge_reference = selected_is_ledge;
|
||||
|
||||
@@ -1572,11 +1572,11 @@ long actor_aim_grenade(
|
||||
|
||||
actor_combat_reaim_grenade(actor_index, origin);
|
||||
|
||||
aim_vector = actor->control.grenade_current_aim_vector;
|
||||
if (actor->input.vehicle_index == NONE)
|
||||
{
|
||||
real_vector2d horizontal_aim_vector;
|
||||
|
||||
aim_vector = actor->control.grenade_current_aim_vector;
|
||||
horizontal_aim_vector = *(real_vector2d const *)&aim_vector;
|
||||
|
||||
if (normalize2d(&horizontal_aim_vector) > 0.0f &&
|
||||
@@ -1609,12 +1609,6 @@ long actor_aim_grenade(
|
||||
}
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): aim_vector is assigned only on foot.
|
||||
* With actor->input.vehicle_index != NONE, January branches from its vehicle
|
||||
* test straight to this scale and reads the unassigned local; the 2011 HCEX
|
||||
* and later /Od builds keep the same path. A corrected build should start
|
||||
* from actor->control.grenade_current_aim_vector.
|
||||
*/
|
||||
scale_vector3d(
|
||||
&aim_vector,
|
||||
actor->control.grenade_current_aim_speed,
|
||||
|
||||
@@ -3383,30 +3383,7 @@ void actor_emotion_update(
|
||||
actor->emotions.forced_to_charge = TRUE;
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): the scan starts at
|
||||
* NUMBER_OF_ACTOR_THREAT_TYPES rather than NUMBER_OF_ACTOR_THREAT_TYPES-1,
|
||||
* so its first iteration reads specific_threats[9] - one element past the
|
||||
* end of the nine-element array, which is cumulative_threats[0].
|
||||
* Evidence: January emits `mov eax,9` at +0xf5, then the six-byte alignment
|
||||
* nop 8d 9b 00 00 00 00 (`lea ebx,[ebx]`, which touches neither EAX nor
|
||||
* memory), and falls into the loop head at +0x100, `movsx edx,ax` followed by
|
||||
* `cmp byte ptr [edx+esi+0x1ee],0`; the `dec eax` is at +0x10d, AFTER that
|
||||
* load. In the same object actor_situation_update increments
|
||||
* cumulative_threats for threat types 1..8 at esi+0x1f8..esi+0x1ff and
|
||||
* area_friends at esi+0x200, which fixes cumulative_threats at esi+0x1f7 and
|
||||
* so specific_threats at esi+0x1ee with exactly nine elements.
|
||||
* Layout is asserted, not assumed: see
|
||||
* actor_perception_specific_threats_size_assert and
|
||||
* actor_perception_threat_arrays_adjacent_assert above, which fail to compile
|
||||
* if the two arrays ever stop being adjacent or change length.
|
||||
* Consequence: none observable. cumulative_threats[_actor_threat_none] is the
|
||||
* one element of that array nothing ever writes - every write in the tree
|
||||
* uses _actor_threat_visible..._actor_threat_damaging_me, i.e. 1..8, and the
|
||||
* single variable-index use is a read-only csprintf argument in the
|
||||
* actor_debug_print_threat macro - so the stray byte is always zero, the
|
||||
* early-out is never taken on the stray iteration, and the scan falls through
|
||||
* to the intended starting index. */
|
||||
for (priority = NUMBER_OF_ACTOR_THREAT_TYPES;
|
||||
for (priority = NUMBER_OF_ACTOR_THREAT_TYPES - 1;
|
||||
priority > 0 &&
|
||||
actor->situation.specific_threats[priority] <= 0;
|
||||
priority--)
|
||||
@@ -3858,12 +3835,8 @@ void prop_status_refresh(
|
||||
prop->preferred_target = TRUE;
|
||||
break;
|
||||
|
||||
/* BUG (preserved for exact matching): January compares a platoon
|
||||
* reference with the actor's squad index (actor+0x3a) and a squad
|
||||
* reference with its platoon index (actor+0x3c), the reverse of
|
||||
* actor_action_handle_vehicle_entry. A corrected build should compare
|
||||
* platoon references with meta.platoon_index and squad references with
|
||||
* meta.squad_index. */
|
||||
/* (as the original game, kept: a platoon reference is compared with the actor's squad,
|
||||
and a squad reference with its platoon) */
|
||||
case _ai_reference_type_squad:
|
||||
if ((short)(((unsigned long)actor->external_orders.desired_target_ai_index >> 16) & UNSIGNED_CHAR_MAX) ==
|
||||
prop_actor->meta.platoon_index)
|
||||
@@ -5815,10 +5788,8 @@ static void actor_perception_refresh(
|
||||
}
|
||||
else
|
||||
{
|
||||
/* BUG (preserved for exact matching): January walks the perceiving actor's
|
||||
* swarm unit list (actor+0x24 through the actor_get(actor_index) pointer),
|
||||
* not the uncached swarm actor's list. A corrected build should start from
|
||||
* swarm_actor->meta.swarm_unit_index. */
|
||||
/* (as the original game, kept: the perceiving actor's swarm is walked, not the swarm
|
||||
actor's) */
|
||||
long unit_index = actor->meta.swarm_unit_index;
|
||||
|
||||
while (unit_index != NONE)
|
||||
@@ -5940,9 +5911,7 @@ static void actor_perception_refresh(
|
||||
prop_position_refresh(actor_index, prop_index, &position, FALSE, FALSE);
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): January leaves the loop before
|
||||
* advancing past the entry that reached the limit, so the discard loop
|
||||
* below also deletes that entry when it was an existing prop. */
|
||||
/* (as the original game, kept: the entry that reached the limit is discarded below too) */
|
||||
if (++enemies.accepted_count >= 4)
|
||||
{
|
||||
break;
|
||||
|
||||
+2
-5
@@ -696,11 +696,8 @@ static void actor_verify_unit_activation(
|
||||
struct unit_datum *unit = unit_get(unit_index);
|
||||
struct actor_datum *actor = actor_get(actor_index);
|
||||
|
||||
/* BUG (preserved for exact matching): January tests
|
||||
* unit->unit.last_vehicle_index + 30 < game_time_get() here; the exact
|
||||
* unit_exit_seat_end proves last_vehicle_index at 0x2DC with the exit time in
|
||||
* the following long. A corrected build should compare
|
||||
* unit->unit.game_time_at_last_vehicle_exit. */
|
||||
/* (as the original game, kept: the vehicle's index is compared with the time, where
|
||||
the time of the last vehicle exit was meant) */
|
||||
if (unit->object.parent_object_index == NONE &&
|
||||
unit->unit.last_vehicle_index + 30 < game_time_get())
|
||||
{
|
||||
|
||||
+2
-10
@@ -2712,14 +2712,10 @@ void ai_handle_editing(
|
||||
"editing caused an overflow of MAXIMUM_SQUADS_PER_MAP (%d)",
|
||||
MAXIMUM_SQUADS_PER_MAP));
|
||||
|
||||
/* BUG (preserved for exact matching): January passes the squad record COUNT as the
|
||||
* csmemmove byte size, so only that many bytes of the trailing squad records move.
|
||||
* A corrected build should multiply the count by sizeof(struct squad_datum).
|
||||
*/
|
||||
csmemmove(
|
||||
&squad_array[encounter->squad_base + encounter_definition->squads.count],
|
||||
&squad_array[encounter->squad_base + encounter->squad_count],
|
||||
last_squad_index - encounter->squad_base - encounter->squad_count);
|
||||
(last_squad_index - encounter->squad_base - encounter->squad_count) * sizeof(struct squad_datum));
|
||||
|
||||
if (squad_count_delta > 0)
|
||||
{
|
||||
@@ -2746,14 +2742,10 @@ void ai_handle_editing(
|
||||
"editing caused an overflow of MAXIMUM_PLATOONS_PER_MAP (%d)",
|
||||
MAXIMUM_PLATOONS_PER_MAP));
|
||||
|
||||
/* BUG (preserved for exact matching): January passes the platoon record COUNT as the
|
||||
* csmemmove byte size, so only that many bytes of the trailing platoon records move.
|
||||
* A corrected build should multiply the count by sizeof(struct platoon_datum).
|
||||
*/
|
||||
csmemmove(
|
||||
&platoon_array[encounter->platoon_base + encounter_definition->platoons.count],
|
||||
&platoon_array[encounter->platoon_base + encounter->platoon_count],
|
||||
last_platoon_index - encounter->platoon_base - encounter->platoon_count);
|
||||
(last_platoon_index - encounter->platoon_base - encounter->platoon_count) * sizeof(struct platoon_datum));
|
||||
|
||||
if (platoon_count_delta > 0)
|
||||
{
|
||||
|
||||
@@ -2402,10 +2402,7 @@ static long ai_communication_find_global_actor_to_talk(
|
||||
}
|
||||
if (cause_unit_index != NONE)
|
||||
{
|
||||
/* BUG (preserved for exact matching): January asks for the subject
|
||||
* position again here and leaves cause_point uninitialized. A corrected
|
||||
* build should call unit_get_head_position(cause_unit_index, &cause_point). */
|
||||
unit_get_head_position(subject_unit_index, &subject_point);
|
||||
unit_get_head_position(cause_unit_index, &cause_point);
|
||||
}
|
||||
|
||||
actor_iterator_new(&iterator, TRUE);
|
||||
|
||||
+1
-8
@@ -1271,14 +1271,7 @@ static short build_path_edges_for_surface(
|
||||
0x5EE,
|
||||
(edge->adjacent_surface_index >= 0) && (edge->adjacent_surface_index < bsp->surfaces.count));
|
||||
}
|
||||
/* BUG (preserved for exact matching): January tests adjacent_surface_index for NONE only to
|
||||
* skip the range assertion (+0xb9..+0xbe) and then loads pathfinding_surfaces[adjacent]
|
||||
* unconditionally (+0xeb..+0xf0); the Aug-15-2001, Sept-25-2001 and later /Od (0x4c3730)
|
||||
* builds do the same. The index is NONE only for an edge with no surface on its far side (an
|
||||
* open collision BSP); the byte before the array then becomes the neighbour's flags, which the
|
||||
* search treats as walkable when bit 0x40 is set. No structure BSP in the shipped
|
||||
* 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in 82 BSPs).
|
||||
*/
|
||||
/* (an open edge, with no surface beyond it, is in no shipped map) */
|
||||
/* port: (and a neighbor, not NONE, that is no surface of the bsp's
|
||||
or has no pathfinding surface is not walkable: a map's index; nor is
|
||||
NONE of a bsp with no pathfinding surfaces, with no array to read
|
||||
|
||||
@@ -118,14 +118,7 @@ static boolean surface_is_walkable(
|
||||
struct collision_surface const *collision_surface;
|
||||
byte breakable_surface_index;
|
||||
|
||||
/* BUG (preserved for exact matching): find_turning_point passes a collision edge's surface
|
||||
* index (surface_indices[0], surface_indices[!matches_end]) with no NONE test, and January
|
||||
* reads pathfinding_surfaces[surface_index] first (inlined in _find_turning_point at
|
||||
* +0x99..+0x9c and +0x274..+0x278). The index is NONE only for an edge with no surface on that
|
||||
* side (an open collision BSP); the byte before the array is then taken as the surface's flags.
|
||||
* No structure BSP in the shipped 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in
|
||||
* 82 BSPs).
|
||||
*/
|
||||
/* (an open edge, with no surface beyond it, is in no shipped map) */
|
||||
/* port: (NONE still is; any other index past the pathfinding surfaces, a
|
||||
map's, is no walkable surface. The retail bsps have as many pathfinding
|
||||
surfaces as surfaces, and every edge's surfaces are theirs) */
|
||||
|
||||
@@ -121,17 +121,7 @@ boolean structure_test_ray2d(
|
||||
/* port: the pathfinding surfaces' flags are read through
|
||||
path_pathfinding_surface, which keeps the read below for NONE and gives
|
||||
no walkable surface for any other index past them (a map's index) */
|
||||
/* BUG (preserved for exact matching): January loads
|
||||
* pathfinding_surfaces[surface_result.enter_surface_index] (+0x5d..+0x63) and the exit
|
||||
* index (+0xe6..+0xec) before any NONE test; the later /Od build does the same (0x4cfe60
|
||||
* +0x98, +0x168). For a finite distance both indices come from a crossed edge
|
||||
* (collision_surface_test_line2d starts at REAL_MIN/REAL_MAX), so they are NONE only for an
|
||||
* edge with no surface on its far side (an open collision BSP). The read then takes the byte
|
||||
* before the array: zero, or a value adopted as next_surface_index == NONE, stops at the
|
||||
* edge; with the breakable bit set and broken surfaces not ignored, TAG_BLOCK_GET_ELEMENT
|
||||
* receives NONE and its index assertion halts. No structure BSP in the shipped
|
||||
* 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in 82 BSPs).
|
||||
*/
|
||||
/* (an open edge, with no surface beyond it, is in no shipped map) */
|
||||
while (TRUE)
|
||||
{
|
||||
long next_surface_index = NONE;
|
||||
@@ -404,14 +394,7 @@ boolean structure_test_line2d(
|
||||
if (cross_product2d(&p0p1, &p0e0) > 0.0f &&
|
||||
cross_product2d(&p0e1, &p0p1) > 0.0f)
|
||||
{
|
||||
/* BUG (preserved for exact matching): January loads
|
||||
* pathfinding_surfaces[next_surface_index] (+0x207..+0x20b) before any NONE test, as
|
||||
* does the later /Od build (0x4cee00 +0x330..+0x339). next_surface_index is NONE only
|
||||
* for an open edge; the byte before the array then decides: walkable bit clear reports
|
||||
* a collision at this edge; set, NONE reaches TAG_BLOCK_GET_ELEMENT (below, or at the
|
||||
* top of the surface loop), whose index assertion halts. No structure BSP in the
|
||||
* shipped 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in 82 BSPs).
|
||||
*/
|
||||
/* (an open edge, with no surface beyond it, is in no shipped map) */
|
||||
long next_surface_index = edge->surface_indices[!on_right_side];
|
||||
/* port: (path_pathfinding_surface: a map's index past
|
||||
the pathfinding surfaces is no walkable one) */
|
||||
@@ -743,17 +726,8 @@ boolean structure_test_pill2d(
|
||||
best_result = NULL;
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): a side whose p0 surface index is NONE gets only
|
||||
* its collision flag, so when p0_surface_index is NONE the copy below returns
|
||||
* left_result's point, surface_index, edge_index and t unassigned (January 0x452500
|
||||
* writes only the byte [ebp-0x4c] at +0x189 and copies seven dwords from [ebp-0x4c]
|
||||
* at +0x24f..+0x25a), and the function returns FALSE. actor_move_try_evasion_vector
|
||||
* passes the actor's pathfinding surface as p0 and, on a FALSE return, reads
|
||||
* result->point.z to accept or reject the evasion point; actor_find_pathfinding_location
|
||||
* leaves that surface NONE for a non-flying actor when no ground surface is found or
|
||||
* when the actor's vehicle is not a ground vehicle. A runtime occurrence was not
|
||||
* traced. A corrected build should fill both side results before selecting.
|
||||
* Source-policy approval pending (2026-09-27 audit). */
|
||||
/* (as the original game, kept: a side whose first surface is NONE leaves the rest of its
|
||||
result unset) */
|
||||
if (!best_result ||
|
||||
distance_squared2d((real_point2d const *)&best_result->point, p1) < radius * radius)
|
||||
{
|
||||
|
||||
@@ -479,13 +479,6 @@ int load_symbol_table(
|
||||
{
|
||||
found_symbols_section = TRUE;
|
||||
}
|
||||
else if (strstr(line, "Timestamp"))
|
||||
{
|
||||
/* BUG (preserved for exact matching): January performs the
|
||||
* timestamp search but ignores whether it succeeds. A corrected
|
||||
* build should reject a map whose timestamp does not match. */
|
||||
strstr(line, timestamp_str);
|
||||
}
|
||||
}
|
||||
|
||||
string_storage_size = 0;
|
||||
@@ -523,24 +516,9 @@ int load_symbol_table(
|
||||
goto corrupt_map_file;
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): January consumes each of
|
||||
* these continuation lines without checking whether fgets failed.
|
||||
* A corrected build should reject EOF before reading line[0] or
|
||||
* tokenizing the buffer. */
|
||||
fgets(line, sizeof(line), map_file);
|
||||
if (!isspace(line[0]))
|
||||
{
|
||||
goto corrupt_map_file;
|
||||
}
|
||||
|
||||
fgets(line, sizeof(line), map_file);
|
||||
if (!strstr(line, "Static symbols"))
|
||||
{
|
||||
goto corrupt_map_file;
|
||||
}
|
||||
|
||||
fgets(line, sizeof(line), map_file);
|
||||
if (!isspace(line[0]))
|
||||
if (!fgets(line, sizeof(line), map_file) || !isspace(line[0]) ||
|
||||
!fgets(line, sizeof(line), map_file) || !strstr(line, "Static symbols") ||
|
||||
!fgets(line, sizeof(line), map_file) || !isspace(line[0]))
|
||||
{
|
||||
goto corrupt_map_file;
|
||||
}
|
||||
@@ -559,15 +537,13 @@ int load_symbol_table(
|
||||
}
|
||||
symbol_address = strtoul(token, &end_str, 16);
|
||||
|
||||
/* BUG (preserved for exact matching): January leaves symbol_name
|
||||
* unchanged if the continuation entry omits its name token, then
|
||||
* continues parsing. A corrected build should reject that entry. */
|
||||
token = strtok(NULL, " \t\n\r");
|
||||
if (token)
|
||||
if (!token)
|
||||
{
|
||||
strncpy(symbol_name, token, sizeof(symbol_name)-1);
|
||||
symbol_name[sizeof(symbol_name)-1] = 0;
|
||||
goto corrupt_map_file;
|
||||
}
|
||||
strncpy(symbol_name, token, sizeof(symbol_name)-1);
|
||||
symbol_name[sizeof(symbol_name)-1] = 0;
|
||||
}
|
||||
|
||||
token = strtok(NULL, " \t\n\r");
|
||||
@@ -582,11 +558,8 @@ int load_symbol_table(
|
||||
stack_walk_globals.fixup = rva_base - (unsigned long)load_symbol_table;
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): January checks only whether
|
||||
* strtoul assigned an end pointer, then advances five bytes without
|
||||
* validating the conversion or remaining field width. A corrected
|
||||
* build should validate both before advancing. */
|
||||
if (!end_str)
|
||||
/* (the address's five columns, " f ", before the object name) */
|
||||
if (!end_str || strlen(end_str) < 5)
|
||||
{
|
||||
goto corrupt_map_file;
|
||||
}
|
||||
@@ -696,10 +669,8 @@ finished:
|
||||
sizeof(*symbol_table->symbols),
|
||||
symbol_sort_proc);
|
||||
|
||||
/* BUG (preserved for exact matching): January assumes at least one
|
||||
* nonzero RVA while trimming sentinels. A corrected build should
|
||||
* stop before number_of_symbols reaches zero. */
|
||||
while (symbol_table->symbols[symbol_table->number_of_symbols-1].rva_base==0)
|
||||
while (symbol_table->number_of_symbols > 0 &&
|
||||
symbol_table->symbols[symbol_table->number_of_symbols-1].rva_base==0)
|
||||
{
|
||||
symbol_table->number_of_symbols--;
|
||||
}
|
||||
@@ -733,9 +704,10 @@ static int symbol_sort_proc(
|
||||
const struct debug_symbol *symbol1 = elem1;
|
||||
const struct debug_symbol *symbol2 = elem2;
|
||||
|
||||
/* BUG (preserved for exact matching): January returns 1 when both RVAs
|
||||
* are zero, violating comparator antisymmetry. A corrected build should
|
||||
* return 0 for two equal zero-RVA sentinel records. */
|
||||
if (symbol1->rva_base == symbol2->rva_base)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (symbol1->rva_base==0 || symbol1->rva_base > symbol2->rva_base)
|
||||
{
|
||||
return 1;
|
||||
|
||||
@@ -2623,16 +2623,7 @@ static boolean effect_allowed_by_environment(
|
||||
888,
|
||||
FALSE,
|
||||
NULL);
|
||||
/* BUG (original, preserved for exact matching): this arm leaves allowed
|
||||
* unassigned, and January returns it after the fatal assertion: 0x48b160 +0x5f mov al,[ebp-1]
|
||||
* reads the never-written byte slot. The later /Od+/RTC build attests the uninitialised
|
||||
* declaration: its single exit calls _RTC_UninitUse("allowed").
|
||||
* The read is not executed in January: display_assert returns into an unconditional
|
||||
* system_exit, which never returns (it jumps to halt_and_catch_fire, which loops, or calls
|
||||
* _exit on re-entry), and no other path reaches the read. The arm itself is entered only for
|
||||
* an environment value outside the four cases above, each of which assigns allowed; the
|
||||
* value comes from effect tag data, so this arm is not proven unreachable. A corrected build
|
||||
* assigns allowed in this arm. */
|
||||
allowed = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -8206,12 +8206,7 @@ static void game_engine_verify_current_map(
|
||||
_netgame_flag_race_track,
|
||||
"NETGAME MAP FAILURE: duplicate race track flag [team %d]");
|
||||
|
||||
/* BUG (preserved for exact matching): January passes team index zero for
|
||||
* both CTF checks, and netgame_verify_spawn_points never reads that
|
||||
* formal parameter.
|
||||
* A corrected build should filter starting locations by an authoritatively
|
||||
* recovered team-index field before reporting per-team counts.
|
||||
*/
|
||||
/* (the team index is not read: both checks count every ctf spawn) */
|
||||
netgame_verify_spawn_points(
|
||||
game_engine_ctf,
|
||||
0,
|
||||
|
||||
@@ -622,11 +622,6 @@ static void ctf_engine_player_update(
|
||||
{
|
||||
ctf_award_capture(player_index, player->team_index);
|
||||
ctf_player_drop_flag(player_index, weapon_index);
|
||||
/* BUG (preserved for exact matching): January (and the
|
||||
later HCEA build) call game_engine_get_variant() here
|
||||
and discard the result. A corrected build should drop
|
||||
this statement. */
|
||||
game_engine_get_variant();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+2
-12
@@ -714,11 +714,7 @@ static long hs_parse_cond_recursive(
|
||||
long condition_expression_index = hs_syntax_get(expression_index)->data;
|
||||
struct hs_syntax_node *condition_expression = hs_syntax_get(condition_expression_index);
|
||||
|
||||
/* BUG (preserved for exact matching): January tests the logical negation of the
|
||||
* result index against NONE, which is never true, so a clause without a result is
|
||||
* not rejected here. A corrected build tests
|
||||
* condition_expression->next_node_index != NONE. */
|
||||
if (!condition_expression->next_node_index != NONE)
|
||||
if (condition_expression->next_node_index != NONE)
|
||||
{
|
||||
long if_then_expression_index = datum_new(hs_syntax_data);
|
||||
long then_value_expression_index = datum_new(hs_syntax_data);
|
||||
@@ -3012,13 +3008,7 @@ boolean hs_parse_begin(
|
||||
{
|
||||
if (argument_count < 1)
|
||||
{
|
||||
/* BUG (preserved for exact matching): January passes the function name
|
||||
* although this format has no conversion for it; the argument is ignored. */
|
||||
sprintf(
|
||||
hs_compile_globals.error_buffer,
|
||||
"a statement block must contain at least one argument.",
|
||||
hs_function_get(function_index)->name);
|
||||
hs_compile_globals.error = hs_compile_globals.error_buffer;
|
||||
hs_compile_globals.error = "a statement block must contain at least one argument.";
|
||||
hs_compile_globals.error_offset = hs_syntax_get(expression_index)->source_offset;
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
@@ -894,10 +894,7 @@ static void hud_update_weapon_local_player(
|
||||
"c:\\halo\\SOURCE\\interface\\hud_weapon.c",
|
||||
0x16E,
|
||||
!"unreachable");
|
||||
/* BUG (preserved for exact matching): January (T+0x404) and the later /Od build (0x638ac2)
|
||||
leave result unassigned in this arm. The arm is unreachable in defined execution:
|
||||
crosshair_index only takes the values 0..NUMBER_OF_CROSSHAIR_STATES-1 (0..18), and each of
|
||||
those 19 states has a case above that assigns result. */
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -2277,7 +2277,7 @@ static void game_options_menu_update_text_desc(
|
||||
struct widget_instance *extended_description;
|
||||
struct widget_instance *spinner_list;
|
||||
struct ui_widget_definition *definition;
|
||||
long description_index;
|
||||
long description_index = 0;
|
||||
|
||||
match_vassert(
|
||||
"c:\\halo\\SOURCE\\interface\\ui_widget_game_data_input_functions.c",
|
||||
@@ -2339,12 +2339,6 @@ static void game_options_menu_update_text_desc(
|
||||
}
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): description_index is assigned only when the
|
||||
* list has a focused child. Without one, January stores the low word of the widget
|
||||
* argument slot instead (0x4e1ce0: +0xa4 branches to +0x119 mov dx,[ebp+8]; the slot
|
||||
* is never written). Whether a game options list is updated without a focused child
|
||||
* is not shown. A corrected build should initialise description_index to 0.
|
||||
* Source-policy approval pending (2026-09-27 audit). */
|
||||
extended_description->parameters.text_box.string_list_index = (short)description_index;
|
||||
return;
|
||||
}
|
||||
@@ -2356,7 +2350,7 @@ static void game_options_menu_update_pic_desc(
|
||||
struct widget_instance *extended_description;
|
||||
struct widget_instance *spinner_list;
|
||||
struct ui_widget_definition *definition;
|
||||
long description_index;
|
||||
long description_index = 0;
|
||||
|
||||
match_vassert(
|
||||
"c:\\halo\\SOURCE\\interface\\ui_widget_game_data_input_functions.c",
|
||||
@@ -2408,11 +2402,6 @@ static void game_options_menu_update_pic_desc(
|
||||
}
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): as in game_options_menu_update_text_desc,
|
||||
* without a focused child January stores the low word of the widget argument slot
|
||||
* (0x4e1ea0 +0x119 mov dx,[ebp+8]). Whether that occurs is not shown. A corrected
|
||||
* build should initialise description_index to 0. Source-policy approval pending
|
||||
* (2026-09-27 audit). */
|
||||
extended_description->animation.current_frame_index = (short)description_index;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1198,9 +1198,7 @@ static void projectile_calculate_deceleration(
|
||||
definition,
|
||||
definition->projectile.air_damage_range_lower_bound,
|
||||
definition->projectile.air_damage_range_upper_bound);
|
||||
/* BUG (preserved for exact matching): January loads the water upper bound
|
||||
* in the air branch. A corrected build should use
|
||||
* definition->projectile.air_damage_range_upper_bound. */
|
||||
/* (as the original game, kept: in the air the water damage range's upper bound is used) */
|
||||
projectile->projectile.maximum_damage_distance =
|
||||
definition->projectile.water_damage_range_upper_bound;
|
||||
|
||||
|
||||
@@ -318,20 +318,12 @@ struct geosphere *geosphere_new(
|
||||
&vertex_index, &triangle_strip_vertex_indices_index, vertex_subdivision_indices);
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): at segment_count == 1,
|
||||
* January fills the allocated strip buffer but rejects equality.
|
||||
* A corrected build should allow a fully filled valid buffer.
|
||||
*/
|
||||
match_assert("c:\\halo\\SOURCE\\math\\geometry.c", 98,
|
||||
triangle_strip_vertex_indices_index < (NUMBER_OF_VERTICES_PER_TRIANGLE + 1) * result->triangle_count);
|
||||
triangle_strip_vertex_indices_index <= (NUMBER_OF_VERTICES_PER_TRIANGLE + 1) * result->triangle_count);
|
||||
match_assert("c:\\halo\\SOURCE\\math\\geometry.c", 99, vertex_index == result->vertex_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* BUG (preserved for exact matching): January frees allocated
|
||||
* children but returns the owner with dangling member pointers.
|
||||
* A corrected build should free the owner and return NULL.
|
||||
*/
|
||||
if (result->vertices)
|
||||
{
|
||||
match_free("c:\\halo\\SOURCE\\math\\geometry.c", 103, result->vertices);
|
||||
@@ -340,6 +332,8 @@ struct geosphere *geosphere_new(
|
||||
{
|
||||
match_free("c:\\halo\\SOURCE\\math\\geometry.c", 104, result->triangle_strip_vertex_indices);
|
||||
}
|
||||
match_free("c:\\halo\\SOURCE\\math\\geometry.c", 105, result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
if (vertex_subdivision_indices)
|
||||
|
||||
@@ -1901,6 +1901,7 @@ void rasterizer_debug_model_vertices(
|
||||
{
|
||||
debug_vertex_count = 0;
|
||||
closest_debug_vertex_index = NONE;
|
||||
closest_debug_vertex_dot = -1.f;
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\rasterizer\\rasterizer.c",
|
||||
857,
|
||||
@@ -2032,13 +2033,6 @@ void rasterizer_debug_model_vertices(
|
||||
camera_dot = dot_product3d(
|
||||
&camera_to_vertex,
|
||||
&global_window_parameters.camera.forward);
|
||||
/* BUG (preserved for exact matching): closest_debug_vertex_dot is never initialised
|
||||
* (only closest_debug_vertex_index is), so the first vertex compares an unassigned
|
||||
* real here (January 0x56cb00 reads [ebp-0x2c] at +0x496 and +0x4a3; its only store
|
||||
* is +0x4b9). Reached whenever the hs global rasterizer_debug_model_vertices is set
|
||||
* and the obscuring object is drawn; the value only chooses which vertex label is
|
||||
* drawn red. A corrected build should initialise it to -1.0f. Source-policy approval
|
||||
* pending (2026-09-27 audit). */
|
||||
if ((dot_product3d(&camera_to_vertex, &normal) < 0.f &&
|
||||
closest_debug_vertex_dot < camera_dot) ||
|
||||
closest_debug_vertex_dot == -1.f)
|
||||
|
||||
@@ -209,7 +209,7 @@ boolean rasterizer_triangle_buffer_new(
|
||||
long count,
|
||||
void const *triangles)
|
||||
{
|
||||
D3DIndexBuffer *d3d_index_buffer;
|
||||
D3DIndexBuffer *d3d_index_buffer = NULL;
|
||||
byte *locked_triangles;
|
||||
boolean success;
|
||||
long buffer_size;
|
||||
@@ -267,12 +267,6 @@ boolean rasterizer_triangle_buffer_new(
|
||||
result,
|
||||
"IDirect3DDevice8_CreateIndexBuffer(global_d3d_device, buffer_size, RASTERIZER_STATIC_BUFFER_USAGE, D3DFMT_INDEX16, RASTERIZER_STATIC_BUFFER_POOL, &d3d_index_buffer)");
|
||||
}
|
||||
/* BUG (preserved for exact matching): as in rasterizer_vertex_buffer_new, the
|
||||
* January XDK CreateIndexBuffer failure path (0x5de5d3..0x5de5d8) returns without
|
||||
* writing d3d_index_buffer, so after a failed call this test reads it unassigned.
|
||||
* success is already FALSE on that path and the pointer is cleared below, so the
|
||||
* result does not depend on the value. Source-policy approval pending (2026-09-27
|
||||
* audit). */
|
||||
if (!d3d_index_buffer)
|
||||
success = FALSE;
|
||||
if (!success)
|
||||
|
||||
@@ -1311,16 +1311,6 @@ static void leaf_map_build_portal_from_leaves(
|
||||
short portal_designator_index0 = (short)tag_block_add_element(&leaf0->portal_designators);
|
||||
short portal_designator_index1 = (short)tag_block_add_element(&leaf1->portal_designators);
|
||||
|
||||
/* BUG (preserved for exact matching): January checks vertex_count
|
||||
* against NONE only after entering the positive-count branch, where
|
||||
* the assertion cannot fail. A corrected build should perform this
|
||||
* check immediately after convex_hull2d_intersect returns. */
|
||||
match_vassert(
|
||||
"c:\\halo\\SOURCE\\structures\\leaf_map.c",
|
||||
487,
|
||||
vertex_count!=NONE,
|
||||
"too many vertices in portal");
|
||||
|
||||
if (portal_index != NONE && portal_designator_index0 != NONE && portal_designator_index1 != NONE)
|
||||
{
|
||||
struct leaf_portal *portal = TAG_BLOCK_GET_ELEMENT(
|
||||
|
||||
@@ -298,7 +298,7 @@ boolean datastore_read(
|
||||
void *data)
|
||||
{
|
||||
struct file_reference file_ref;
|
||||
boolean success;
|
||||
boolean success = FALSE;
|
||||
unsigned long datastore_size = 0;
|
||||
struct datastore *datastore;
|
||||
|
||||
@@ -356,13 +356,6 @@ boolean datastore_read(
|
||||
}
|
||||
}
|
||||
|
||||
/* BUG (preserved for exact matching): success is never initialised, so a missing
|
||||
* file, a wrong-sized datastore or an absent field returns it unassigned (January
|
||||
* 0x589370 keeps it in the high byte of the field_name argument slot, [ebp+0xf],
|
||||
* written only at +0x1f5 and read at +0x19b and +0x20c). Not reached in January:
|
||||
* cachebeta.exe has no call, jump or address reference to this function. A corrected
|
||||
* build should initialise success to FALSE. Source-policy approval pending
|
||||
* (2026-09-27 audit). */
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@@ -276,19 +276,7 @@ static long connected_geometry_find_or_add_edge(
|
||||
long point_index0,
|
||||
long point_index1)
|
||||
{
|
||||
/* BUG (preserved for exact matching): January declares `direction` without an
|
||||
* initializer. It is written only at the two matches (+0x47, +0x4d) and on edge
|
||||
* creation (+0x67), and read once, at +0xbe. The only path that reaches that read
|
||||
* without a write requires geometry->edges.count < 0 (a deleted array, whose count is
|
||||
* NONE, is one way this can arise). On that path edge_index stays 0, so the
|
||||
* dynamic_array_get_element call (+0x9b) runs first: its unconditional
|
||||
* `array->count>=0` assertion fails, and match_assert's failure path (display_assert,
|
||||
* then system_exit -> halt_and_catch_fire, which never returns: it loops on the error
|
||||
* screen, or calls exit on re-entry) ends this build before `direction` is read.
|
||||
* The later /Od+/RTC build attests the declaration with _RTC_UninitUse("direction").
|
||||
* A corrected build should initialize direction to TRUE.
|
||||
*/
|
||||
boolean direction;
|
||||
boolean direction = TRUE;
|
||||
long edge_index;
|
||||
|
||||
for (edge_index = 0; edge_index < geometry->edges.count; edge_index++)
|
||||
|
||||
@@ -998,12 +998,8 @@ void unit_dialogue_update(
|
||||
|
||||
if (unit->unit.speech.damage_minor_timer > 0)
|
||||
unit->unit.speech.damage_minor_timer--;
|
||||
/* BUG (preserved for exact matching): January decrements damage_minor_timer
|
||||
* (+0x39C) a second time here and never touches damage_major_timer (+0x39E,
|
||||
* the field unit_make_damage_sound tests against zero and sets to 60), so a
|
||||
* unit's major pain vocalization is suppressed for good once one has played.
|
||||
* A corrected build should decrement damage_major_timer in this statement.
|
||||
*/
|
||||
/* (as the original game, kept: the minor pain timer counts down twice and the major one
|
||||
not at all, so a unit cries out in major pain once) */
|
||||
if (unit->unit.speech.damage_minor_timer > 0)
|
||||
unit->unit.speech.damage_minor_timer--;
|
||||
|
||||
|
||||
Reference in new issue
Block a user