macos: fix the pointer-truncation crash family, add PORT_ADDR_STRICT (D301-D308)

Census-driven: rebuilt the compiled set with -Wint-to-pointer-cast
-Wpointer-to-int-cast -Wint-conversion, giving 276 actionable sites, triaged
in three disjoint file groups. Group A (206 warnings) and most of C were
already-correct round trips; these are the real bugs.

- D301: chrCreateBloodStain() crashed on guard gunfire. Two defects —
  portN64ToHost(0) returned base+0 instead of NULL (defeating the decomp's
  `if (p != NULL)` failure checks), and Vertex.CollisionRelatedNode (a u32 vma
  kept 32-bit by PROMOTE32) was read with a bare cast.
- D302: six more truncations. Four are the "twin function" pattern — one of a
  near-duplicate solo/MP pair converted, the other missed. Includes bg.c:5213,
  where a truncated loop bound meant bgRoomCalcBB() never ran its body on
  arm64, leaving EVERY room in every level with an inverted bounding box.
  Silent, not a crash. Possible relevance to open D249.
- D303: texReadUncompressed() aligned its destination through u32. Latent —
  a probe over a full level boot showed the path is unreachable on PC
  (textures come from the pre-converted sidecars).
- D306: MSAA=8 produced a completely black screen. GL_MAX_SAMPLES is 4, and
  nothing clamped the request, so the renderbuffer was never allocated.
  video.c's own comment claimed MSAA "snaps down to the highest supported
  level"; it did not. Also fixed two index-vs-GL-name bugs in the same file.
- D308: the watch's Control page crashed. options.c cast a stack-local table
  pointer to (s32) before passing it as WatchContButtonPositions*, and
  watchRenderControllerOpaque forwarded the truncated value. Verified A/B
  against the user's exact repro — fault address byte-identical.
- D305: PORT_ADDR_STRICT, a debug gate (off by default, compiled out) that
  validates every u32->host conversion against the regions the port maps and
  reports offenders with a backtrace. Zero reports across 6 levels, 5 of them
  under full-control input.

Also records D307 (reticule jitter in aim mode) in the finding log as OPEN,
with the cause identified from live captures and no code change — an attempted
fix was reverted for not working.

Verified: clean build, 21/21 sweep, plus a targeted repro per fix.
This commit is contained in:
Dan Turner committed 2026-10-04 10:50:01 +01:00
1 parent 498be832c2
commit 3785dcad33
14 files changed
+269 -23

No files matched your search

+10
View File
@@ -73,6 +73,12 @@ else()
set(PORT_ADDR_BASE 0x0 CACHE STRING "Host base of the N64 address window")
endif()
# Debug gate: validate every u32 -> host-pointer conversion against the regions
# the port actually maps, and report offenders at the conversion site with a
# backtrace. Off by default (zero cost); enable with -DPORT_ADDR_STRICT=ON.
# Catches the D299-D303 truncated/never-rebased-pointer family early.
option(PORT_ADDR_STRICT "Validate N64->host address conversions at runtime" OFF)
include(TargetArch)
include(FindPkgConfig)
@@ -259,6 +265,10 @@ add_definitions(
-DPORT_ADDR_BASE=${PORT_ADDR_BASE}
${REGION_DEFS}
)
if(PORT_ADDR_STRICT)
add_definitions(-DPORT_ADDR_STRICT=1)
message(STATUS "PORT_ADDR_STRICT enabled: N64->host conversions are validated at runtime")
endif()
if(TARGET_IS_64BIT)
add_definitions(-DPLATFORM_64BIT=1)
endif()
+26
View File
@@ -187,6 +187,32 @@ runtime. `ge007.ini` is written under `data/` on first launch.
On macOS the binary is `./build-pc/ge007.aarch64` (Apple Silicon) or
`./build-pc/ge007.x86_64` (Intel).
### Debugging address bugs (`PORT_ADDR_STRICT`)
The port realises the N64 address space at a host base (`PORT_ADDR_BASE`,
non-zero only on arm64 macOS). The recurring failure mode is a 32-bit value
that is *not* a valid N64 address reaching `portN64ToHost()` — a truncated host
pointer, or one that was never re-based. The resulting fault usually happens
far from the cause, and sometimes there is no fault at all, just silent
corruption.
```sh
cmake -S . -B build-strict -DROMID=ntsc-final -DPORT_ADDR_STRICT=ON
cmake --build build-strict -j
```
Every conversion is then validated against the mapped regions and offenders are
reported at the conversion site with a backtrace naming the caller. It is
**off by default and compiled out entirely**, so normal builds pay nothing.
To prove the detector is live rather than merely silent:
```sh
GE_ADDRSTRICT_SELFTEST=1 ./build-strict/ge007.aarch64 -level_09
```
See findings D299–D305 for the bug family this exists to catch.
### Packaging a macOS app
```sh
+1
View File
@@ -130,6 +130,7 @@ Two classes:
| `GE_D96` | `src/game/chrprop.c:436,507` (cached `probe`) | D96 chrprop trace | dead (D96 closed) |
| `GE_D104` | `src/game/bg.c:654` (rate-limited, `d104c`) | D104 depth-clear trace | dead (D104 closed) |
| `GE_D71LOG` | `port/fast3d/gfx_pc.cpp:645` | D71 fast3d trace | dead (D71 closed) |
| `GE_ADDRSTRICT_SELFTEST` | `port/src/port_addr.c` (`portAddrStrictSelfTest`) | **Negative self-test for `PORT_ADDR_STRICT` (D305).** Feeds `portN64ToHost()` a deliberately-unmapped address (`0x30000000`) at boot so a run can prove the validator is live rather than merely silent. Requires a build configured with `-DPORT_ADDR_STRICT=ON`; a no-op otherwise. | **live** (D305) |
| `GE_D75` | `src/game/title.c` `sub_GAME_7F007F30` (capped 8) | D75 Bug 2 gun-barrel model probe: logs `chrModelInstance`/`gunModelInstance` ptr+obj+numMatrices, `render_pos`, `renderData.mtxlist`, `g_GfxMemPos`, `osVirtualToPhysical(render_pos)` | **LIVE** (D75 Bug 2 OPEN — see §F "D75 Bug 2 — RUNTIME PROBE") |
| `GE_D305` | `src/snd.c:420` (`sndHandleEvent` desync branch, `#ifdef PORT`) | D305: logs the sound-preemption desync state when it is detected (after the NULL-guard fix made the crash itself impossible) — confirms whether `Game.AllUnlocked` correlates or was coincidental, and whether it recurs. Inert unset. | **live** (D305 monitoring) |
| `GE_D309` | `src/game/chrai.c:810,3424`, `src/game/chraction.c:10139` (all `#ifdef PORT`) | D309 freeze probe: logs AI label-not-found restarts (`chrai.c`) and the run-to-bond persistent-list step-on path (`chraction.c`, capped 400 lines) — the diagnostics used to root-cause the elusive complete freeze (D309/D310). Inert unset. | **live** (D309/D310 fixed — kept for recurrence) |
+43 -1
View File
@@ -1222,11 +1222,44 @@ static int gfx_opengl_create_framebuffer() {
return i;
}
/* video.c documents MSAA as "snaps down to the highest supported level", but
* nothing ever queried the limit: gfx_msaa_level went to
* glRenderbufferStorageMultisample() verbatim, and a request above
* GL_MAX_SAMPLES leaves the renderbuffer unallocated -> incomplete FBO ->
* a completely BLACK screen (MSAA=8 on Apple Silicon, where GL_MAX_SAMPLES
* is 4). Clamp here, where the GL limit actually matters. */
static uint32_t gfx_opengl_clamp_msaa(uint32_t level)
{
static GLint max_samples = 0;
static uint32_t warned_level = 0;
if (level <= 1) {
return 1;
}
if (max_samples == 0) {
glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
if (max_samples < 1) {
max_samples = 1;
}
}
if ((GLint)level > max_samples) {
if (warned_level != level) {
sysLogPrintf(LOG_WARNING,
"MSAA: %u samples requested but GL_MAX_SAMPLES is %d; using %d",
level, (int)max_samples, (int)max_samples);
warned_level = level;
}
return (uint32_t)max_samples;
}
return level;
}
static void gfx_opengl_update_framebuffer_parameters(int fb_id, uint32_t width, uint32_t height, uint32_t msaa_level,
bool opengl_invert_y, bool render_target, bool has_depth_buffer,
bool can_extract_depth) {
Framebuffer& fb = framebuffers[fb_id];
msaa_level = gfx_opengl_clamp_msaa(msaa_level);
width = max(width, 1U);
height = max(height, 1U);
@@ -1340,7 +1373,12 @@ void gfx_opengl_resolve_msaa_color_buffer(int fb_id_target, int fb_id_source) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_src.fbo);
glBlitFramebuffer(0, 0, fb_src.width, fb_src.height, 0, 0, fb_dst.width, fb_dst.height, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
/* current_framebuffer is an INDEX into framebuffers[], not a GL name; fb 0 is the window's default framebuffer (name 0). */
/* Restore whatever was bound before the resolve. `current_framebuffer` is
* an INDEX into framebuffers[], NOT a GL framebuffer name -- binding it
* directly named an unrelated GL object (or nothing) and left the wrong
* target bound for the rest of the frame. With MSAA on that produced a
* completely black screen. Use the same fb_id==0 -> GL name 0 rule as
* gfx_opengl_bind_framebuffer above. */
glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer == 0 ? 0 : framebuffers[current_framebuffer].fbo);
glEnable(GL_SCISSOR_TEST);
}
@@ -1405,6 +1443,10 @@ void gfx_opengl_copy_framebuffer(int fb_dst, int fb_src, int left, int top, bool
glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
/* Same index-vs-name bug as the MSAA resolve above: for current_framebuffer
* == 0 this bound framebuffers[0].fbo, which is a generated but
* attachment-less FBO (incomplete on a strict GL implementation) -- the
* window's default framebuffer is GL name 0. */
glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer == 0 ? 0 : framebuffers[current_framebuffer].fbo);
glReadBuffer(GL_BACK);
+28 -1
View File
@@ -71,6 +71,17 @@ extern "C" {
extern uintptr_t g_portImageBase;
extern int g_portUseImageRel;
#ifdef PORT_ADDR_STRICT
/* Debug gate (-DPORT_ADDR_STRICT=1). Validates that a u32 being turned into a
* host pointer actually names one of the N64 regions the port maps. The whole
* D299/D300/D301/D302 bug family shares one signature -- a truncated or
* never-rebased address reaching portN64ToHost -- and it is almost always
* diagnosed from a fault somewhere far away, long after the bad value was
* created. This reports it AT THE CONVERSION, with a backtrace naming the
* caller. Compiled out entirely by default; costs one call when enabled. */
void portAddrStrictCheck(uint32_t a);
#endif
/* Reserve the window (macOS only) and record the image base. Must run before
* any of the port's fixed mappings and before large allocations. */
void portAddrInit(void);
@@ -79,9 +90,25 @@ void portAddrInit(void);
}
#endif
/* u32 N64/physical address -> live host pointer. */
/* u32 N64/physical address -> live host pointer.
*
* N64 address 0 maps to NULL, NOT to PORT_ADDR_BASE. Offset 0 is never a
* mapped address (the window's lowest live region is the cart at
* 0x1000_0000), so base+0 lands in the reserved PROT_NONE area and faults.
* More importantly, game code routinely signals failure by returning an N64
* address of 0 and then testing the converted pointer for NULL --
* `p = PORT_N64PTR(T, f()); if (p != NULL) *p = ...` (e.g.
* chrCreateBloodStain(), objDeform()). With base+0 that test passes and the
* very next store faults (D301). Returning NULL restores the decomp's
* intended semantics. */
static inline void *portN64ToHost(uint32_t a)
{
if (a == 0) {
return NULL;
}
#ifdef PORT_ADDR_STRICT
portAddrStrictCheck(a);
#endif
if (g_portUseImageRel && a >= 0x40000000u && a < 0x70000000u) {
return (void *)(g_portImageBase + (uintptr_t)(a - 0x40000000u));
}
+89
View File
@@ -12,10 +12,14 @@
*/
#include <stdint.h>
#include <stdlib.h>
#include "platform.h"
#include "system.h"
#include "port_addr.h"
#ifdef PORT_ADDR_STRICT
#include <execinfo.h>
#endif
#if defined(PLATFORM_MACOS) && defined(PLATFORM_ARM)
#include <mach/mach.h>
@@ -64,6 +68,87 @@ static void portAddrSelfTest(void)
ok ? "ALL PASS" : "FAILURES");
}
#ifdef PORT_ADDR_STRICT
/*
* Strict address validation (-DPORT_ADDR_STRICT=1, off by default).
*
* Every bug in the D299-D303 family reaches portN64ToHost() holding a value
* that is not a real N64 address -- either a host pointer truncated to 32 bits
* or an address that was never re-based. The fault then happens later, in
* unrelated code, which is what made these expensive to find. This checks the
* value against the regions the port actually maps and reports the bad ones
* at the point of conversion, naming the caller.
*
* Regions (see port/include/port_addr.h and port/src/dram.c):
* 0x10000000..0x1fffffff cart image + pcmodels/pccg sidecars
* 0x40000000..0x6fffffff image-relative encoding (exe globals)
* 0x70000000..0x707fffff DRAM V1
* 0x80000000..0x807fffff DRAM V2 (KSEG0 mirror of V1)
* 0xa0000000..0xbfffffff game-thread stacks
* Segmented DL addresses (0x00000000..0x0fffffff) never name host memory and
* are resolved by fast3d's seg_addr(), not here -- so they are reported too.
*/
static int portAddrRegionOk(uint32_t a)
{
if (a >= 0x10000000u && a <= 0x1fffffffu) return 1; /* cart + sidecars */
if (a >= 0x40000000u && a <= 0x6fffffffu) return 1; /* image-relative */
if (a >= 0x70000000u && a <= 0x707fffffu) return 1; /* DRAM V1 */
if (a >= 0x80000000u && a <= 0x807fffffu) return 1; /* DRAM V2 */
if (a >= 0xa0000000u && a <= 0xbfffffffu) return 1; /* thread stacks */
return 0;
}
/* Negative self-test: GE_ADDRSTRICT_SELFTEST=1 feeds the validator a value
* that is deliberately not a mapped N64 address, so a run can prove the
* detector is actually live rather than merely silent. */
void portAddrStrictSelfTest(void)
{
if (getenv("GE_ADDRSTRICT_SELFTEST")) {
sysLogPrintf(LOG_INFO, "PORT_ADDR_STRICT: self-test, expect one report below");
portAddrStrictCheck(0x30000000u); /* in the gap between cart and image-rel */
}
}
void portAddrStrictCheck(uint32_t a)
{
/* Report each distinct bad value once -- a bad pointer in a per-frame path
* would otherwise emit thousands of identical lines and bury the first. */
enum { SEEN_MAX = 64 };
static uint32_t seen[SEEN_MAX];
static int seenCount;
int i;
if (portAddrRegionOk(a)) {
return;
}
for (i = 0; i < seenCount; i++) {
if (seen[i] == a) {
return;
}
}
if (seenCount < SEEN_MAX) {
seen[seenCount++] = a;
}
sysLogPrintf(LOG_ERROR,
"PORT_ADDR_STRICT: 0x%08x is not a mapped N64 address "
"(-> %p). Truncated host pointer, or an address that was "
"never re-based (D299-D303 class).",
(unsigned)a, (void *)((uintptr_t)PORT_ADDR_BASE + (uintptr_t)a));
#if defined(PLATFORM_MACOS) || defined(__APPLE__) || defined(__linux__)
{
void *bt[24];
int n = backtrace(bt, (int)(sizeof(bt) / sizeof(bt[0])));
/* Skip frame 0 (this function); the caller is what matters. */
if (n > 1) {
backtrace_symbols_fd(bt + 1, n - 1, 2);
}
}
#endif
}
#endif /* PORT_ADDR_STRICT */
void portAddrInit(void)
{
g_portImageBase = sysImageBase();
@@ -107,4 +192,8 @@ void portAddrInit(void)
(unsigned long long)g_portImageBase, g_portUseImageRel);
portAddrSelfTest();
#ifdef PORT_ADDR_STRICT
portAddrStrictSelfTest();
#endif
}
+9 -3
View File
@@ -745,11 +745,17 @@ typedef union
/* PC port (D43/D45): stored as a raw vma (0x05xxxxxx) instead of a real
* pointer so sizeof(Vertex) stays 16 bytes — the gSPVertex stride is
* hardcoded to 16B and propobj.c copies vertices with sizeof(Vertex).
* modelPromoteNodeOffsetsToPointers rebases it via PROMOTE32; chr.c casts
* back to ModelNode* at the use site. N64 layout unchanged (4B either way). */
* modelPromoteNodeOffsetsToPointers rebases it via PROMOTE32, which is
* `var = (u32)((u32)var + diff)` — note that unlike PROMOTE it keeps the
* field a 32-bit N64 ADDRESS, it does NOT produce a host pointer.
* D301: consumers must therefore re-base it (PORT_N64PTR), never cast it
* straight to a pointer — a bare cast leaves 0x7070_xxxx and faults on
* arm64, where the window is based at PORT_ADDR_BASE.
* N64 layout unchanged (4B either way). */
u32 LinkedTo;
/* Collision vertices only: points to related model node (raw vma). */
/* Collision vertices only: related model node, as a raw vma — same union
* storage as LinkedTo above, so the same PORT_N64PTR rule applies. */
u32 CollisionRelatedNode; /* 0x8 */
#else
struct Vertex *LinkedTo;
+13
View File
@@ -5289,7 +5289,20 @@ void bgRoomCalcBB(s32 room) // canonical name
limits.maxY = -0x7fff;
limits.maxZ = -0x7fff;
#ifdef PORT
/* D302: the induction variable is a full host pointer, so truncating the
* bound to (s32) makes the comparison false on the very first iteration
* once the window is based at PORT_ADDR_BASE -- the loop body never runs,
* `limits` keeps its sentinel init, and every room ends up with an
* INVERTED bounding box (min = pos + 0x7fff, max = pos - 0x7fff) below.
* Silent: no crash, just wrong culling/visibility for every room of every
* level. Byte arithmetic must be preserved (usize_point_index_binary is a
* byte count), so widen the cast rather than doing pointer arithmetic.
* Mirrors the same fix already applied at bg.c:3399. */
for (; vertices < (Vtx *) ((uintptr_t) g_BgRoomInfo[room].vertices + g_BgRoomInfo[room].usize_point_index_binary); vertices++)
#else
for (; vertices < (Vtx *) ((s32) g_BgRoomInfo[room].vertices + g_BgRoomInfo[room].usize_point_index_binary); vertices++)
#endif
{
for (j = 0; j < 3; j++)
{
+20 -10
View File
@@ -8236,7 +8236,7 @@ void bondviewMovePlayerUpdateViewport(s8 stick_x, s8 stick_y, u16 buttons)
if ((cameraBufferToggle != 0) && (viGetFrameBuf2() == (u8*)(cfb_16[1])))
{
viSetFrameBuf2((u8 *) resolution);
viSetFrameBuf2(PORT_N64PTR(u8, resolution));
}
#ifdef VERSION_EU
@@ -8967,7 +8967,17 @@ void mp_respawn_handler(void)
{
coord3d start_pos = ZeroCoordSpawnPos;
f32 start_look_angle;
#ifdef PORT
/* D302: PadRecord.stan is a StandTile* host pointer. An s32 local
* truncates it, and every consumer below (bondviewYPositionRelated,
* change_player_pos_to_target, prop->stan) rebuilds it unbased and
* dereferences it. Local only -- no struct/ABI impact -- and the s32 pad
* below keeps the N64 stack layout identical. The solo twin
* (bondview_r.c:102) already declares this StandTile*. */
StandTile *start_stan;
#else
s32 start_stan;
#endif
s32 pad;
f32 stan_height;
s32 var_v0;
@@ -9059,7 +9069,7 @@ void mp_respawn_handler(void)
switch (intro_record->type)
{
case 0: // INTROTYPE_SPAWN
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroSpawn));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroSpawn));
break;
case 1: // INTROTYPE_ITEM
if (check_ramrom_flags() == ((struct SetupIntroAmmo*)intro_record)->is_demo_playback) {
@@ -9069,28 +9079,28 @@ void mp_respawn_handler(void)
bondinvAddInvItem(((struct SetupIntroItem*)intro_record)->item_right);
}
}
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroItem));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroItem));
break;
case 2: // INTROTYPE_AMMO
if (check_ramrom_flags() == ((struct SetupIntroAmmo*)intro_record)->is_demo_playback) {
give_cur_player_ammo(((struct SetupIntroAmmo*)intro_record)->ammo_type, ((struct SetupIntroAmmo*)intro_record)->ammo_amount);
}
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroAmmo));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroAmmo));
break;
case 3: // INTROTYPE_SWIRL
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroSwirl));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroSwirl));
break;
case 4: // INTROTYPE_ANIM
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroAnim));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroAnim));
break;
case 5: // INTROTYPE_CUFF
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroCuff));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroCuff));
break;
case 6: // INTROTYPE_CAMERA
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroCamera));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroCamera));
break;
default: // INTROTYPE_WATCH, INTROTYPE_CREDITS
intro_record = (struct SetupIntroEmpty*)((s32)intro_record + sizeof(struct SetupIntroEmpty));
intro_record = (struct SetupIntroEmpty*)((uintptr_t)intro_record + sizeof(struct SetupIntroEmpty));
break;
}
#ifdef DEBUG
@@ -11188,7 +11198,7 @@ join_768:
if (ppointers[index]->bodyModel->anim2 == NULL)
{
startframe = (0.0f <= frame) ? (frame) : (0.0f);
modelSetAnimation(ppointers[index]->bodyModel, (ModelAnimation *) anim, 0, startframe, angle, 16.0f);
modelSetAnimation(ppointers[index]->bodyModel, PORT_N64PTR(ModelAnimation, anim), 0, startframe, angle, 16.0f);
ppointers[index]->players_cur_animation = anim;
ppointers[index]->field_1288 = angle;
+8 -3
View File
@@ -3584,10 +3584,15 @@ after_opcode:
}
#ifdef PORT
/* PC port (D43/D45): CollisionRelatedNode is a raw vma (u32), not a pointer. */
relatednode = (ModelNode *)(uintptr_t)((ModelRoData_DisplayList_CollisionRecord *) node)->CollisionVertices[bestindex].CollisionRelatedNode;
/* PC port (D43/D45): CollisionRelatedNode is a raw vma (u32), not a pointer.
* D301: that vma is an N64 address (the port stores window pointers as
* 32-bit values, and PORT_ADDR_BASE is 4 GiB-aligned, so the low 32 bits
* ARE the N64 address), so it must be re-based like every other
* address-carrying field -- a bare cast leaves it as 0x7070_xxxx and the
* &relatednode->Data deref below faults. Test the re-based pointer. */
relatednode = PORT_N64PTR(ModelNode, ((ModelRoData_DisplayList_CollisionRecord *) node)->CollisionVertices[bestindex].CollisionRelatedNode);
if (((ModelRoData_DisplayList_CollisionRecord *) node)->CollisionVertices[bestindex].CollisionRelatedNode != 0)
if (relatednode != NULL)
#else
relatednode = (ModelNode *) ((ModelRoData_DisplayList_CollisionRecord *) node)->CollisionVertices[bestindex].CollisionRelatedNode;
+4
View File
@@ -23,6 +23,7 @@
void cleanupObjects(s32 stage)
{
#ifdef PORT
/* D300: the decomp's `(u32)g_CurrentSetup.propDefs` converts the pointer
* to a 32-bit integer and back into a `u32 *` -- a no-op where pointers
* are 32-bit (N64), but on the 64-bit port it TRUNCATES the host pointer
@@ -32,6 +33,9 @@ void cleanupObjects(s32 stage)
* pointer-to-pointer and keep `u32 *` for sizepropdef()'s 4-byte-unit
* increment. Same class as D3x; semantics unchanged. */
u32 *obj = (u32 *)g_CurrentSetup.propDefs;
#else
u32 *obj = (u32)g_CurrentSetup.propDefs;
#endif
if (obj)
{
+13
View File
@@ -1739,9 +1739,22 @@ void texReadAlphaBits(u8 *image,s32 count)
*/
s32 texReadUncompressed(u8 *dst, s32 width, s32 height, s32 format)
{
#ifdef PORT
/* D303: aligning a host pointer through u32 truncates it -- on arm64 dst is
* ~0x1000_70xx_xxxx and these would become unbased 0x70xx_xxxx, then be
* written through. Align at full pointer width instead. Identical result
* where pointers are 32-bit. NOTE: currently unreachable on the PC port --
* textures come from the pre-converted pccg sidecars, and a GE_D303 probe
* over a full level boot recorded zero calls -- so this is latent, fixed
* defensively rather than in response to an observed fault. */
u32 *dst32 = (u32 *)(((uintptr_t)dst + 0xf) & ~(uintptr_t)0xf);
u16 *dst16 = (u16 *)(((uintptr_t)dst + 7) & ~(uintptr_t)7);
u8 *dst8 = (u8 *)(((uintptr_t)dst + 7) & ~(uintptr_t)7);
#else
u32 *dst32 = (u32 *)(((u32)dst + 0xf) & ~0xf);
u16 *dst16 = (u16 *)(((u32)dst + 7) & ~7);
u8 *dst8 = (u8 *)(((u32)dst + 7) & ~7);
#endif
s32 x;
s32 y;
+4 -4
View File
@@ -1924,18 +1924,18 @@ void process_03_unknown(ModelRenderData *renderData, Model *model, ModelNode *no
jointIndex = rodata->JointID;
skeleton = model->obj->Skeleton;
angle = sub_GAME_7F06E540(jointIndex, model->gunhand, skeleton, model->anim, (u8 *)model->unk34);
angle = sub_GAME_7F06E540(jointIndex, model->gunhand, skeleton, model->anim, PORT_N64PTR(u8, model->unk34));
if (model->unk2c != 0.0f) {
tmp = sub_GAME_7F06E540(jointIndex, model->gunhand, skeleton, model->anim, (u8 *)model->unk38);
tmp = sub_GAME_7F06E540(jointIndex, model->gunhand, skeleton, model->anim, PORT_N64PTR(u8, model->unk38));
angle = sub_GAME_7F06D0CC(angle, tmp, model->unk2c);
}
if (model->unk84 != 0.0f) {
tmp = sub_GAME_7F06E540(jointIndex, model->unk25, skeleton, model->anim2, (u8 *)model->unk64);
tmp = sub_GAME_7F06E540(jointIndex, model->unk25, skeleton, model->anim2, PORT_N64PTR(u8, model->unk64));
if (model->unk5c != 0.0f) {
tmp2 = sub_GAME_7F06E540(jointIndex, model->unk25, skeleton, model->anim2, (u8 *)model->unk68);
tmp2 = sub_GAME_7F06E540(jointIndex, model->unk25, skeleton, model->anim2, PORT_N64PTR(u8, model->unk68));
tmp = sub_GAME_7F06D0CC(tmp, tmp2, model->unk5c);
}
+1 -1
View File
@@ -3490,7 +3490,7 @@ Gfx *draw_watch_controller(Gfx *gdl)
}
else
{
gdl = watchRenderControllerOpaque(gdl, &finalmtx, 1, (s32) (&table2), &contpadnum1);
gdl = watchRenderControllerOpaque(gdl, &finalmtx, 1, (WatchContButtonPositions *) (&table2), &contpadnum1);
}
}