forked from Ports/goldeneye-pc-port
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:
1 parent
498be832c2
commit
3785dcad33
14 files changed
+269
-23
No files matched your search
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user