mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
Merge branch 'main' into feat/alpha-asset-prep-tool
This commit is contained in:
commit
af881ff33c
24 files changed
+410
-41
No files matched your search
@@ -23,7 +23,11 @@ concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Three jobs:
|
||||
# Jobs:
|
||||
#
|
||||
# linux-build — ubuntu, system GCC. Actually compiles + links (unlike
|
||||
# validate). Surfaces GCC-on-Linux strictness gaps MinGW
|
||||
# misses. Not a runtime claim; not a required check yet.
|
||||
#
|
||||
# validate — ubuntu, configure-only. Byte-compiles the tooling,
|
||||
# regenerates the US ROM-asset symbol file, and CMake-
|
||||
@@ -108,6 +112,47 @@ jobs:
|
||||
- name: CMake configure (ntsc-final)
|
||||
run: cmake -S . -B build-ntsc -DROMID=ntsc-final
|
||||
|
||||
# Linux (Ubuntu, system GCC) compile job. Unlike `validate` this actually
|
||||
# runs `cmake --build`, so it surfaces the GCC-on-Linux strictness gaps that
|
||||
# MinGW does not flag. The port has never been run on Linux — a green build
|
||||
# here is a "does it compile + link" signal only, not a runtime claim.
|
||||
# Not a required check yet; flip it to required once it is reliably green.
|
||||
#
|
||||
# Toolchain: ubuntu-24.04 + gcc-14. The port is developed on MinGW GCC 16;
|
||||
# pinning a recent GCC here keeps the first bring-up focused on real
|
||||
# portability gaps rather than a decade of accumulated old-GCC pedantry.
|
||||
# (A "does it build on the GCC a typical distro ships" pass can come later.)
|
||||
linux-build:
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential gcc-14 g++-14 cmake python3 \
|
||||
libsdl2-dev zlib1g-dev libgl1-mesa-dev
|
||||
|
||||
- name: Regenerate ROM-asset symbol file (US)
|
||||
run: python3 scripts/gen_romassets.py u
|
||||
|
||||
- name: Configure (ntsc-final)
|
||||
run: cmake -S . -B build-linux -DROMID=ntsc-final
|
||||
-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
|
||||
|
||||
- name: Build (ntsc-final)
|
||||
run: cmake --build build-linux -j"$(nproc)"
|
||||
|
||||
- name: Show binary
|
||||
run: |
|
||||
ls -la build-linux/ge007* || true
|
||||
file build-linux/ge007* || true
|
||||
|
||||
# Real Windows (MSYS2 MINGW64) build — the only platform the port is
|
||||
# developed and tested on. This compiles and LINKS the full executable.
|
||||
#
|
||||
|
||||
+81
-4
@@ -52,11 +52,25 @@ include(TargetArch)
|
||||
include(FindPkgConfig)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
# GNU extensions ON: strict -std=c11/-std=c++20 define __STRICT_ANSI__, which
|
||||
# disables glibc's _DEFAULT_SOURCE and hides POSIX symbols the port needs
|
||||
# (clock_gettime/CLOCK_REALTIME in port/src/libultra.c, mmap flags, etc.).
|
||||
# -std=gnu11/gnu++20 is GCC's own default and what MinGW was already using.
|
||||
set(CMAKE_C_EXTENSIONS ON)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Opt-in ccache launcher. The game sources are frozen (1:1 with the decomp),
|
||||
# so ccache makes branch-switch / worktree rebuilds nearly free. No-op when
|
||||
# ccache is not installed.
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
if(CCACHE_PROGRAM)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
message(STATUS "ccache enabled: ${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
|
||||
# Default build type
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt)
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "" FORCE)
|
||||
@@ -148,6 +162,15 @@ configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/port/include/hoststring.h.in"
|
||||
"${CMAKE_BINARY_DIR}/port/include/hoststring.h" @ONLY)
|
||||
|
||||
# Host <sched.h> path for port/shim/sched.h. The decomp's src/sched.h is on
|
||||
# the include path and shadows the system header; libstdc++'s <thread>/gthr
|
||||
# headers in the fast3d C++ TUs need the real <sched.h> (cpu_set_t,
|
||||
# sched_yield). Both MinGW and Linux keep <sched.h> at bin/../include.
|
||||
get_filename_component(PORT_HOST_SCHED_H "${_PORT_GCC_DIR}/../include/sched.h" ABSOLUTE)
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/port/include/hostsched.h.in"
|
||||
"${CMAKE_BINARY_DIR}/port/include/hostsched.h" @ONLY)
|
||||
|
||||
# Host <stdlib.h> path for port/shim/stdlib.h. The N64 include/stdlib.h
|
||||
# typedefs lldiv_t/ldiv_t, which conflict with the host's when both land in
|
||||
# one C++ TU (fast3d): MinGW's own sec_api/stdlib_s.h does `#include
|
||||
@@ -160,7 +183,17 @@ configure_file(
|
||||
|
||||
# Host <stddef.h> path for port/shim/stddef.h (same reason as stdlib: the N64
|
||||
# stub shadows the host header in C++ TUs, hiding max_align_t from cstddef).
|
||||
get_filename_component(PORT_HOST_STDDDEF_H "${_PORT_GCC_DIR}/../include/stddef.h" ABSOLUTE)
|
||||
# stddef.h is compiler-internal, not in bin/../include: on a native Linux GCC
|
||||
# it lives under lib/gcc/<triple>/<ver>/include. Ask the compiler where it is;
|
||||
# fall back to the MinGW bin/../include layout.
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_C_COMPILER}" -print-file-name=include/stddef.h
|
||||
OUTPUT_VARIABLE _PORT_GCC_STDDEF OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
|
||||
if(_PORT_GCC_STDDEF AND EXISTS "${_PORT_GCC_STDDEF}")
|
||||
get_filename_component(PORT_HOST_STDDDEF_H "${_PORT_GCC_STDDEF}" ABSOLUTE)
|
||||
else()
|
||||
get_filename_component(PORT_HOST_STDDDEF_H "${_PORT_GCC_DIR}/../include/stddef.h" ABSOLUTE)
|
||||
endif()
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/port/include/hoststddef.h.in"
|
||||
"${CMAKE_BINARY_DIR}/port/include/hoststddef.h" @ONLY)
|
||||
@@ -192,7 +225,11 @@ add_compile_options(
|
||||
# get a real backtrace of a hung/crashed game thread.
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
# Optimisation: the software RSP + software rasteriser in port/fast3d are the
|
||||
# hot path and are very sensitive to -O level (the difference between ~1 fps
|
||||
# and playable). Non-Debug builds keep the CMAKE_BUILD_TYPE optimisation
|
||||
# (RelWithDebInfo => -O2); only an explicit Debug build drops to -Og.
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_compile_options(-Og)
|
||||
endif()
|
||||
|
||||
@@ -223,6 +260,25 @@ add_compile_options(
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-unused-function>)
|
||||
|
||||
# The decomp's `#define inherits struct` idiom (src/bondtypes.h) makes the base
|
||||
# struct an unnamed tagged member and accesses its fields directly
|
||||
# (obj->type where type lives in the embedded PropRecord). GCC targeting
|
||||
# *-w64-mingw32 enables this via -fms-extensions implicitly; a native Linux/
|
||||
# macOS GCC does not, so `'ObjectRecord' has no member named 'type'`. Enable
|
||||
# the Plan 9 extensions explicitly there (superset of -fms-extensions: also the
|
||||
# automatic base-pointer conversion the idiom relies on). C only — the decomp
|
||||
# is C; fast3d C++ does not use `inherits`.
|
||||
if(NOT MINGW)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>)
|
||||
# GCC 14 promoted several long-standing C leniencies to hard errors
|
||||
# (int<->pointer for a known prototype, incompatible-pointer arg types,
|
||||
# implicit int/function). The WIP decomp still trips these (e.g.
|
||||
# append_text_picked_up's vestigial u8* params in propobj.c). -fpermissive
|
||||
# downgrades them to warnings — the C counterpart of the -Wno-error lines
|
||||
# above, and the documented path for building older C against GCC 14+.
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C>:-fpermissive>)
|
||||
endif()
|
||||
|
||||
# ASLR must be OFF: dram_syms.s defines absolute symbols (cfb_16 @ 0x70000000,
|
||||
# _bssSegmentEnd @ 0x70050000) that the game references through pointer-typed
|
||||
# externs (src/bondgame.h: `extern u32 *_bssSegmentEnd`). On x86-64/PE, taking
|
||||
@@ -233,6 +289,27 @@ add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-unused-function>)
|
||||
# base at startup.
|
||||
if(MINGW)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-dynamicbase")
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
# Linux equivalent of the ASLR-off requirement above: dram_syms.s defines
|
||||
# absolute symbols at 0x70000000 referenced through pointer-typed externs. A
|
||||
# PIE build (GCC's default on modern distros) emits GOT/relative relocations
|
||||
# for them — R_X86_64_32S link errors, or a runtime rebase to garbage. Build
|
||||
# and link no-PIE so the image keeps its preferred load address.
|
||||
add_compile_options(-fno-pie)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie")
|
||||
# Move the image base to 0x20000000. The default no-PIE base 0x400000 puts
|
||||
# .data/.bss symbols around 0x00500000-0x00600000, which COLLIDES with the
|
||||
# N64 address ranges fast3d's seg_addr() decodes: a game-built display list
|
||||
# that embeds `&symbol` (via the osVirtualToPhysical identity shim) hands
|
||||
# seg_addr a value < 0x800000, which it treats as a K0-physical DRAM offset
|
||||
# and remaps to 0x80xxxxxx (zeroed DRAM mirror) -> "unknown GBI opcode 0x00"
|
||||
# -> black screen. 0x20000000 sits in the gap between the segmented-address
|
||||
# space (< 0x10000000), the cart map (0x10000000), the D131 module range
|
||||
# ([0x40000000,0x70000000)) and DRAM (>= 0x70000000); seg_addr passes such
|
||||
# addresses through untouched. Still < 2GiB so -mcmodel=small / R_X86_64_32
|
||||
# relocations are fine (unlike a 0x140000000 base, which needs mcmodel=large).
|
||||
# This is the ELF analogue of the Windows build's fixed 0x140000000 base.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x20000000")
|
||||
endif()
|
||||
|
||||
# port/shim first: PC-only shadow headers (bondtypes.h, bondconstants.h,
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROMID="${1:-ntsc-final}"
|
||||
BUILD_DIR="build-pc"
|
||||
BUILD_DIR="${BUILD_DIR:-build-pc}"
|
||||
|
||||
echo "==> Configuring PC port (ROMID=${ROMID})"
|
||||
cmake -S . -B "${BUILD_DIR}" -DROMID="${ROMID}"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 531 KiB |
@@ -980,9 +980,16 @@ extern void osAckRamromWrite(void);
|
||||
|
||||
/* byte string operations */
|
||||
|
||||
#if defined(PORT) && !defined(_WIN32)
|
||||
/* On the Linux/macOS port these resolve to the host libc; glibc <strings.h>
|
||||
* declares them with size_t sizes, which conflict with the N64 `int`
|
||||
* prototypes below (GCC 14 hard error). Use the host declarations. */
|
||||
#include <strings.h>
|
||||
#else
|
||||
extern void bcopy(const void *, void *, int);
|
||||
extern int bcmp(const void *, const void *, int);
|
||||
extern void bzero(void *, int);
|
||||
#endif
|
||||
|
||||
/* Miscellaneous operations */
|
||||
|
||||
|
||||
@@ -1083,7 +1083,15 @@ extern "C" bool gfx_opengl_dump_bound_fbo(uint32_t width, uint32_t height, const
|
||||
size_t n = (size_t)width * height * 3;
|
||||
uint8_t* pixels = (uint8_t*)malloc(n);
|
||||
if (!pixels) return false;
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glReadBuffer(GL_BACK);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glFinish();
|
||||
glReadPixels(0, 0, (GLsizei)width, (GLsizei)height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
|
||||
{
|
||||
GLenum e = glGetError();
|
||||
if (e) sysLogPrintf(LOG_WARNING, "dump_bound_fbo: glReadPixels err 0x%x (%ux%u)", (unsigned)e, width, height);
|
||||
}
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) { free(pixels); return false; }
|
||||
fprintf(f, "P6\n%u %u\n255\n", width, height);
|
||||
@@ -1199,7 +1207,11 @@ bool gfx_opengl_start_draw_to_framebuffer(int fb_id, float noise_scale) {
|
||||
if (noise_scale != 0.0f) {
|
||||
current_noise_scale = 1.0f / noise_scale;
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo);
|
||||
/* fb_id 0 is the on-screen target: bind the window's default
|
||||
* framebuffer (GL name 0), not framebuffers[0].fbo (a generated but
|
||||
* attachment-less FBO -> incomplete -> nothing renders on a strict
|
||||
* GL implementation like Mesa/llvmpipe). */
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb_id == 0 ? 0 : fb.fbo);
|
||||
current_framebuffer = fb_id;
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -427,10 +427,19 @@ static inline void sync_framerate_with_timer(void) {
|
||||
previous_time = t;
|
||||
}
|
||||
|
||||
/* Optional hook run with the freshly-composited frame still in the back
|
||||
* buffer, immediately before the swap. video.c uses it for GE_PCDUMP /
|
||||
* screenshots: reading the back buffer AFTER SDL_GL_SwapWindow is undefined
|
||||
* on buffer-exchange drivers (Mesa, WSLg) and yields a black capture. */
|
||||
extern "C" void (*gfx_pre_swap_hook)(void) = NULL;
|
||||
|
||||
static void gfx_sdl_swap_buffers_begin(void) {
|
||||
if (target_fps) {
|
||||
sync_framerate_with_timer();
|
||||
}
|
||||
if (gfx_pre_swap_hook) {
|
||||
gfx_pre_swap_hook();
|
||||
}
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef PORT_HOSTSCHED_H
|
||||
#define PORT_HOSTSCHED_H
|
||||
/* Generated by CMake from hostsched.h.in — do not edit.
|
||||
* Includes the HOST <sched.h> by absolute path so that port/shim/sched.h can
|
||||
* give C++ TUs (fast3d, libstdc++'s <thread>/gthr headers) the real
|
||||
* <sched.h> — cpu_set_t, sched_yield — instead of the decomp's src/sched.h,
|
||||
* which sits on the include path and would otherwise shadow it. */
|
||||
#include "@PORT_HOST_SCHED_H@"
|
||||
#endif
|
||||
@@ -201,6 +201,11 @@ void fileUpdateSelectedBondInSave();
|
||||
s32 fogGetPropDistColor();
|
||||
void fogLoadLevelEnvironment();
|
||||
void free();
|
||||
/* D38: the N64 <stdlib.h> stub declares neither, and unlike MinGW a strict
|
||||
* host GCC does not leak them — so getenv()'s 64-bit pointer return is
|
||||
* truncated to int (crashed configGetFrameDump on Linux). */
|
||||
char * getenv();
|
||||
void * realloc();
|
||||
int frontGetPlayersFavoriteWeaponInHand();
|
||||
void generate_player_thrown_grenade();
|
||||
void generate_player_thrown_knife();
|
||||
@@ -389,9 +394,10 @@ s32 sizepropdef();
|
||||
Gfx * skyRender();
|
||||
void skySetStageNum();
|
||||
void skyTick();
|
||||
/* Exact match of GCC's built-in (and MSVCRT) prototype; size_t is
|
||||
* `unsigned long long` on this MinGW toolchain. */
|
||||
int snprintf(char *, unsigned long long, const char *, ...);
|
||||
/* Match the C library prototype. size_t (from <PR/ultratypes.h> above) is
|
||||
* `unsigned long long` on MinGW but `unsigned long` on Linux/glibc — spelling
|
||||
* it `size_t` keeps this compatible with both libcs' <stdio.h>. */
|
||||
int snprintf(char *, size_t, const char *, ...);
|
||||
PropRecord * something_with_generating_object();
|
||||
void speedgraphInit();
|
||||
void speedgraphMarkerUpdate();
|
||||
@@ -446,7 +452,9 @@ s32 texLoadFromGdl();
|
||||
s32 texReadUncompressed();
|
||||
void texSelect();
|
||||
s32 texShrinkNonPaletted();
|
||||
long long time();
|
||||
#if defined(_WIN32)
|
||||
long long time(); /* MinGW: <time.h> is shadowed by the N64 stub here; POSIX gets the real one */
|
||||
#endif
|
||||
const char * tokenFind();
|
||||
s32 tokenReadIo();
|
||||
void transform3Dto2DWithZScaling();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* PC port shim for <sched.h>.
|
||||
*
|
||||
* The decomp ships src/sched.h (the N64 OSSc* scheduler client interface),
|
||||
* which is on the compiler include path. libstdc++'s <thread> / gthr headers
|
||||
* (pulled into the fast3d C++ TUs) do `#include <sched.h>` and need the HOST
|
||||
* header — cpu_set_t, sched_yield — so C++ TUs must be routed to the real
|
||||
* <sched.h> by absolute path (generated hostsched.h, same trick as
|
||||
* port/shim/string.h). C TUs keep resolving <sched.h> to the decomp's own
|
||||
* src/sched.h, exactly as on the console.
|
||||
*
|
||||
* Inert in the N64 build (port/shim is not on its include path).
|
||||
*/
|
||||
#if defined(__cplusplus)
|
||||
#include "hostsched.h"
|
||||
#else
|
||||
#include_next <sched.h>
|
||||
#endif
|
||||
+26
-7
@@ -4,16 +4,35 @@
|
||||
* The N64 decomp's include/stdarg.h does `#include <ultra64.h>` (the console
|
||||
* dev-kit convenience: one header pulls in all of libultra). On the PC that
|
||||
* means any HOST header that internally includes <stdarg.h> (SDL_stdinc.h,
|
||||
* parts of MinGW's own headers, ...) drags PR/os.h into the middle of host
|
||||
* header parsing. If MinGW's <errno.h> has already run (it defines the
|
||||
* `errno` macro), PR/os.h's controller structs — which have fields literally
|
||||
* named `errno` — fail to parse ("field '_errno' declared as a function").
|
||||
*
|
||||
* Parse the N64 header with the errno macro suppressed; restore it after.
|
||||
* Inert in the N64 build (port/shim is not on its include path).
|
||||
* parts of the CRT's own headers, glibc's <stdio.h>, ...) drags PR/os.h into
|
||||
* the middle of host header parsing.
|
||||
*/
|
||||
|
||||
#if defined(_WIN32)
|
||||
/*
|
||||
* MinGW: parse the N64 header with the errno macro suppressed (PR/os.h has
|
||||
* fields literally named `errno`); restore it after. MinGW's CRT headers do
|
||||
* not need __gnuc_va_list.
|
||||
*/
|
||||
#pragma push_macro("errno")
|
||||
#undef errno
|
||||
#include "include/stdarg.h"
|
||||
#pragma pop_macro("errno")
|
||||
|
||||
#else
|
||||
/*
|
||||
* Linux/macOS: do NOT pull <ultra64.h> into system-header parsing. glibc's
|
||||
* <stdio.h> needs both va_list and the __gnuc_va_list alias that GCC's real
|
||||
* <stdarg.h> would provide; the decomp header defines neither. Provide the
|
||||
* whole set straight from the always-available compiler builtins.
|
||||
*/
|
||||
#ifndef _PORT_SHIM_STDARG_H
|
||||
#define _PORT_SHIM_STDARG_H
|
||||
typedef __builtin_va_list va_list;
|
||||
typedef __builtin_va_list __gnuc_va_list;
|
||||
#define va_start(ap, last) __builtin_va_start(ap, last)
|
||||
#define va_end(ap) __builtin_va_end(ap)
|
||||
#define va_arg(ap, type) __builtin_va_arg(ap, type)
|
||||
#define va_copy(dst, src) __builtin_va_copy(dst, src)
|
||||
#endif
|
||||
#endif
|
||||
+36
-1
@@ -25,6 +25,41 @@
|
||||
#undef __need_NULL
|
||||
#undef __need_wint_t
|
||||
#include "hoststddef.h"
|
||||
#else
|
||||
#elif defined(_WIN32)
|
||||
/*
|
||||
* C TUs on MinGW: keep the empty N64 include/stddef.h stub. MinGW's CRT
|
||||
* headers leak size_t/ptrdiff_t transitively, and its own <stddef.h> drags in
|
||||
* the `#define errno (*_errno())` CRT macro, which collides with PR/os.h's
|
||||
* `u8 errno;` struct field — so routing to the host header is not an option
|
||||
* here.
|
||||
*/
|
||||
#include "include/stddef.h"
|
||||
#else
|
||||
/*
|
||||
* C TUs on Linux/macOS: the N64 include/stddef.h stub defines nothing, glibc
|
||||
* only defines these in the compiler's shadowed builtin <stddef.h>, and the
|
||||
* MinGW hoststddef trick (bin/../include/stddef.h) does not exist on Linux.
|
||||
* Provide the standard types straight from the always-predefined GCC/Clang
|
||||
* builtins — no header, so nothing to collide with. C11 permits the benign
|
||||
* identical redefinition if a real <stddef.h> is ever reached another way.
|
||||
*/
|
||||
#ifndef _PORT_SHIM_STDDEF_TYPES
|
||||
#define _PORT_SHIM_STDDEF_TYPES
|
||||
typedef __SIZE_TYPE__ size_t;
|
||||
typedef __PTRDIFF_TYPE__ ptrdiff_t;
|
||||
/* SDL's headers (SDL_stdinc.h, SDL_hidapi.h) and glibc's <bits/wchar2.h>
|
||||
* reference wchar_t, which in C is a library typedef owned by the compiler's
|
||||
* shadowed <stddef.h>. Provide it from the builtin, guarded by the same
|
||||
* macro GCC/glibc use so a real <stddef.h> reached later is a no-op. */
|
||||
#ifndef _WCHAR_T
|
||||
#define _WCHAR_T
|
||||
typedef __WCHAR_TYPE__ wchar_t;
|
||||
#endif
|
||||
#ifndef NULL
|
||||
#define NULL ((void *)0)
|
||||
#endif
|
||||
#ifndef offsetof
|
||||
#define offsetof(t, m) __builtin_offsetof(t, m)
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -17,4 +17,15 @@
|
||||
#include "hoststdlib.h"
|
||||
#else
|
||||
#include "include/stdlib.h"
|
||||
#if !defined(_WIN32)
|
||||
/* The N64 stub declares only lldiv_t/ldiv_t + lldiv/ldiv. MinGW's other CRT
|
||||
* headers leak getenv() etc.; a strict host GCC (Linux/macOS) does not, so
|
||||
* getenv()'s 64-bit pointer return is assumed int and truncated (crashed
|
||||
* configGetFrameDump). Declare the pointer-returning stragglers the port +
|
||||
* game code use that nothing else declares. K&R form = compatible with any
|
||||
* real prototype. (malloc/calloc/free are in port/include/pc_protos.h and
|
||||
* a few port TUs' own local decls — not repeated here to avoid conflicts.) */
|
||||
extern char *getenv();
|
||||
extern void *realloc();
|
||||
#endif
|
||||
#endif
|
||||
@@ -402,6 +402,39 @@ static void crashHandler(int sig, siginfo_t *siginfo, void *ctx)
|
||||
sysFatalError("Crash!\n\n%s", msg);
|
||||
}
|
||||
|
||||
/* D38: called by the kernel heartbeat watchdog (port/src/libultra.c) when a
|
||||
* hang is detected. A full cross-thread backtrace on POSIX would need a
|
||||
* signal-based stack-walk of every peer thread; for now log the live thread
|
||||
* roster plus the calling (watchdog) thread's own backtrace, which is enough
|
||||
* to see the watchdog fired and which threads were still up. */
|
||||
void crashDumpThreads(const unsigned long *tids, const char **names, int count)
|
||||
{
|
||||
char msg[CRASH_MAX_MSG + 1] = { 0 };
|
||||
unsigned msglen = 0;
|
||||
|
||||
CRASH_MSG("THREAD DUMP (%d live):\n", count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
CRASH_MSG(" tid=0x%lx %s\n", tids ? tids[i] : 0UL,
|
||||
(names && names[i]) ? names[i] : "?");
|
||||
}
|
||||
CRASH_MSG("\nWATCHDOG THREAD BACKTRACE:\n");
|
||||
{
|
||||
void *frames[CRASH_MAX_FRAMES] = { NULL };
|
||||
const int nframes = backtrace(frames, CRASH_MAX_FRAMES);
|
||||
char **strings = (nframes > 0) ? backtrace_symbols(frames, nframes) : NULL;
|
||||
for (int i = 0; i < nframes; ++i) {
|
||||
CRASH_MSG("#%02d: %s\n", i,
|
||||
(strings && strings[i]) ? strings[i] : "?");
|
||||
}
|
||||
free(strings);
|
||||
}
|
||||
sysLogPrintf(LOG_ERROR, "%s", msg);
|
||||
{
|
||||
FILE *f = fopen(CRASH_LOG_FNAME, "ab");
|
||||
if (f) { fprintf(f, "%s", msg); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PLATFORM_WINDOWS / PLATFORM_LINUX */
|
||||
|
||||
int g_CrashEnabled = 0;
|
||||
|
||||
+53
-1
@@ -173,8 +173,14 @@ static uint64_t portNextTimerUs(void);
|
||||
|
||||
static void portHeartbeatCheck(void)
|
||||
{
|
||||
/* A real hang is a permanent stall; transient >3 s gaps are normal on a
|
||||
* slow/software-GL/debugger setup (level loads, gdb pauses, SIGTERM
|
||||
* teardown) and were spamming ge007.crash.log with dozens of thread
|
||||
* dumps. Use a longer window and stop after a few reports. */
|
||||
static int fired = 0;
|
||||
uint64_t now = sysGetMicroseconds();
|
||||
if (now - g_lastFrameUs > 3000000 && now - g_lastHeartbeatUs > 2000000) {
|
||||
if (fired < 3 && now - g_lastFrameUs > 8000000 && now - g_lastHeartbeatUs > 5000000) {
|
||||
fired++;
|
||||
g_lastHeartbeatUs = now;
|
||||
sysLogPrintf(LOG_ERROR,
|
||||
"kernel heartbeat: no frame rendered for %llu ms (frames=%d); state:",
|
||||
@@ -334,6 +340,29 @@ void osCreateThread(OSThread *t, OSId id, void (*entry)(void *), void *arg,
|
||||
t->state = OS_STATE_STOPPED;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/mman.h>
|
||||
/* The decomp aligns/compares stack-buffer pointers by truncating to u32
|
||||
* (e.g. `(u32)compbuffer` in image.c texLoad) — an N64 assumption. glibc
|
||||
* puts pthread stacks above 4 GiB, so those truncations corrupt. Force each
|
||||
* game-thread stack into the low 2 GiB with MAP_32BIT so every such idiom
|
||||
* works exactly as on the console. Windows pthread stacks are already low. */
|
||||
static void *portAllocLowStack(size_t sz)
|
||||
{
|
||||
#if defined(MAP_32BIT)
|
||||
void *p = mmap(NULL, sz, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT
|
||||
#if defined(MAP_STACK)
|
||||
| MAP_STACK
|
||||
#endif
|
||||
, -1, 0);
|
||||
if (p != MAP_FAILED)
|
||||
return p;
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void osStartThread(OSThread *t)
|
||||
{
|
||||
PortThread *pt = portFind(t);
|
||||
@@ -344,7 +373,15 @@ void osStartThread(OSThread *t)
|
||||
pthread_attr_t attr;
|
||||
int rc;
|
||||
pthread_attr_init(&attr);
|
||||
#if !defined(_WIN32)
|
||||
void *lowStack = portAllocLowStack(PORT_THREAD_STACK);
|
||||
if (lowStack)
|
||||
pthread_attr_setstack(&attr, lowStack, PORT_THREAD_STACK);
|
||||
else
|
||||
pthread_attr_setstacksize(&attr, PORT_THREAD_STACK);
|
||||
#else
|
||||
pthread_attr_setstacksize(&attr, PORT_THREAD_STACK);
|
||||
#endif
|
||||
rc = pthread_create(&pt->th, &attr, portThreadWrapper, pt);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (rc != 0) {
|
||||
@@ -765,6 +802,7 @@ static int dramHostAddrValid(uintptr_t addr, u32 size)
|
||||
* buffers (e.g. ramrom_data_target), stack compbuffers, sidecar images.
|
||||
* Truncated wild addresses (0x40xxxxxx from s32 pointer math) are not
|
||||
* committed, so VirtualQuery still catches them. */
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
if (VirtualQuery((LPCVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi) &&
|
||||
@@ -774,6 +812,13 @@ static int dramHostAddrValid(uintptr_t addr, u32 size)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
/* No cheap committed-memory probe on POSIX; this is a TEMP D60 diagnostic.
|
||||
* Be permissive so legitimate .bss/stack/sidecar DMA targets are not
|
||||
* flagged as fatal (a genuinely wild target still faults in the memcpy). */
|
||||
(void)size;
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void piServiceDma(s32 direction, u32 srcPA, void *dstVA, u32 size)
|
||||
@@ -817,6 +862,7 @@ static void piServiceDma(s32 direction, u32 srcPA, void *dstVA, u32 size)
|
||||
wp += snprintf(wp, win + sizeof(win) - (wp - win),
|
||||
" %p", (void *)sp[i]);
|
||||
}
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
{
|
||||
PVOID tlow = NULL, thigh = NULL;
|
||||
GetCurrentThreadStackLimits(&tlow, &thigh);
|
||||
@@ -826,6 +872,12 @@ static void piServiceDma(s32 direction, u32 srcPA, void *dstVA, u32 size)
|
||||
((uintptr_t)dstVA >= (uintptr_t)tlow &&
|
||||
(uintptr_t)dstVA < (uintptr_t)thigh));
|
||||
}
|
||||
#else
|
||||
/* TEMP D60 diagnostic: no portable committed-stack query without
|
||||
* _GNU_SOURCE (pthread_getattr_np); the raw stack window above and
|
||||
* the crash handler's register dump are enough to symbolicate. */
|
||||
sysLogPrintf(LOG_ERROR, "D60 thread stack: (n/a on POSIX)\n");
|
||||
#endif
|
||||
sysLogPrintf(LOG_ERROR,
|
||||
"D60 BAD DMA TARGET dst=%p src=0x%08X size=0x%X "
|
||||
"stack@rbp:%s\n",
|
||||
|
||||
@@ -115,6 +115,7 @@ int main(int argc, char **argv)
|
||||
* loader loads at 0x140000000 and those relocations are no-ops; if we
|
||||
* ever got relocated, every such slot would be silently corrupted.
|
||||
* Fail loudly instead. */
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
if (sysImageBase() != 0x140000000ul) {
|
||||
sysLogPrintf(LOG_ERROR,
|
||||
"image loaded at %p, expected preferred base 0x140000000; "
|
||||
@@ -122,6 +123,11 @@ int main(int argc, char **argv)
|
||||
(void *)sysImageBase());
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
/* Linux/ELF no-PIE: dram_syms.s absolute symbols resolve to their literal
|
||||
* values independent of the image base (no .refptr indirection), so the
|
||||
* load address is not constrained. sysImageBase() is a stub here anyway. */
|
||||
#endif
|
||||
|
||||
/* 2b. Reserve the N64-DRAM region: s32-safe view @ 0x70000000 (cfb_16,
|
||||
* mempools) + KSEG0 mirror @ 0x80000000 (see port/src/dram.c). */
|
||||
|
||||
+6
-2
@@ -106,10 +106,14 @@ u8 (*tlbmanageGetTlbAllocatedBlock(void))[TLB_BLOCK_SIZE]
|
||||
return (u8 (*)[TLB_BLOCK_SIZE])0x70700000;
|
||||
}
|
||||
|
||||
/* --- K&R libc helpers (IDO provided these; the host libc does not) ------- */
|
||||
/* Signatures mirror include/PR/os.h:983 / include/bstring.h exactly. */
|
||||
/* --- K&R libc helpers (IDO provided these; MinGW's libc does not) -------- */
|
||||
/* Signatures mirror include/PR/os.h:983 / include/bstring.h exactly.
|
||||
* glibc still exports bcopy/bzero (declared in <strings.h> with size_t), so
|
||||
* on the POSIX port we use those and must NOT redefine them here. */
|
||||
#if defined(_WIN32)
|
||||
void bcopy(const void *src, void *dst, int n) { memmove(dst, src, (size_t)n); }
|
||||
void bzero(void *s, int n) { memset(s, 0, (size_t)n); }
|
||||
#endif
|
||||
|
||||
/* --- libm internals (IDO's libm exposed these; MinGW's does not) --------- */
|
||||
/* Quiet NaN float, used by gu/cosf.c and game/zlib.c. The VALUE is the same */
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include <string.h>
|
||||
|
||||
extern void *malloc(unsigned long long size);
|
||||
extern int snprintf(char *str, unsigned long long maxsize, const char *format, ...);
|
||||
extern int snprintf(char *str, size_t maxsize, const char *format, ...);
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
/* D38: <stdlib.h>/<string.h> resolve to the decomp's N64 stubs via the
|
||||
* include path; declare the host functions this file uses. */
|
||||
extern void *malloc(unsigned long long size);
|
||||
extern int snprintf(char *str, unsigned long long maxsize, const char *format, ...);
|
||||
extern int snprintf(char *str, size_t maxsize, const char *format, ...);
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
|
||||
+6
-5
@@ -23,7 +23,7 @@
|
||||
/* D38: <stdio.h>/<stdlib.h> resolve to the decomp's N64 stubs via the include
|
||||
* path; declare the host functions this file uses. */
|
||||
extern void *malloc(unsigned long long size);
|
||||
extern int snprintf(char *str, unsigned long long maxsize, const char *format, ...);
|
||||
extern int snprintf(char *str, size_t maxsize, const char *format, ...);
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
@@ -157,10 +157,11 @@ int romdataInit(void)
|
||||
uintptr_t p = (uintptr_t)&probe;
|
||||
uintptr_t hi = p & ~(uintptr_t)0xFFFFFFFFu;
|
||||
if (hi == 0) {
|
||||
sysLogPrintf(LOG_ERROR, "romdataInit: image base 0x%llX is not "
|
||||
"4GiB-aligned; ANIM_DATA_* offsets would be wrong",
|
||||
(unsigned long long)p);
|
||||
return -1;
|
||||
/* Low image base (Linux no-PIE loads at 0x400000). The ANIM_DATA_*
|
||||
* lvalues are only ever address-taken — `(s32)&ANIM_DATA_x` yields
|
||||
* the offset, never a dereference — so g_pc_animdata_base just has
|
||||
* to be some 4GiB-aligned address whose low 32 bits are zero. */
|
||||
hi = (uintptr_t)0x100000000ull;
|
||||
}
|
||||
g_pc_animdata_base = (u8 *)hi;
|
||||
}
|
||||
|
||||
+20
-11
@@ -100,6 +100,10 @@ static u32 frames = 0;
|
||||
/* Set by the host event pump (F12), consumed on the render thread in
|
||||
* videoEndFrame where a GL context is current. */
|
||||
static volatile int screenshotReq = 0;
|
||||
|
||||
/* Pre-swap capture hook (defined below, registered in videoInit). */
|
||||
static void videoPreSwapCapture(void);
|
||||
extern void (*gfx_pre_swap_hook)(void);
|
||||
static double fpsWindowStart = 0.0;
|
||||
static int fpsNumFrames = 0;
|
||||
static float vidAvgFPS = 0.f;
|
||||
@@ -172,6 +176,8 @@ int videoInit(void)
|
||||
* still needs a current context on this thread. */
|
||||
gfx_sdl_release_context();
|
||||
|
||||
gfx_pre_swap_hook = videoPreSwapCapture;
|
||||
|
||||
initDone = 1;
|
||||
sysLogPrintf(LOG_INFO, "video: %dx%d window (native %dx%d)",
|
||||
(int)gfx_current_dimensions.width, (int)gfx_current_dimensions.height,
|
||||
@@ -289,18 +295,13 @@ void videoSubmitCommands(Gfx *cmds)
|
||||
gfx_run(cmds);
|
||||
}
|
||||
|
||||
void videoEndFrame(void)
|
||||
/* Runs from gfx_sdl_swap_buffers_begin with the composited frame still in the
|
||||
* back buffer, just before SDL_GL_SwapWindow. Reading the back buffer after
|
||||
* the swap is undefined on buffer-exchange drivers (Mesa/WSLg) -> black. */
|
||||
static void videoPreSwapCapture(void)
|
||||
{
|
||||
if (!initDone) {
|
||||
return;
|
||||
}
|
||||
gfx_end_frame();
|
||||
|
||||
/* TEMP D70 (env-gated, debug-only, env-gated): GE_PCDUMP="first-last"
|
||||
* or "first-last:step" dumps the presented frame as ./ppm/frame_NNNNNN.ppm
|
||||
* (one frame behind real time — read happens after SwapWindow). Used to
|
||||
* pixel-verify the intro's 3D content. Also honours [Debug] FrameDump in
|
||||
* ge007.ini (env var wins). */
|
||||
/* GE_PCDUMP="first-last" / "first-last:step" -> ./ppm/frame_NNNNNN.ppm.
|
||||
* Also honours [Debug] FrameDump in ge007.ini (env var wins). */
|
||||
const char *pcdump = configGetFrameDump();
|
||||
if (pcdump) {
|
||||
static int lo = -1, hi = 0, step = 1;
|
||||
@@ -335,6 +336,14 @@ void videoEndFrame(void)
|
||||
sysLogPrintf(LOG_WARNING, "video: screenshot failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void videoEndFrame(void)
|
||||
{
|
||||
if (!initDone) {
|
||||
return;
|
||||
}
|
||||
gfx_end_frame();
|
||||
|
||||
++frames;
|
||||
++fpsNumFrames;
|
||||
|
||||
@@ -2435,7 +2435,14 @@ void texLoad(s32 *updateword, struct texpool *pool)
|
||||
|
||||
if (tex == NULL)
|
||||
{
|
||||
#ifdef PORT
|
||||
/* compbuffer is a stack array; (u32) truncates the 64-bit host stack
|
||||
* address (glibc pthread stacks live above 4GiB). Align at full
|
||||
* pointer width -- same result on N64, correct on the port. */
|
||||
alignedcompbuffer = (u8 *) (((uintptr_t)compbuffer + 0xF) & ~(uintptr_t)0xF);
|
||||
#else
|
||||
alignedcompbuffer = (u8 *) (((u32)compbuffer + 0xF) >> 4 << 4);
|
||||
#endif
|
||||
|
||||
if (alignedcompbuffer);
|
||||
if (tex);
|
||||
|
||||
@@ -9921,7 +9921,14 @@ void sub_GAME_7F04F244(PropRecord* prop, rect4f** polygon, s32* edges, f32* top,
|
||||
}
|
||||
|
||||
|
||||
#ifdef PORT
|
||||
/* GCC 14 rejects the vestigial u8* params (enum/int -> pointer is a hard
|
||||
error, not downgraded by -fpermissive). param2/param3 are unused; match
|
||||
the two callsites' actual types. Codegen-neutral. */
|
||||
void append_text_picked_up(u8 *buffer, AMMOTYPE param2, u32 param3)
|
||||
#else
|
||||
void append_text_picked_up(u8 *buffer,u8 * param2,u8 * param3)
|
||||
#endif
|
||||
{
|
||||
u8 *str;
|
||||
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ void sub_GAME_7F09BAC4(s32 find, s32 replacement) {
|
||||
while (var_a1 != NULL) {
|
||||
val = var_a1->Opcode & 0xFF;
|
||||
if (val == 0x18) {
|
||||
temp_v0_2 = modelGetNodeRwData(var_v0->chrflags, var_a1);
|
||||
temp_v0_2 = modelGetNodeRwData((Model*)var_v0->chrflags, var_a1);
|
||||
if (find == *temp_v0_2) {
|
||||
*temp_v0_2 = replacement;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user