mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 08:12:42 +02:00
D150 (src/str.c) — NULL-tolerant strcpy/strncpy/strcat under #ifdef PORT. The watch BRIEF/OBJECTIVES pages build text with strcat(buf, langGet(id)); langGet() returns NULL on PC for a string bank the in-level flow never loaded, and str.c's primitives are __nonnull__ builtins so a plain param NULL check is optimised away — the guard launders the ptr through an empty asm (GE_IS_NULL). NULL src -> empty string instead of an AV crash. D151 (src/bondtypes.h) — watch data (objectives / mission background / M / Q / Moneypenny) all rendered blank. propDef types 35 (WatchMenuObjectiveText) and 23 (ObjectiveStart) store a plain s32 lang slot id in their 3rd word; the decomp structs decode it as [u16 reserved][u16 text] reading text at offset 0xA, which only works because the id is in the low 16 bits of a big-endian word. d88_propdefs.py bswap32's the whole word -> text @0xA reads 0 -> langGet(0) -> NULL. Fix: text is a full u32 at the word offset under #ifdef PORT (N64 layout kept under #else). No converter change, no sidecar regen. This is the D143/D150 "briefing text renders blank" root cause. D152 (port/src/libultra.c) — mission-failed / objective-failed permanent black screen. The D147 recursive-mutex behind osSetIntMask can be leaked (libaudio has unbalanced / early-return mask calls; a transient thread can acquire OS_IM_NONE and exit) -> mainThread + amMain both block forever in alEvtq* -> gfx pipeline stalls. Live gdb.txt from the user's session confirmed the deadlock (mainThread in the mission-fail audio fade-out sndSetScalerApplyVolumeAllSfxSlot -> alEvtqPostEvent). Mitigation: model the mask as a self-healing logical lock (owner/depth under s_imMx + condvar) — normal microsecond critical sections behave as before, but a waiter blocked > 2s concludes the holder leaked it, logs the stale owner + the stealing caller's return address, and steals the section. Worst case is a ~2s audio hiccup + a log line naming the leak, instead of a hung game. Proper narrow fix (dedicated ALEventQueue lock) still owed. D153 investigated and dismissed — the "-level_09 crashes at frame ~900" seen while regression-testing was D117/D134 host-scheduling flakiness under heavy concurrent build+run load; on a quiet machine -level_09 runs past frame 2400 clean with all three changes applied. Build: green (ntsc-final). Golden framediff not run clean (same machine-load flakiness); one quiet -level_09 run = 2400+ frames, 0 heartbeats.
203 lines
5.1 KiB
C
203 lines
5.1 KiB
C
#include "str.h"
|
|
|
|
#ifdef PORT
|
|
/* D150: strcpy/strncpy/strcat are __nonnull__ builtins to GCC, so a plain
|
|
* `if (src == NULL)` on the parameter is optimised away as provably-dead.
|
|
* Launder the pointer through an empty asm so the compiler cannot prove it
|
|
* non-NULL and the guard survives (-Og elided it otherwise — verified in
|
|
* the disassembly). */
|
|
static inline const void *ge_launder_ptr(const void *p) {
|
|
__asm__("" : "+r"(p));
|
|
return p;
|
|
}
|
|
#define GE_IS_NULL(p) (ge_launder_ptr(p) == NULL)
|
|
#endif
|
|
|
|
char *strcpy(char *dst, const char *src) {
|
|
unsigned char *ptr = dst;
|
|
#ifdef PORT
|
|
/* D150: the watch briefing/objective pages assemble their text with
|
|
* strcpy/strcat(buf, langGet(id)); langGet() returns NULL for a string
|
|
* bank the PC menu flow never loaded (D129/D143), so a NULL src here
|
|
* faults. Treat NULL as the empty string (blank text, not a crash) —
|
|
* same philosophy as the D143 textRender/textMeasure NULL guards.
|
|
* N64 langGet never returns NULL for these ids, so this is inert there. */
|
|
if (GE_IS_NULL(src)) { if (!GE_IS_NULL(dst)) *ptr = '\0'; return dst; }
|
|
#endif
|
|
while(*ptr++ = *src++);
|
|
return dst;
|
|
}
|
|
|
|
char *strncpy(char *dst, const char *src, size_t n) {
|
|
unsigned char *ptr = dst;
|
|
#ifdef PORT
|
|
if (GE_IS_NULL(src)) { while (n--) *ptr++ = '\0'; return dst; } /* D150 */
|
|
#endif
|
|
while((*ptr++ = *src++)) {
|
|
if (--n == 0) {
|
|
break;
|
|
}
|
|
}
|
|
while(n--) {
|
|
(*ptr++) = '\0';
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
char *strcat(char *dst, const char *src) {
|
|
unsigned char *ptr = dst;
|
|
#ifdef PORT
|
|
if (GE_IS_NULL(dst)) { return dst; } /* D150 */
|
|
if (GE_IS_NULL(src)) { return dst; } /* D150 — see strcpy note */
|
|
#endif
|
|
while (*ptr) { ptr++; };
|
|
while(*ptr++ = *src++);
|
|
return dst;
|
|
}
|
|
|
|
int strcmp(const char* str1, const char* str2) {
|
|
unsigned int var;
|
|
unsigned char c1;
|
|
unsigned char c2;
|
|
while (TRUE) {
|
|
var = c1 = *(str1++);
|
|
if (var != (c2 = *str2)) {
|
|
if (c1 < c2) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
if (c1 == '\0') {
|
|
return 0;
|
|
}
|
|
str2++;
|
|
}
|
|
}
|
|
|
|
int strncmp(const char *str1, const char *str2, size_t n) {
|
|
unsigned int var;
|
|
unsigned char c1;
|
|
unsigned char c2;
|
|
while (TRUE) {
|
|
if (n == 0) {
|
|
return 0;
|
|
}
|
|
n--;
|
|
var = c1 = *str1++;
|
|
if (var != (c2 = *str2)) {
|
|
if (c1 < c2) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
if (c1 == '\0') {
|
|
return 0;
|
|
}
|
|
str2++;
|
|
}
|
|
}
|
|
|
|
unsigned char toupper(unsigned char c) {
|
|
if ((c >= 'a') && (c <= 'z')) {
|
|
return ('A' + c - 'a');
|
|
} else {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
int isdigit(unsigned char c) {
|
|
return ((c >= '0') && (c <= '9'));
|
|
}
|
|
|
|
int isalpha(unsigned char c) {
|
|
return (((c >= 'a') && (c <= 'z')) ||
|
|
((c >= 'A') && (c <= 'Z')));
|
|
}
|
|
|
|
int isspace(unsigned char c) {
|
|
return ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\f') || (c == '\v'));
|
|
}
|
|
|
|
#define ULONG_MAX ((unsigned long)(~0L)) /* 0xFFFFFFFF */
|
|
long int strtol(const char *str, char **endptr, int base) {
|
|
int neg;
|
|
unsigned char *ptr;
|
|
unsigned int cutoff;
|
|
unsigned int cutlim;
|
|
unsigned int accum;
|
|
unsigned char c;
|
|
unsigned char *before;
|
|
int overflow;
|
|
if ((base < 0) || (base == 1) || (base > 36)) {
|
|
base = 10;
|
|
}
|
|
ptr = str;
|
|
while (isspace(*ptr)) { ptr++; };
|
|
if ((int)*ptr) {
|
|
if (*ptr == '-') {
|
|
neg = 1;
|
|
ptr++;
|
|
} else if (*ptr == '+') {
|
|
neg = 0;
|
|
ptr++;
|
|
} else {
|
|
neg = 0;
|
|
}
|
|
if (base == 16) {
|
|
if ((ptr[0] == '0') && (toupper(ptr[1]) == 'X')) {
|
|
ptr += 2;
|
|
}
|
|
}
|
|
if (base == 0) {
|
|
if (ptr[0] == '0') {
|
|
if (toupper(ptr[1]) == 'X') {
|
|
ptr += 2;
|
|
base = 16;
|
|
} else {
|
|
base = 8;
|
|
}
|
|
} else {
|
|
base = 10;
|
|
}
|
|
}
|
|
before = ptr;
|
|
overflow = 0;
|
|
accum = 0;
|
|
cutoff = ULONG_MAX / base;
|
|
cutlim = ULONG_MAX % base;
|
|
for (; (int)(c = *ptr); ptr++) {
|
|
if (isdigit(c)) {
|
|
c -= '0';
|
|
} else if (isalpha(c)) {
|
|
c = (toupper(c) - ('A' - 0xA));
|
|
} else {
|
|
break;
|
|
}
|
|
if (c >= base) {
|
|
break;
|
|
}
|
|
if ((accum > cutoff) || ((accum == cutoff) && ((unsigned int)c > cutlim))) {
|
|
overflow = 1;
|
|
} else {
|
|
accum *= base;
|
|
accum += (unsigned int)c;
|
|
}
|
|
}
|
|
if (ptr != before) {
|
|
if (endptr != NULL) {
|
|
*endptr = ptr;
|
|
}
|
|
if (overflow) {
|
|
return -1;
|
|
}
|
|
return (neg ? -accum : accum);
|
|
}
|
|
}
|
|
if (endptr != NULL) {
|
|
*endptr = str;
|
|
}
|
|
return 0;
|
|
}
|