forked from Ports/goldeneye-pc-port
Add PC port scaffolding (CMake build + port layer)
Initial scaffolding for a PC port of GoldenEye 007, modeled on the Perfect Dark PC port (same Rare "Indy" engine). The N64 build (Makefile + IDA) is untouched; the PC build is a parallel CMake build. Build: - CMakeLists.txt: SDL2 + zlib + OpenGL. Compiles src/game/*.c + curated engine files + libultra audio/gu + port/. Per-region macro sets (ROMID US/EU/JP) mirror the N64 Makefile's LCDEFS exactly. - build-pc.sh, cmake/: configure+build helper and target/SDL2 modules. Port layer (port/): - video/audio/input/fs/romdata/config/system/mixer: stubs with TODO(Phase N) markers and correct interfaces. - libultra.c: libultra OS shims (message queues, VI, PI, AI, EEPROM, controller, PFS/motor no-ops, region-gated osTvType). - gesched.c: software-RSP scheduler replacing src/sched.c task submission. - n64stubs.c: inert stubs for N64-boot + rmon symbols. - random.c: PRNG ported verbatim from src/random.s (bit-exact). - ucode.c: RSP/ASP/GSP microcode segment markers (dummies; Phase 3 TODO). Docs: - docs/PCPortResearch.md: architecture, GE-specific deltas, phased plan, and the scaffolding review (findings A1-A4/B1-B4/C1-C2 + resolutions). - PORTING.md: top-level entry point. See docs/PCPortResearch.md for the full plan.
This commit is contained in:
1 parent
ef209c113c
commit
cd6cc158b6
37 files changed
+3092
-1
No files matched your search
+6
-1
@@ -67,6 +67,11 @@ bin/*
|
||||
build/*
|
||||
build-*/*
|
||||
notes/*
|
||||
|
||||
# PC port (see CMakeLists.txt / port/)
|
||||
build-pc/
|
||||
data/
|
||||
ge007.ini
|
||||
!build/test.c
|
||||
!notes/StyleGuide.md
|
||||
#tools
|
||||
@@ -111,4 +116,4 @@ assets/obseg/em/data/under.seg
|
||||
|
||||
scripts/local/
|
||||
scripts/make/local/
|
||||
scripts/__pycache__/generate_prop_model_c.cpython-310.pyc
|
||||
scripts/__pycache__/generate_prop_model_c.cpython-310.pyc
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
# ============================================================================
|
||||
# GoldenEye 007 — PC port build
|
||||
#
|
||||
# This is a PARALLEL build system to the N64 `Makefile`. It compiles the same
|
||||
# game C sources (src/game, src) for a modern host (Windows/Linux/macOS)
|
||||
# against the PC shims in `port/`. The N64 build is untouched.
|
||||
#
|
||||
# See docs/PCPortResearch.md for the architecture and plan.
|
||||
# Modelled on the Perfect Dark PC port (perfect-dark-pc-port/perfect_dark).
|
||||
#
|
||||
# Usage:
|
||||
# cmake -S . -B build-pc -DROMID=ntsc-final
|
||||
# cmake --build build-pc -j
|
||||
# ============================================================================
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||
|
||||
if(APPLE)
|
||||
if(CMAKE_OSX_ARCHITECTURES MATCHES "arm64")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "" FORCE)
|
||||
else()
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
project(ge007 C CXX)
|
||||
|
||||
include(TargetArch)
|
||||
include(FindPkgConfig)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Default build type
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt)
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Options
|
||||
# ----------------------------------------------------------------------------
|
||||
# Which ROM the executable expects in the data/ dir.
|
||||
set(ROMID "ntsc-final" CACHE STRING "ROM id: ntsc-final (US), pal-final (EU), jpn-final (JP)")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Target arch / platform
|
||||
# ----------------------------------------------------------------------------
|
||||
target_architecture(TARGET_ARCH)
|
||||
if(TARGET_ARCH MATCHES "64")
|
||||
set(TARGET_IS_64BIT TRUE)
|
||||
else()
|
||||
set(TARGET_IS_64BIT FALSE)
|
||||
endif()
|
||||
message(STATUS "Target arch: ${TARGET_ARCH} (64bit=${TARGET_IS_64BIT})")
|
||||
|
||||
if(WIN32)
|
||||
set(TARGET_OS "windows")
|
||||
elseif(APPLE)
|
||||
set(TARGET_OS "osx")
|
||||
elseif(UNIX)
|
||||
set(TARGET_OS "linux")
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown target OS")
|
||||
endif()
|
||||
set(TARGET_PLATFORM "${TARGET_ARCH}-${TARGET_OS}")
|
||||
message(STATUS "Target platform: ${TARGET_PLATFORM}")
|
||||
|
||||
# Region -> compile definitions.
|
||||
#
|
||||
# The game code #ifdefs a full per-region macro set (see the N64 Makefile,
|
||||
# the VERSION blocks). e.g. VERSION_EU is used in 60+ files, LEFTOVERDEBUG in
|
||||
# 26, BUGFIX_R1 in 21. We MUST emit the same set the N64 build uses for the
|
||||
# matching region, or the PC build takes different conditional branches and
|
||||
# silently diverges (and fails to link). These mirror Makefile LCDEFS exactly.
|
||||
if(ROMID STREQUAL "ntsc-final")
|
||||
set(VERSION_IS_PAL 0)
|
||||
set(BIN_REGION "")
|
||||
set(REGION_DEFS
|
||||
-DVERSION_US -DLANG_US -DREFRESH_NTSC
|
||||
-DLEFTOVERDEBUG -DLEFTOVERSPECTRUM -DBUGFIX_R0 -DBYTEMATCH)
|
||||
elseif(ROMID STREQUAL "pal-final")
|
||||
set(VERSION_IS_PAL 1)
|
||||
set(BIN_REGION ".pal")
|
||||
set(REGION_DEFS
|
||||
-DVERSION_EU -DLANG_EU -DREFRESH_PAL
|
||||
-DBUGFIX_R1 -DBUGFIX_R2 -DBYTEMATCH)
|
||||
elseif(ROMID STREQUAL "jpn-final")
|
||||
set(VERSION_IS_PAL 0)
|
||||
set(BIN_REGION ".jpn")
|
||||
set(REGION_DEFS
|
||||
-DVERSION_JP -DLANG_JP -DREFRESH_NTSC
|
||||
-DBUGFIX_R1 -DLEFTOVERDEBUG -DLEFTOVERSPECTRUM -DBYTEMATCH)
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown ROMID: ${ROMID}")
|
||||
endif()
|
||||
|
||||
# git hash for version info
|
||||
execute_process(COMMAND git rev-parse --short HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE VERSION_HASH OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
if(NOT VERSION_HASH)
|
||||
set(VERSION_HASH "unknown")
|
||||
endif()
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/port/include/versioninfo.h.in"
|
||||
"${CMAKE_BINARY_DIR}/port/include/versioninfo.h" @ONLY)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Compiler definitions & flags
|
||||
# ----------------------------------------------------------------------------
|
||||
add_definitions(
|
||||
-DPORT=1
|
||||
-D_LANGUAGE_C=1
|
||||
-DAVOID_UB=1
|
||||
-DPAL=${VERSION_IS_PAL}
|
||||
${REGION_DEFS}
|
||||
)
|
||||
if(TARGET_IS_64BIT)
|
||||
add_definitions(-DPLATFORM_64BIT=1)
|
||||
endif()
|
||||
|
||||
# The decomp is big-endian N64 code with unaligned accesses; these flags are
|
||||
# required to compile it cleanly on a little-endian host (same as the PD port).
|
||||
add_compile_options(
|
||||
-fno-strict-aliasing
|
||||
-funsigned-char
|
||||
-fwrapv
|
||||
-include "versioninfo.h"
|
||||
)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_compile_options(-Og)
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
-Wall
|
||||
-Wno-address
|
||||
-Wno-misleading-indentation
|
||||
-Wno-missing-braces
|
||||
-Wno-multichar
|
||||
-Wno-tautological-compare
|
||||
-Wno-unused-but-set-variable
|
||||
-Wno-unused-value
|
||||
-Wno-unused-variable
|
||||
)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-unused-function>)
|
||||
|
||||
include_directories(
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}/include/PR"
|
||||
"${CMAKE_SOURCE_DIR}/src"
|
||||
"${CMAKE_SOURCE_DIR}/src/game"
|
||||
"${CMAKE_SOURCE_DIR}/src/libultra"
|
||||
"${CMAKE_SOURCE_DIR}/src/libultra/audio"
|
||||
"${CMAKE_SOURCE_DIR}/port/include"
|
||||
"${CMAKE_BINARY_DIR}/port/include"
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Dependencies: SDL2, zlib, OpenGL
|
||||
# ----------------------------------------------------------------------------
|
||||
set(LIBS "")
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
include_directories(SYSTEM "${SDL2_INCLUDE_DIR}")
|
||||
list(APPEND LIBS "${SDL2_LIBRARY}")
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
if(ZLIB_INCLUDE_DIR)
|
||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIR}")
|
||||
endif()
|
||||
list(APPEND LIBS "${ZLIB_LIBRARY}")
|
||||
|
||||
if(NOT GL_LIBRARY)
|
||||
if(WIN32)
|
||||
set(GL_LIBRARY opengl32)
|
||||
elseif(APPLE)
|
||||
set(GL_LIBRARY "-framework OpenGL")
|
||||
else()
|
||||
set(GL_LIBRARY GL)
|
||||
endif()
|
||||
endif()
|
||||
list(APPEND LIBS "${GL_LIBRARY}")
|
||||
|
||||
if(NOT EXTRA_LIBRARIES)
|
||||
if(WIN32)
|
||||
set(EXTRA_LIBRARIES dbghelp -static-libstdc++)
|
||||
else()
|
||||
set(EXTRA_LIBRARIES stdc++ m dl pthread)
|
||||
endif()
|
||||
endif()
|
||||
list(APPEND LIBS "${EXTRA_LIBRARIES}")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Sources
|
||||
# ----------------------------------------------------------------------------
|
||||
# Port layer (shims + software RSP).
|
||||
file(GLOB_RECURSE SRC_PORT "${CMAKE_SOURCE_DIR}/port/src/*.c")
|
||||
file(GLOB_RECURSE SRC_PORT_CXX "${CMAKE_SOURCE_DIR}/port/src/*.cpp")
|
||||
file(GLOB_RECURSE SRC_FAST3D "${CMAKE_SOURCE_DIR}/port/fast3d/*.cpp")
|
||||
|
||||
# Game code — compiled unmodified.
|
||||
file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c")
|
||||
|
||||
# libaudio (AL) — runs on the CPU, platform independent.
|
||||
file(GLOB SRC_LIBAUDIO "${CMAKE_SOURCE_DIR}/src/libultra/audio/*.c")
|
||||
# Rare's audio "New" driver (drvrNew/env/reverb) — synthesizer.c calls
|
||||
# alSaveNew/alAuxBusNew/alMainBusNew/alLoadNew/alResampleNew/alEnvmixerNew/
|
||||
# alEnvmixerParam/alFxParam, all defined here. Runs on the CPU; must be linked.
|
||||
file(GLOB SRC_LIBULTRARE_AUDIO "${CMAKE_SOURCE_DIR}/src/libultrare/audio/*.c")
|
||||
# gu matrix helpers.
|
||||
file(GLOB SRC_GU "${CMAKE_SOURCE_DIR}/src/libultra/gu/*.c")
|
||||
|
||||
# VI mode table (DATA, not a driver): fr.c references osViModeTable ~15x.
|
||||
# This is the only libultra/io file we compile — the rest are hardware
|
||||
# drivers that get shimmed in port/src/libultra.c.
|
||||
set(SRC_LIBULTRARE_DATA
|
||||
src/libultrare/io/vitbl.c
|
||||
)
|
||||
list(PREPEND SRC_LIBULTRARE_DATA "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
# Engine files in src/ that are platform independent.
|
||||
# NOTE: this list is a starting point and must be curated during Phase 1.
|
||||
#
|
||||
# INCLUDED here (platform-independent or CPU-side):
|
||||
# init.c — provides mainproc() (the game's real main entry) + the thread
|
||||
# setup helpers. Its N64-only init() is compiled but NOT called;
|
||||
# the N64 symbols init() references are stubbed in
|
||||
# port/src/n64stubs.c (see A3 in docs/PCPortResearch.md).
|
||||
# cfb.c — defines cfb_16 (the framebuffer), referenced by fr.c and game
|
||||
# code (bondview2.c, front.c). Pure data.
|
||||
# stacks.c — thread-stack arrays (sp_boot/sp_rmon/sp_idle/sp_shed/sp_main/
|
||||
# sp_audi + sp_debug under LEFTOVERDEBUG). Pure data; referenced
|
||||
# by init.c (thread setup) and audi.c (sp_audi). The SP_*_SZ
|
||||
# sizes in bondconstants.h match these exactly, so sizeof() is
|
||||
# correct. No duplicate-definition risk (all other refs are
|
||||
# extern decls in bondgame.h / audi.c).
|
||||
#
|
||||
# EXCLUDED (hardware drivers, replaced by port/ shims):
|
||||
# sched.c crash.c rmon.c usb.c tlb_*.c pi.c vi.c motor.c
|
||||
# (there is no src/spectrum.c in this decomp)
|
||||
# All of src/libultra/io/*.c and src/libultrare/io/*.c EXCEPT vitbl.c.
|
||||
# motor.c / pfsinit.c / pfsisplug.c are NOT compiled: joy.c calls
|
||||
# osMotorInit/Start/Stop and osPfsInit/osPfsIsPlug only for accessory
|
||||
# (Rumble Pak / Memory Pak) detection, so they are shimmed as no-ops in
|
||||
# port/src/libultra.c (see B2 — saves are EEPROM, not PFS).
|
||||
set(SRC_ENGINE
|
||||
src/init.c
|
||||
src/audi.c
|
||||
src/snd.c
|
||||
src/music.c
|
||||
src/joy.c
|
||||
src/fr.c
|
||||
src/cfb.c
|
||||
src/stacks.c
|
||||
src/boss.c
|
||||
src/mema.c
|
||||
src/memp.c
|
||||
src/deb.c
|
||||
src/debugmenu.c
|
||||
src/speed_graph.c
|
||||
src/token.c
|
||||
src/str.c
|
||||
src/sprintf.c
|
||||
src/ramrom.c
|
||||
)
|
||||
list(PREPEND SRC_ENGINE "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
set(SRC
|
||||
${SRC_GAME}
|
||||
${SRC_PORT}
|
||||
${SRC_PORT_CXX}
|
||||
${SRC_FAST3D}
|
||||
${SRC_LIBAUDIO}
|
||||
${SRC_LIBULTRARE_AUDIO}
|
||||
${SRC_LIBULTRARE_DATA}
|
||||
${SRC_GU}
|
||||
${SRC_ENGINE}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Target
|
||||
# ----------------------------------------------------------------------------
|
||||
set(BIN_NAME "ge007${BIN_REGION}.${TARGET_ARCH}")
|
||||
add_executable(ge007 ${SRC})
|
||||
target_link_libraries(ge007 ${LIBS})
|
||||
set_target_properties(ge007 PROPERTIES OUTPUT_NAME "${BIN_NAME}")
|
||||
if(WIN32)
|
||||
set_target_properties(ge007 PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
|
||||
message(STATUS "Output binary: ${BIN_NAME}")
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# GoldenEye 007 — PC Port
|
||||
|
||||
A work-in-progress port of [GoldenEye 007](https://en.wikipedia.org/wiki/GoldenEye_007)
|
||||
to modern platforms (Windows / Linux / macOS), built on the decompilation in
|
||||
this repository.
|
||||
|
||||
It follows the approach of the
|
||||
[Perfect Dark PC port](https://github.com/perfect-dark-pc-port/perfect_dark),
|
||||
which ports the same Rare "Indy" engine (a later revision).
|
||||
|
||||
> **Status: scaffolding.** The directory structure, build system, and port-layer
|
||||
> interfaces are in place. The actual implementation is being brought in phase
|
||||
> by phase. See below.
|
||||
|
||||
## Read this first
|
||||
|
||||
* **[docs/PCPortResearch.md](docs/PCPortResearch.md)** — the preliminary
|
||||
research: how the PD port works, what GE-specific work remains (the `G_SETTEX`
|
||||
RSP command, custom color-combiner/render modes), the hardware surfaces that
|
||||
must be shimmed, and the phased plan. **Start here.**
|
||||
* **[port/README.md](port/README.md)** — the port layer layout and how it fits
|
||||
together.
|
||||
|
||||
## How it works (one paragraph)
|
||||
|
||||
The N64 game code (`src/game`, `src`) is compiled **unmodified** for the host.
|
||||
The **RSP (the N64's 3D coprocessor) is emulated in software**
|
||||
(`port/fast3d/`): it interprets the display list the game builds and emits
|
||||
OpenGL calls, bypassing the RDP. All other N64 hardware (VI, AI, PI, SI, OS
|
||||
threads/timers) is shimmed in `port/src/libultra.c` and friends. Saves
|
||||
(Memory Pak / EEPROM) are backed by files.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
CMakeLists.txt # PC build (parallel to the N64 Makefile)
|
||||
cmake/ # CMake helper modules
|
||||
build-pc.sh # configure + build helper
|
||||
port/
|
||||
├── fast3d/ # software RSP (adapted from the PD port)
|
||||
├── include/ # port-facing headers
|
||||
└── src/ # port layer (main, libultra shims, video, audio, input, ...)
|
||||
docs/PCPortResearch.md
|
||||
```
|
||||
|
||||
The N64 build (`Makefile`, `tools/`, `rsp/`, etc.) is **untouched**.
|
||||
|
||||
## Building (once implementation lands)
|
||||
|
||||
```sh
|
||||
./build-pc.sh ntsc-final # or pal-final / jpn-final
|
||||
```
|
||||
|
||||
Then put your ROM in `./data/` and run the binary. (The ROM is not
|
||||
distributed; you must own the game.)
|
||||
|
||||
## Phased plan (summary)
|
||||
|
||||
| Phase | Goal |
|
||||
|-------|------|
|
||||
| 0 | Scaffolding (this change) |
|
||||
| 1 | Boot to a window: system/fs/rom/config/video + OS shims |
|
||||
| 2 | Rendering: bring in fast3d, add `G_SETTEX`, port scheduler |
|
||||
| 3 | Audio + input |
|
||||
| 4 | Saves + polish (widescreen, mouse-look, FPS, config) |
|
||||
|
||||
Details and risks: [docs/PCPortResearch.md §8–9](docs/PCPortResearch.md).
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Build the GoldenEye 007 PC port.
|
||||
#
|
||||
# Usage:
|
||||
# ./build-pc.sh [ntsc-final|pal-final|jpn-final]
|
||||
#
|
||||
# Dependencies (see docs/PCPortResearch.md):
|
||||
# - CMake >= 3.16
|
||||
# - SDL2 dev
|
||||
# - zlib dev
|
||||
# - OpenGL dev (opengl32 on Windows, GL on Linux, OpenGL.framework on macOS)
|
||||
#
|
||||
# Example (Linux):
|
||||
# sudo apt install cmake libsdl2-dev zlib1g-dev libgl4-mesa-dev
|
||||
# Example (macOS):
|
||||
# brew install cmake sdl2 zlib
|
||||
# Example (Windows/MSYS2):
|
||||
# pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-SDL2 \
|
||||
# mingw-w64-x86_64-zlib mingw-w64-x86_64-cmake
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROMID="${1:-ntsc-final}"
|
||||
BUILD_DIR="build-pc"
|
||||
|
||||
echo "==> Configuring PC port (ROMID=${ROMID})"
|
||||
cmake -S . -B "${BUILD_DIR}" -DROMID="${ROMID}"
|
||||
|
||||
echo "==> Building"
|
||||
cmake --build "${BUILD_DIR}" -j
|
||||
|
||||
echo "==> Done."
|
||||
echo " Binary: ${BUILD_DIR}/ge007.*"
|
||||
echo " Put your ROM in ./data/ (see README) and run the binary."
|
||||
@@ -0,0 +1,172 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
# Locate SDL2 library
|
||||
# This module defines
|
||||
# SDL2_LIBRARY, the name of the library to link against
|
||||
# SDL2_FOUND, if false, do not try to link to SDL2
|
||||
# SDL2_INCLUDE_DIR, where to find SDL.h
|
||||
#
|
||||
# This module responds to the the flag:
|
||||
# SDL2_BUILDING_LIBRARY
|
||||
# If this is defined, then no SDL2_main will be linked in because
|
||||
# only applications need main().
|
||||
# Otherwise, it is assumed you are building an application and this
|
||||
# module will attempt to locate and set the the proper link flags
|
||||
# as part of the returned SDL2_LIBRARY variable.
|
||||
#
|
||||
# Don't forget to include SDL2main.h and SDL2main.m your project for the
|
||||
# OS X framework based version. (Other versions link to -lSDL2main which
|
||||
# this module will try to find on your behalf.) Also for OS X, this
|
||||
# module will automatically add the -framework Cocoa on your behalf.
|
||||
#
|
||||
#
|
||||
# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
|
||||
# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
|
||||
# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
|
||||
# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
|
||||
# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
|
||||
# as appropriate. These values are used to generate the final SDL2_LIBRARY
|
||||
# variable, but when these values are unset, SDL2_LIBRARY does not get created.
|
||||
#
|
||||
#
|
||||
# $SDL2DIR is an environment variable that would
|
||||
# correspond to the ./configure --prefix=$SDL2DIR
|
||||
# used in building SDL2.
|
||||
# l.e.galup 9-20-02
|
||||
#
|
||||
# Modified by Eric Wing.
|
||||
# Added code to assist with automated building by using environmental variables
|
||||
# and providing a more controlled/consistent search behavior.
|
||||
# Added new modifications to recognize OS X frameworks and
|
||||
# additional Unix paths (FreeBSD, etc).
|
||||
# Also corrected the header search path to follow "proper" SDL2 guidelines.
|
||||
# Added a search for SDL2main which is needed by some platforms.
|
||||
# Added a search for threads which is needed by some platforms.
|
||||
# Added needed compile switches for MinGW.
|
||||
#
|
||||
# On OSX, this will prefer the Framework version (if found) over others.
|
||||
# People will have to manually change the cache values of
|
||||
# SDL2_LIBRARY to override this selection or set the CMake environment
|
||||
# CMAKE_INCLUDE_PATH to modify the search paths.
|
||||
#
|
||||
# Note that the header path has changed from SDL2/SDL.h to just SDL.h
|
||||
# This needed to change because "proper" SDL2 convention
|
||||
# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
|
||||
# reasons because not all systems place things in SDL2/ (see FreeBSD).
|
||||
#
|
||||
# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake
|
||||
# module with the minor edit of changing "SDL" to "SDL2" where necessary. This
|
||||
# was not created for redistribution, and exists temporarily pending official
|
||||
# SDL2 CMake modules.
|
||||
|
||||
FIND_PATH(SDL2_INCLUDE_DIR SDL.h
|
||||
HINTS
|
||||
$ENV{SDL2DIR}
|
||||
PATH_SUFFIXES include/SDL2 include
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/include/SDL2
|
||||
/usr/include/SDL2
|
||||
/sw # Fink
|
||||
/opt/local # DarwinPorts
|
||||
/opt/csw # Blastwave
|
||||
/opt
|
||||
/boot/system/develop/headers/SDL2 #Hiaku OS
|
||||
)
|
||||
#MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}")
|
||||
|
||||
FIND_LIBRARY(SDL2_LIBRARY_TEMP
|
||||
NAMES SDL2
|
||||
HINTS
|
||||
$ENV{SDL2DIR}
|
||||
PATH_SUFFIXES lib64 lib
|
||||
PATHS
|
||||
/sw
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
/system/lib #Hiaku OS
|
||||
)
|
||||
|
||||
#MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}")
|
||||
|
||||
IF(NOT SDL2_BUILDING_LIBRARY)
|
||||
IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
|
||||
# Non-OS X framework versions expect you to also dynamically link to
|
||||
# SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
|
||||
# seem to provide SDL2main for compatibility even though they don't
|
||||
# necessarily need it.
|
||||
FIND_LIBRARY(SDL2MAIN_LIBRARY
|
||||
NAMES SDL2main
|
||||
HINTS
|
||||
$ENV{SDL2DIR}
|
||||
PATH_SUFFIXES lib64 lib
|
||||
PATHS
|
||||
/sw
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
)
|
||||
ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
|
||||
ENDIF(NOT SDL2_BUILDING_LIBRARY)
|
||||
|
||||
# SDL2 may require threads on your system.
|
||||
# The Apple build may not need an explicit flag because one of the
|
||||
# frameworks may already provide it.
|
||||
# But for non-OSX systems, I will use the CMake Threads package.
|
||||
IF(NOT APPLE)
|
||||
FIND_PACKAGE(Threads)
|
||||
ENDIF(NOT APPLE)
|
||||
|
||||
# MinGW needs an additional library, mwindows
|
||||
# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows
|
||||
# (Actually on second look, I think it only needs one of the m* libraries.)
|
||||
IF(MINGW)
|
||||
SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
|
||||
ENDIF(MINGW)
|
||||
|
||||
SET(SDL2_FOUND "NO")
|
||||
IF(SDL2_LIBRARY_TEMP)
|
||||
# For SDL2main
|
||||
IF(NOT SDL2_BUILDING_LIBRARY)
|
||||
IF(SDL2MAIN_LIBRARY)
|
||||
SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP})
|
||||
ENDIF(SDL2MAIN_LIBRARY)
|
||||
ENDIF(NOT SDL2_BUILDING_LIBRARY)
|
||||
|
||||
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
|
||||
# CMake doesn't display the -framework Cocoa string in the UI even
|
||||
# though it actually is there if I modify a pre-used variable.
|
||||
# I think it has something to do with the CACHE STRING.
|
||||
# So I use a temporary variable until the end so I can set the
|
||||
# "real" variable in one-shot.
|
||||
IF(APPLE)
|
||||
SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa")
|
||||
ENDIF(APPLE)
|
||||
|
||||
# For threads, as mentioned Apple doesn't need this.
|
||||
# In fact, there seems to be a problem if I used the Threads package
|
||||
# and try using this line, so I'm just skipping it entirely for OS X.
|
||||
IF(NOT APPLE)
|
||||
SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
|
||||
ENDIF(NOT APPLE)
|
||||
|
||||
# For MinGW library
|
||||
IF(MINGW)
|
||||
SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})
|
||||
ENDIF(MINGW)
|
||||
|
||||
# Set the final string here so the GUI reflects the final state.
|
||||
SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found")
|
||||
# Set the temp variable to INTERNAL so it is not seen in the CMake GUI
|
||||
SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "")
|
||||
|
||||
SET(SDL2_FOUND "YES")
|
||||
ENDIF(SDL2_LIBRARY_TEMP)
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2
|
||||
REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR)
|
||||
@@ -0,0 +1,28 @@
|
||||
# Determine the target architecture and set TARGET_ARCH / TARGET_IS_64BIT.
|
||||
# Modelled on the Perfect Dark PC port's cmake/TargetArch.cmake.
|
||||
|
||||
function(target_architecture OUT_VAR)
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
# Derive from the cross-compiler target, e.g. x86_64-w64-mingw32-gcc
|
||||
string(REGEX MATCH "^(i[3-6]86|x86_64|aarch64|arm|powerpc|wasm32)" _arch
|
||||
"${CMAKE_C_COMPILER_TARGET}")
|
||||
else()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
|
||||
set(_arch "x86_64")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3-6]86|x86)$")
|
||||
set(_arch "i686")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$")
|
||||
set(_arch "aarch64")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm)$")
|
||||
set(_arch "arm")
|
||||
else()
|
||||
set(_arch "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(_arch STREQUAL "i386")
|
||||
set(_arch "i686")
|
||||
endif()
|
||||
|
||||
set(${OUT_VAR} "${_arch}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -0,0 +1,485 @@
|
||||
# GoldenEye 007 — PC Port: Preliminary Research
|
||||
|
||||
This document captures the preliminary research done to plan a PC port of
|
||||
GoldenEye 007, based on the decompilation in this repository
|
||||
(`n64decomp/007`). It is modelled on the approach used by the
|
||||
[Perfect Dark PC port](https://github.com/perfect-dark-pc-port/perfect_dark),
|
||||
which ports the same Rare "Indy" engine (a later revision) to modern
|
||||
platforms.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
* **Feasibility: HIGH.** GoldenEye and Perfect Dark share the same Rare
|
||||
engine family. The PD port proves the whole approach works end-to-end
|
||||
(singleplayer + split-screen multiplayer, mouse-look, widescreen, 60 FPS,
|
||||
mods). GE is, if anything, a *simpler* graphics pipeline than PD in some
|
||||
respects, though it has a few custom RSP extensions (see §5).
|
||||
* **Recommended strategy: fork this decomp and add a `port/` layer**, exactly
|
||||
mirroring the PD port's layout. The N64 build (Makefile + IDO) stays
|
||||
untouched; the PC build is a parallel CMake build that compiles the same
|
||||
game C sources against a set of PC shims.
|
||||
* **The single hardest component is the RSP (Reality Signal Processor)
|
||||
emulator.** The PD port ships a complete, working "fast3d" software RSP
|
||||
(`port/fast3d/`, ~7k lines of C++) that interprets GBI display lists and
|
||||
emits OpenGL calls. It **already handles `G_TRI4`**, which is the main
|
||||
custom triangle command GE uses. GE's other custom command, `G_SETTEX`,
|
||||
appears **unused** (its emitter `gsSPUseTexture` is never called), so the
|
||||
remaining GE-specific work is mostly **verifying the custom
|
||||
render/color-combiner modes** against the RSP ucode.
|
||||
* **Audio is straightforward.** GE uses the standard libaudio (AL) engine on
|
||||
the R4300, the same as PD. The PD port's SDL audio + mixer approach carries
|
||||
over directly.
|
||||
* **Saves** are **EEPROM-based** (controller EEPROM via `osEeprom*`), not
|
||||
Memory Pak. The PFS/motor code in `joy.c` is only for accessory (Rumble Pak
|
||||
/ Memory Pak) detection and can be stubbed. Phase 4 is therefore a
|
||||
file-backed EEPROM plus no-op PFS/motor shims — simpler than a full Memory
|
||||
Pak emulation.
|
||||
|
||||
---
|
||||
|
||||
## 2. What the PD port actually does (the reference architecture)
|
||||
|
||||
The PD port is a **fork of the PD decomp** with one added top-level
|
||||
directory, `port/`. The original game code (`src/game/*.c`, `src/lib/*.c`)
|
||||
is compiled **unmodified** for the PC. All N64-hardware dependencies are
|
||||
satisfied by the `port/` layer:
|
||||
|
||||
```
|
||||
port/
|
||||
├── fast3d/ # Software RSP: interprets GBI display lists -> OpenGL
|
||||
│ ├── gfx_pc.cpp # the RSP interpreter (matrices, lights, tris, fog)
|
||||
│ ├── gfx_opengl.cpp # OpenGL rendering backend (shaders, textures, zbuf)
|
||||
│ ├── gfx_sdl2.cpp # SDL2 window-manager backend
|
||||
│ ├── gfx_cc.cpp # color-combiner -> GLSL shader compiler
|
||||
│ └── ...
|
||||
├── include/ # port-facing headers
|
||||
│ ├── audio.h config.h fs.h input.h mixer.h mod.h
|
||||
│ ├── romdata.h system.h video.h utils.h platform.h
|
||||
└── src/
|
||||
├── main.c # PC entry point (replaces boot.s / _start.s)
|
||||
├── libultra.c # shims for the libultra OS API (threads, time, VI, PI, SI, SP, AI)
|
||||
├── video.c # SDL2 window + GL context + frame pacing
|
||||
├── audio.c # SDL audio device, buffer queueing
|
||||
├── mixer.c # audio mixing / RSP-audio-buffer processing
|
||||
├── mod.c # MOD music playback
|
||||
├── input.c # SDL2 keyboard/mouse/gamepad -> N64 controller structs
|
||||
├── fs.c # filesystem abstraction over the ROM + data dir
|
||||
├── romdata.c # loads the .z64 ROM from disk, sets up segments
|
||||
├── config.c # INI config (pd.ini)
|
||||
├── system.c # platform primitives (time, logging, paths)
|
||||
├── crash.c # crash handler / stack traces
|
||||
├── pdsched.c # replacement RCP scheduler that drives the software RSP
|
||||
└── utils.c
|
||||
```
|
||||
|
||||
### 2.1 The key architectural insight: emulate the RSP, bypass the RDP
|
||||
|
||||
On real hardware the render path is:
|
||||
|
||||
```
|
||||
R4300 builds a Gfx display list
|
||||
|
|
||||
v
|
||||
RSP (coproc 0) runs "fast3d" ucode, turns GBI cmds into RDP cmds
|
||||
|
|
||||
v
|
||||
RDP (coproc 1) rasterizes into the framebuffer
|
||||
```
|
||||
|
||||
The PD port **emulates the RSP in software** (`fast3d/gfx_pc.cpp`) and
|
||||
**bypasses the RDP entirely**: the software RSP translates GBI commands
|
||||
directly into OpenGL calls. The R4300 game code is run natively and is
|
||||
unaware of the difference — it just builds display lists and "starts the
|
||||
RSP" as usual.
|
||||
|
||||
The frame flow (from `port/src/pdsched.c` + `port/src/video.c`):
|
||||
|
||||
```
|
||||
videoStartFrame() -> gfx_start_frame()
|
||||
game builds display list (Gfx*)
|
||||
videoSubmitCommands(Gfx* cmds) -> gfx_run(cmds) # <- the software RSP runs here
|
||||
videoEndFrame() -> gfx_end_frame() # swap buffers, FPS accounting
|
||||
```
|
||||
|
||||
### 2.2 How the RSP is invoked
|
||||
|
||||
The game's RCP scheduler (`src/lib/sched.c` in PD, `src/sched.c` in GE) calls
|
||||
`osSpTaskLoad()` + `osSpTaskStartGo()` to hand a task (gfx or audio) to the
|
||||
RSP. The port:
|
||||
|
||||
* **Excludes** the original `libultra/io/sptask.c` (and the other `io/*.c`
|
||||
hardware drivers) from the PC build.
|
||||
* Provides its own scheduler (`pdsched.c`) that, instead of writing to RSP
|
||||
registers, calls `gfx_run()` on the display list for gfx tasks and feeds the
|
||||
audio buffer for audio tasks.
|
||||
* Shims the rest of the libultra OS API in `libultra.c` (threads become
|
||||
no-ops / single-threaded, `osGetTime` maps to a host clock, VI/PI/SI/AI
|
||||
functions map to the port's video/audio/fs/input layers).
|
||||
|
||||
### 2.3 Build system
|
||||
|
||||
The PC build is **CMake** (the N64 build stays Make + IDO). Dependencies:
|
||||
**SDL2, zlib, OpenGL** (plus `stdc++`, `m`, `dl`). It compiles:
|
||||
|
||||
* `src/game/*.c` (all of it)
|
||||
* a curated list of `src/lib/*.c` (the engine helpers that are
|
||||
platform-independent: mema, memp, model, music, snd, vi, rdp, etc.)
|
||||
* `src/lib/ultra/audio/*.c` (libaudio — runs fine on the CPU)
|
||||
* `src/lib/ultra/gu/*.c` (gu matrix helpers)
|
||||
* `port/src/*.c` + `port/src/*.cpp` + `port/fast3d/*.cpp`
|
||||
|
||||
It deliberately **excludes** the N64 I/O drivers (`src/lib/ultra/io/*.c`),
|
||||
the boot/TLB assembly, and the RSP microcode (`.s` under `rsp/`).
|
||||
|
||||
---
|
||||
|
||||
## 3. GoldenEye decomp: what we're porting
|
||||
|
||||
* **Repo:** `n64decomp/007` (this repo). WIP decomp that byte-matches the
|
||||
US/EU/JP ROMs. Game code lives in `src/game/` (~242 files) plus engine
|
||||
files in `src/`.
|
||||
* **Engine layout** (same family as PD):
|
||||
* `src/sched.c` — RCP scheduler (`osCreateScheduler`, `__scExec`,
|
||||
`__scYield`). Structurally near-identical to PD's `src/lib/sched.c`.
|
||||
* `src/game/rsp.c` — builds the Gfx display list, wraps it in an `OSScTask`,
|
||||
and starts it via the scheduler (`rspGfxTaskStart`).
|
||||
* `src/fr.c` — frame/VI management, video modes (`osViSetMode`).
|
||||
* `src/audi.c`, `src/snd.c`, `src/music.c` — libaudio-based sound + music.
|
||||
* `src/joy.c` — controllers, **EEPROM** save handling (via `joyGamePak*`
|
||||
-> `osEeprom*`), plus PFS/motor code for accessory (Rumble Pak / Memory
|
||||
Pak) detection only.
|
||||
* `src/init.c` — `init()` -> `mainproc()` -> `bossEntry()` (game entry).
|
||||
**Compiled for the PC** (provides `mainproc()`); its N64-only `init()` is
|
||||
stubbed (see A3).
|
||||
* **Custom RSP microcode:** `rsp/graphics/gmain.s` (1545 lines) — a modified
|
||||
fast3d. This is the RSP-side code that consumes the display list. For the PC
|
||||
port we do **not** run this; we replace it with the software RSP. But it is
|
||||
the authoritative reference for which GBI commands GE actually emits.
|
||||
|
||||
### 3.1 Entry / main loop
|
||||
|
||||
```
|
||||
boot.s -> init() [src/init.c]
|
||||
-> decompress data segment
|
||||
-> osInitialize(), TLB setup
|
||||
-> osCreateThread(mainThread, mainproc)
|
||||
mainproc()
|
||||
-> idleCreateThread(), piCreateManager(), rmonCreateThread()
|
||||
-> schedulerInitThread() # osCreateScheduler + osScAddClient(gfxClient)
|
||||
-> bossEntry() # [src/boss.c] real game start
|
||||
```
|
||||
|
||||
The port's `main.c` replaces `boot.s`/`init()` and drives `mainproc()` (or
|
||||
`bossEntry()`) directly after setting up video/audio/input/ROM.
|
||||
|
||||
### 3.2 The two libultra trees: `src/libultra/` and `src/libultrare/`
|
||||
|
||||
GE ships **two** libultra trees. `src/libultra/` is the standard Nintendo
|
||||
libultra; `src/libultrare/` holds Rare's modified/replacement files. The N64
|
||||
build selects which files to compile via `src/libultrare/Makefile.libultrare`,
|
||||
which explicitly lists each file as *original* (from `libultra/`) or *Rare*
|
||||
(from `libultrare/`). The Rare files **override** the originals where they
|
||||
overlap.
|
||||
|
||||
What the PC build must do with each:
|
||||
|
||||
* **`src/libultra/audio/*.c`** — standard libaudio (AL). Runs on the CPU;
|
||||
**compile** (the game's `audi.c`/`snd.c`/`music.c` depend on it).
|
||||
* **`src/libultrare/audio/*.c`** (`drvrNew.c`, `env.c`, `reverb.c`) — Rare's
|
||||
audio "New" driver. `synthesizer.c` calls `alSaveNew`/`alAuxBusNew`/
|
||||
`alMainBusNew`/`alLoadNew`/`alResampleNew`/`alEnvmixerNew`/`alEnvmixerParam`/
|
||||
`alFxParam`, all defined here. Runs on the CPU; **compile**.
|
||||
* **`src/libultra/gu/*.c`** — matrix helpers (`guMtxF`, `guOrtho`, ...).
|
||||
**Compile**.
|
||||
* **`src/libultrare/io/vitbl.c`** — defines `osViModeTable` (a data table),
|
||||
referenced ~15× by `fr.c`. **Compile** (it's data, not a driver). This is the
|
||||
*only* `io/` file we compile.
|
||||
* **All other `src/libultra/io/*.c` and `src/libultrare/io/*.c`** — hardware
|
||||
drivers (PI, SI, SP, DP, VI, controller, PFS, motor, EEPROM, ...). **Exclude
|
||||
and shim** in `port/src/libultra.c`. Notably `pfsinit.c`/`pfsisplug.c`
|
||||
(`osPfsInit`/`osPfsIsPlug`) and `motor.c` (`osMotor*`) are called by
|
||||
`joy.c` only for accessory detection, so they are shimmed as no-ops (see §7).
|
||||
* **`src/libultra/os/*.c` + `src/libultrare/os/*.c`** — OS core (threads,
|
||||
message queues, timers, cache, TLB). **Exclude and shim** in
|
||||
`port/src/libultra.c` (+ `n64stubs.c` for the boot/TLB/FPU symbols).
|
||||
|
||||
> The exact original-vs-Rare file lists are in
|
||||
> `src/libultrare/Makefile.libultrare` (`LIBULTRA_*_C_FILES` vs
|
||||
> `LIBULTRARE_*_C_FILES`). Use it as the ground truth when curating the PC
|
||||
> build's source list.
|
||||
|
||||
---
|
||||
|
||||
## 4. N64 hardware surfaces that must be shimmed
|
||||
|
||||
| Subsystem | GE API used | Port strategy (from PD) |
|
||||
|-----------|-------------|--------------------------|
|
||||
| RSP (coproc 0) | `osSpTaskLoad/StartGo/Yield`, display lists | Software RSP (`fast3d`) — **main work** |
|
||||
| RDP (coproc 1) | `osDpSetStatus`, `osDpSetNextBuffer`, render modes | Bypassed; OpenGL backend |
|
||||
| VI (video) | `osViSetMode`, `osViVSyncCallback`, `osViWaitVSync` | SDL2 window + GL context + frame pacing |
|
||||
| AI (audio) | `osAiSetFrequency/SetNextBuffer/GetLength` | SDL audio device + queue |
|
||||
| PI (peripheral) | `osPiRawStartDma`, `osPiCreateManager`, cart reads | Read from ROM file on disk |
|
||||
| SI (controller) | `osContInit/StartReadData`, `osEeprom*`, `osPfs*`/`osMotor*` | SDL input + file-backed EEPROM; PFS/motor stubbed (accessory detection only) |
|
||||
| OS core | threads, msg queues, timers, `osGetTime`, cache | Single-threaded shims + host clock |
|
||||
| R4300 specifics | TLB, cache, FPU csr, K0/K1 segments | No-ops / identity (native x86-64) |
|
||||
|
||||
---
|
||||
|
||||
## 5. GE-specific graphics differences vs PD (the RSP work)
|
||||
|
||||
GE's `include/gbi_extension.h` defines the custom GBI surface. Compared to the
|
||||
PD port's `fast3d`, the deltas are:
|
||||
|
||||
1. **`G_TRI4`** — draws up to 4 triangles in one command using 4-bit vertex
|
||||
indices (0–15). A memory-saving optimization (Rare packed 4 tris into the
|
||||
slot normally used for `G_TRI2`).
|
||||
* **Status: already supported** by the PD `fast3d` (`gfx_sp_tri4`, and the
|
||||
PD game itself uses it). ✅
|
||||
2. **`G_SETTEX`** (`0xc0`) — "use texture from bank": selects a texture by
|
||||
bank/tile with detail/mipmap type, min-level, detail-id and texture-id.
|
||||
Emitted only by `gsSPUseTexture` (`include/gbi_extension.h:193`).
|
||||
* **Status: appears UNUSED.** `gsSPUseTexture` is defined but never called
|
||||
anywhere in the game code or the RSP ucode (`rsp/graphics/gmain.s`), and
|
||||
`G_SETTEX` appears nowhere outside its `#define`. The game uses the
|
||||
standard texture commands (`gSPTexture`, `gSPTextureL`,
|
||||
`gSPTextureRectangle`, `gSPTextureRectangleFlip`) — all already handled
|
||||
by the PD `fast3d`. **If this holds (verify the decomp is complete),
|
||||
Phase 2 shrinks to verifying the custom CC/RM modes and the PD fast3d
|
||||
may work largely as-is.** Keep a `G_SETTEX` decode path as a safety net,
|
||||
but it is no longer the headline task.
|
||||
3. **Custom color-combiner modes** — `G_CC_MODULATEIFADE`, `G_CC_FADE`,
|
||||
`G_CC_DECALFADE`, `G_CC_BLENDRGBFADEA`, etc. (fade/blend variants).
|
||||
* **Status: handled generically** by the fast3d color-combiner -> GLSL
|
||||
shader compiler (`gfx_cc.cpp`), which compiles arbitrary CC equations.
|
||||
Needs verification that each GE mode produces correct GLSL.
|
||||
4. **Custom render mode** — `G_RM_CUSTOM_AA_ZB_XLU_SURF` (AA_ZB_XLU with
|
||||
`Z_UPD`).
|
||||
* **Status: handled** by the render-mode -> GL state mapping; verify.
|
||||
5. **`gDPLoadTLUT06/07`** — custom TLUT load variants.
|
||||
* **Status: minor**, map onto the existing `G_LOADTLUT` path.
|
||||
|
||||
**Conclusion:** the PD `fast3d` is a strong starting point. `G_SETTEX` appears
|
||||
unused (see item 2), so the concrete new RSP work is **verification of the
|
||||
custom CC/RM modes** against `gmain.s`, with `G_SETTEX` as a low-priority
|
||||
safety net. Everything else (matrices, lights, fog, tri1/tri4, texture load,
|
||||
sync/flush) is shared.
|
||||
|
||||
> Note: GE's on-hardware RSP ucode is `rsp/graphics/gmain.s`. We do not run it
|
||||
> on PC, but it is the ground truth for the exact command encodings GE emits —
|
||||
> use it to validate the software RSP's command decoding.
|
||||
|
||||
---
|
||||
|
||||
## 6. Audio
|
||||
|
||||
* GE uses **libaudio (AL)** on the R4300 (`src/audi.c`, `src/snd.c`,
|
||||
`src/music.c`) — identical architecture to PD. Sound synthesis (ADPCM
|
||||
decode, mixing, FX) runs on the CPU; the RSP audio ucode is a thin
|
||||
pass-through to the AI DMA.
|
||||
* The PD port runs libaudio natively and routes the mixed output through
|
||||
`audio.c` (SDL device) + `mixer.c`. **Carries over directly.**
|
||||
* Music: GE uses the same MOD-style music system; PD's `mod.c` is the model.
|
||||
* Output rate: PD uses 22020 Hz stereo s16. GE's `OUTPUT_RATE` in `audi.c`
|
||||
should be matched.
|
||||
|
||||
---
|
||||
|
||||
## 7. Input & saves
|
||||
|
||||
* **Controllers:** `src/joy.c` uses `osContInit` / `osContStartReadData`.
|
||||
The port maps SDL2 keyboard/mouse/gamepad into the N64 `OSError`/controller
|
||||
structs. PD implements 1964GEPD-style and Xbox-style bindings — a good
|
||||
default for GE too (fire=Z, aim=R, etc.).
|
||||
* **Saves:** GE saves are **EEPROM-based**, not Memory Pak. The actual save
|
||||
I/O (`src/game/file2.c`) uses `joyGamePakProbe/LongRead/LongWrite`, which
|
||||
map to `osEepromProbe/LongRead/LongWrite` (controller EEPROM). The PFS/motor
|
||||
code in `joy.c` (`osPfsInit`, `osMotorInit/Start/Stop`) is only for
|
||||
accessory (Rumble Pak / Memory Pak) detection. The port therefore:
|
||||
* backs the EEPROM with a file in the data dir (e.g. `eeprom.bin`), and
|
||||
* stubs PFS/motor to report "no accessory" so the game proceeds without
|
||||
rumble/mempak.
|
||||
This is simpler than a full Memory Pak/PFS emulation.
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommended plan
|
||||
|
||||
### Phase 0 — Scaffolding (this change)
|
||||
* Add `port/` directory structure + headers + stub sources.
|
||||
* Add a top-level `CMakeLists.txt` (+ `cmake/` modules) for the PC build,
|
||||
parallel to the N64 `Makefile`.
|
||||
* This document + `port/README.md`.
|
||||
|
||||
### Phase 1 — Boot to a window
|
||||
* Port `system.c`, `fs.c`, `romdata.c`, `config.c`, `main.c`.
|
||||
* Port `video.c` + a minimal `fast3d` (clear color, present).
|
||||
* Goal: load `ge007.u.z64`, open a window, draw a clear color.
|
||||
|
||||
### Phase 2 — RSP / rendering
|
||||
* Bring in the PD `fast3d` (gfx_pc / gfx_opengl / gfx_cc / gfx_sdl2).
|
||||
* Verify the custom CC/RM modes against `gmain.s`; add a `G_SETTEX` decode
|
||||
path only if it turns out to be used (see §5 — it appears unused).
|
||||
* Port `pdsched.c` (GE variant) to drive the software RSP.
|
||||
* Goal: render the title screen / first level.
|
||||
|
||||
### Phase 3 — Audio + input
|
||||
* Port `audio.c`, `mixer.c`, `mod.c`; wire libaudio output to SDL.
|
||||
* **Decide the ASP strategy** (see §11 C2): if audio runs the `aspMain`
|
||||
microcode, wire `aspMainTextStart`/`aspMainDataStart` to the real microcode
|
||||
bytes in the ROM; if audio is CPU-only, the `port/src/ucode.c` dummies stand.
|
||||
* Port `input.c` (keyboard/mouse/gamepad).
|
||||
* Goal: playable with sound.
|
||||
|
||||
### Phase 4 — Saves + polish
|
||||
* File-backed EEPROM (saves); PFS/motor already stubbed as no-ops.
|
||||
* Widescreen, mouse-look, FPS options, config file, crash handler.
|
||||
* Goal: feature-parity with the original + QoL extras.
|
||||
|
||||
---
|
||||
|
||||
## 9. Open questions / risks
|
||||
|
||||
1. **`G_SETTEX` (low priority)** — appears unused (`gsSPUseTexture` is never
|
||||
called). If a future decomp revision or a missed call site shows it is used,
|
||||
reverse the texture-bank selection (bank -> image, tile, detail/mipmap type)
|
||||
from `gmain.s` + `tex.c` / `initmttex.c`. Not currently the main unknown.
|
||||
2. **Custom CC correctness** — the GLSL compiler must handle every fade/blend
|
||||
mode GE uses; validate per-mode against the original. This is now the main
|
||||
rendering unknown.
|
||||
3. **Scheduler fidelity** — GE's `sched.c` has speed-graph / profiling hooks
|
||||
(`speedgraphMarkerHandler`) and a slightly different task model than PD;
|
||||
the port scheduler must reproduce the gfx/audio interleaving and VSync
|
||||
pacing.
|
||||
4. **Decomp completeness** — the decomp is WIP; some functions may still be
|
||||
stubs/`unk_`. The PC port inherits whatever state the decomp is in. Track
|
||||
the upstream `n64decomp/007` and rebase.
|
||||
5. **Endianness / alignment** — GE, like PD, is big-endian N64 code with
|
||||
unaligned accesses; the PD port handles this with careful casts and
|
||||
`-fno-strict-aliasing`. Reuse those flags.
|
||||
|
||||
---
|
||||
|
||||
## 10. References
|
||||
|
||||
* PD port: https://github.com/perfect-dark-pc-port/perfect_dark
|
||||
* PD decomp: https://github.com/n64decomp/perfect_dark
|
||||
* GE decomp (this repo): https://github.com/n64decomp/007
|
||||
* GE docs: https://github.com/kholdfuzion/goldeneye_docs
|
||||
* N64 devkit / libultra: `include/PR/*.h` in this repo
|
||||
* GE custom GBI: `include/gbi_extension.h`
|
||||
* GE RSP ucode: `rsp/graphics/gmain.s`
|
||||
|
||||
---
|
||||
|
||||
## 11. Scaffolding review: findings & resolutions
|
||||
|
||||
The initial scaffolding was reviewed against the sources. Each finding below
|
||||
was verified and resolved in the same change. Code comments in `CMakeLists.txt`
|
||||
and `port/src/` reference these labels (A1–A4, B1–B4).
|
||||
|
||||
### A. Build-blocking issues
|
||||
|
||||
* **A1 — Region/version macros.** The N64 `Makefile` defines a full per-region
|
||||
macro set (`VERSION_*`, `LANG_*`, `REFRESH_*`, `LEFTOVERDEBUG`,
|
||||
`LEFTOVERSPECTRUM`, `BUGFIX_R*`, `BYTEMATCH`) that the game code `#ifdef`s
|
||||
heavily (e.g. `VERSION_EU` in 60+ files). **Resolved:** `CMakeLists.txt`
|
||||
now emits the matching `REGION_DEFS` per `ROMID`, mirroring the Makefile
|
||||
exactly.
|
||||
* **A2 — Missing source files.** The initial `SRC_ENGINE` list + "exclude all
|
||||
`io/*.c`" strategy missed files the included code depends on. **Resolved:**
|
||||
added `src/cfb.c` (defines `cfb_16`, the framebuffer),
|
||||
`src/libultrare/audio/*.c` (`drvrNew`/`env`/`reverb` — the audio "New"
|
||||
driver `synthesizer.c` calls), and `src/libultrare/io/vitbl.c` (defines
|
||||
`osViModeTable`, referenced ~15× by `fr.c`). `motor.c` / `pfsinit.c` /
|
||||
`pfsisplug.c` are **not** compiled — `joy.c` calls `osMotor*`/`osPfs*` only
|
||||
for accessory detection, so they are shimmed as no-ops (see B2).
|
||||
* **A3 — `mainproc()` / `init.c` + the excluded engine files' symbols.**
|
||||
`mainproc()` is defined only in `src/init.c`, which the initial build
|
||||
excluded, contradicting `main.c` calling it. **Resolved:** `init.c` is now
|
||||
**compiled** (it provides `mainproc()` + the thread-setup helpers). Its
|
||||
N64-only `init()` is compiled but not called.
|
||||
|
||||
Compiling `init.c` (and the other included engine files) pulls in a set of
|
||||
symbols that live in the EXCLUDED files (`sched.c`, `rmon.c`, `vi.c`,
|
||||
`stacks.c`). Each is now provided:
|
||||
* **`src/stacks.c`** (added to `SRC_ENGINE`) — thread-stack arrays
|
||||
`sp_boot`/`sp_rmon`/`sp_idle`/`sp_shed`/`sp_main`/`sp_audi` (+ `sp_debug`
|
||||
under `LEFTOVERDEBUG`). Pure data; the `SP_*_SZ` sizes in
|
||||
`bondconstants.h` match exactly, so `sizeof()` is correct.
|
||||
* **`port/src/gesched.c`** (replaces `sched.c`) — scheduler globals
|
||||
`os_scheduler`, `gfxClient[3]`, `g_schedViCurrentFrameBuffer`,
|
||||
`g_ViChangeVideoModes`, `g_ViModes`, `g_ViModePtrs`, plus
|
||||
`get_counters()` and `permit_stderr()`.
|
||||
* **`port/src/n64stubs.c`** — `init()`'s N64 symbols (segment starts,
|
||||
segment boundary pointers, `jump_decompressfile`, TLB, FPU CSR) and the
|
||||
rmon host-I/O functions (`rmonMain`, `osReadHost`, `osWriteHost`,
|
||||
`rmonGetToken`, `rmonStatus`, `osSyncPrintf`).
|
||||
* **`port/src/libultra.c`** — the libultra OS calls `init.c` makes, plus
|
||||
`osTvType` (region-based), `viInit`, `vi_c_debug_MQ` (from the excluded
|
||||
`vi.c`), and `osPiReadIo` (cartridge token read, called by `token.c`).
|
||||
|
||||
Every external symbol referenced by the compiled set was cross-checked
|
||||
against its definition; no duplicates (all other `sp_*`/`rmon*` references
|
||||
are `extern` decls).
|
||||
* **A4 — `libultra.c` missing the high-level message API + others.** The
|
||||
initial stub had low-level `osEnqueueMesg`/`osDequeueMesg` but not
|
||||
`osSendMesg`/`osRecvMesg` (used in 13/26 files) or several others. **
|
||||
Resolved:** `port/src/libultra.c` now provides `osSendMesg`/`osRecvMesg`/
|
||||
`osSetEventMesg`, the controller query/read API, `osPfs*`/`osMotor*`,
|
||||
`osSetTimer`/`osStopTimer`/`osSetIntMask`, `osViBlack`/`osViSetYScale`/
|
||||
`osViSetXScale`/`osViSetEvent`, `osUnmapTLB`, `__osGetFpcCsr`/
|
||||
`__osSetFpcCsr`, `piCreateManager`, and `viDebugRemoved`. The scheduler API
|
||||
(`osCreateScheduler`/`osScAddClient`/`osScGetCmdQ`) lives in
|
||||
`port/src/gesched.c` (it's game scheduler API, not libultra).
|
||||
|
||||
### B. Research-doc corrections
|
||||
|
||||
* **B1 — `G_SETTEX` appears unused.** `gsSPUseTexture` (the only emitter) is
|
||||
never called in the game code or `gmain.s`; the game uses standard texture
|
||||
commands the PD `fast3d` already handles. **Resolved:** §5, §8 (Phase 2),
|
||||
and §9 updated — Phase 2 shrinks to verifying the custom CC/RM modes; a
|
||||
`G_SETTEX` decode path is kept only as a safety net.
|
||||
* **B2 — Saves are EEPROM-based, not Memory Pak/PFS.** The save I/O
|
||||
(`src/game/file2.c`) uses `joyGamePak*` -> `osEeprom*`; the PFS/motor code
|
||||
in `joy.c` is accessory detection only. **Resolved:** §7 and Phase 4
|
||||
updated — Phase 4 is a file-backed EEPROM + no-op PFS/motor shims.
|
||||
* **B3 — `src/libultrare/` was unmentioned.** **Resolved:** new §3.2 documents
|
||||
the two libultra trees and exactly which files the PC build compiles vs.
|
||||
shims, citing `Makefile.libultrare` as ground truth.
|
||||
* **B4 — `src/spectrum.c` does not exist.** **Resolved:** removed from the
|
||||
`CMakeLists.txt` excluded-files comment.
|
||||
|
||||
### C. Assembly (`.s`) file symbols
|
||||
|
||||
The A/B sweeps covered excluded **`.c`** files. A second symbol source is the
|
||||
MIPS assembly (`src/*.s`), also not compiled for the PC. Sweeping every global
|
||||
label in the `.s` files against the compiled C set found **10 more** undefined
|
||||
symbols, in two distinct classes:
|
||||
|
||||
* **C1 — PRNG (`src/random.s`) — ported to C, not stubbed.** `randomGetNext`,
|
||||
`randomGetNextFrom`, `randomSetSeed`, and `g_randomSeed` are used for real
|
||||
gameplay logic: `randomGetNextFrom()` feeds the CRC (`src/game/crc.c`),
|
||||
`g_randomSeed` is persisted in replay state (`src/game/ramromreplay.c`), and
|
||||
`randomGetNext()` drives `RANDOMFRAC()`/`RANDOMGETNEXT_F32()`. A no-op would
|
||||
silently corrupt CRCs and replays. **Resolved:** `port/src/random.c` ports
|
||||
the three functions **verbatim** (each MIPS instruction mirrored with explicit
|
||||
`dsll32`/`dsrl32` masks for bit-exactness) and defines
|
||||
`u64 g_randomSeed = 0xAB8D9F7781280783ULL` (the two `.words` in `random.s`).
|
||||
Signatures match `src/random.h`. Verified bit-exact against a Python
|
||||
simulation of the assembly (the PRNG is effectively 32-bit stored in a u64;
|
||||
the initial seed's high 32 bits are exercised only on the first call).
|
||||
* **C2 — RSP/ASP/GSP microcode segment markers — dummy definitions.**
|
||||
`rspbootTextStart`/`rspbootTextEnd` (`rspboot.s`), `gsp3DTextStart`/
|
||||
`gsp3DDataStart` (`gspboot.s`), `aspMainTextStart`/`aspMainDataStart`
|
||||
(`aspboot.s`) are ROM addresses of the RSP/ASP microcode, referenced by
|
||||
`src/audi.c` and `src/game/rsp.c` (declared `extern long long int <name>[]`).
|
||||
**Resolved:** `port/src/ucode.c` defines all six as `long long int <name>[1] = {0}`.
|
||||
The graphics path is safe permanently (fast3d interprets the GBI list directly
|
||||
and never runs the gsp3D microcode, so the size differences are never read).
|
||||
**Carried into Phase 3:** the audio path is the one place it could matter — if
|
||||
the port emulates the ASP by *running* the `aspMain` microcode, those two
|
||||
markers must point at the real microcode bytes in the ROM (PD `pd.ld`
|
||||
`RSP_TEXT_SEGMENT` model); if audio is CPU-only, the dummies are fine.
|
||||
A `TODO(Phase 3)` on the `aspMain*` dummies records this decision.
|
||||
@@ -0,0 +1,70 @@
|
||||
# GoldenEye 007 — PC port layer
|
||||
|
||||
This directory contains the PC port layer: the code that lets the (unmodified)
|
||||
GoldenEye game sources run on a modern host instead of N64 hardware.
|
||||
|
||||
It mirrors the layout of the [Perfect Dark PC
|
||||
port](https://github.com/perfect-dark-pc-port/perfect_dark), which ports the
|
||||
same Rare engine family. See `docs/PCPortResearch.md` (repo root) for the full
|
||||
research write-up and phased plan.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
port/
|
||||
├── fast3d/ # Software RSP: interprets GBI display lists -> OpenGL
|
||||
│ ├── gfx_pc.cpp # RSP interpreter (matrices, lights, tris, fog)
|
||||
│ ├── gfx_opengl.cpp # OpenGL rendering backend (shaders, textures, zbuf)
|
||||
│ ├── gfx_sdl2.cpp # SDL2 window-manager backend
|
||||
│ ├── gfx_cc.cpp # color-combiner -> GLSL shader compiler
|
||||
│ └── ...
|
||||
├── include/ # port-facing headers
|
||||
│ ├── audio.h config.h fs.h input.h mixer.h
|
||||
│ ├── platform.h romdata.h system.h video.h utils.h
|
||||
│ └── versioninfo.h.in
|
||||
└── src/
|
||||
├── main.c # PC entry point (replaces boot.s / init())
|
||||
├── libultra.c # shims for the libultra OS API
|
||||
├── video.c # SDL2 window + GL context + frame pacing
|
||||
├── audio.c # SDL audio device, buffer queueing
|
||||
├── mixer.c # audio mixing
|
||||
├── input.c # SDL2 keyboard/mouse/gamepad -> N64 controller structs
|
||||
├── fs.c # filesystem abstraction over the ROM + data dir
|
||||
├── romdata.c # loads the .z64 ROM from disk, sets up segments
|
||||
├── config.c # INI config (ge007.ini)
|
||||
├── system.c # platform primitives (time, logging, paths)
|
||||
├── crash.c # crash handler / stack traces
|
||||
├── gesched.c # replacement RCP scheduler that drives the software RSP
|
||||
└── utils.c
|
||||
```
|
||||
|
||||
## How it works (short version)
|
||||
|
||||
* The R4300 game code (`src/game`, `src`) is compiled **unmodified**.
|
||||
* The **RSP is emulated in software** (`fast3d/`): it interprets the Gfx
|
||||
display list the game builds and emits OpenGL calls, bypassing the RDP.
|
||||
* The **RCP scheduler** (`gesched.c`) replaces `src/sched.c`'s hardware task
|
||||
submission: instead of writing RSP registers it calls `gfx_run()`.
|
||||
* **libultra OS** functions are shimmed in `libultra.c` (single-threaded,
|
||||
host clock, VI/PI/SI/AI mapped onto the video/audio/fs/input layers).
|
||||
* **Saves** (Memory Pak / EEPROM) are backed by files in the data dir.
|
||||
|
||||
## GE-specific RSP work
|
||||
|
||||
The PD `fast3d` already handles `G_TRI4` (GE's 4-triangle command). The new
|
||||
work for GE is:
|
||||
|
||||
* **`G_SETTEX`** — GE's custom texture-bank command (`include/gbi_extension.h`,
|
||||
`gsSPUseTexture`). Must be added to the RSP interpreter + GL backend.
|
||||
* Verify GE's custom color-combiner modes (`G_CC_*FADE*`) and render mode
|
||||
(`G_RM_CUSTOM_AA_ZB_XLU_SURF`) produce correct GLSL / GL state.
|
||||
|
||||
Use `rsp/graphics/gmain.s` (the real GE RSP ucode) as the ground truth for the
|
||||
exact command encodings.
|
||||
|
||||
## Status
|
||||
|
||||
**Scaffolding only.** The files in `src/` and `fast3d/` are stubs that define
|
||||
the interfaces and mark the work to be done. The actual implementation is
|
||||
brought in phase by phase (see `docs/PCPortResearch.md` §8), largely by
|
||||
adapting the PD port's `port/` layer.
|
||||
@@ -0,0 +1,51 @@
|
||||
# fast3d — software RSP for the GE PC port
|
||||
|
||||
This directory will hold the **software RSP**: a C++ interpreter that consumes
|
||||
the Gfx display list the game builds and emits OpenGL calls, bypassing the N64
|
||||
RDP entirely.
|
||||
|
||||
## Source: adapt from the Perfect Dark PC port
|
||||
|
||||
The PD port ships a complete, working fast3d at
|
||||
`perfect-dark-pc-port/perfect_dark/port/fast3d/`:
|
||||
|
||||
| File | Role | ~Lines |
|
||||
|--------------------|-------------------------------------------------------------|--------|
|
||||
| `gfx_pc.cpp` | The RSP interpreter: matrices, lights, fog, tri1/tri4, vtx | 2800 |
|
||||
| `gfx_opengl.cpp` | OpenGL backend: shaders, textures, zbuf, framebuffers | 1300 |
|
||||
| `gfx_cc.cpp` | Color-combiner equation -> GLSL shader compiler | 60 |
|
||||
| `gfx_sdl2.cpp` | SDL2 window-manager backend | 460 |
|
||||
| `gfx_api.h` | Public API (`gfx_init/run/start_frame/end_frame/...`) | 58 |
|
||||
| `gfx_pc.h` | Internal state (RSP struct, texture cache, combiner pool) | 60 |
|
||||
| `gfx_rendering_api.h` | Rendering-API vtable (GL backend implements this) | 61 |
|
||||
| `gfx_window_manager_api.h` | Window-manager vtable (SDL2 implements this) | 54 |
|
||||
| `glad/` | GL loader | — |
|
||||
|
||||
**The PD fast3d already handles `G_TRI4`** (GE's 4-triangle command) — the PD
|
||||
game uses it too. So the bulk of the RSP work is done; we adapt, not rewrite.
|
||||
|
||||
## GE-specific work to add
|
||||
|
||||
1. **`G_SETTEX`** (opcode `0xc0`) — GE's custom texture-bank command
|
||||
(`include/gbi_extension.h`, `gsSPUseTexture`). Add a case in the `gfx_pc.cpp`
|
||||
dispatch and a corresponding texture-bank selection in the GL backend.
|
||||
Reverse the exact semantics from `rsp/graphics/gmain.s` + `src/game/tex.c` /
|
||||
`initmttex.c`.
|
||||
2. **Verify custom color-combiner modes** — `G_CC_MODULATEIFADE`, `G_CC_FADE`,
|
||||
`G_CC_DECALFADE`, `G_CC_BLENDRGBFADEA`, etc. The `gfx_cc.cpp` compiler
|
||||
handles arbitrary CC equations; confirm each GE mode compiles to correct
|
||||
GLSL.
|
||||
3. **Verify the custom render mode** — `G_RM_CUSTOM_AA_ZB_XLU_SURF`
|
||||
(AA_ZB_XLU with `Z_UPD`).
|
||||
4. **`gDPLoadTLUT06/07`** — map onto the existing `G_LOADTLUT` path.
|
||||
|
||||
## Ground truth
|
||||
|
||||
`rsp/graphics/gmain.s` (repo root) is the real GE RSP ucode. We do **not** run
|
||||
it on PC, but it is the authoritative reference for the exact command
|
||||
encodings GE emits — use it to validate the software RSP's decoding.
|
||||
|
||||
## Status
|
||||
|
||||
**Empty (scaffolding).** Populate during Phase 2 by adapting the PD fast3d and
|
||||
adding the GE-specific commands above.
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef GFX_API_H
|
||||
#define GFX_API_H
|
||||
|
||||
/*
|
||||
* Public API for the software RSP (fast3d).
|
||||
*
|
||||
* This is the interface the rest of the port (video.c / gesched.c) uses to
|
||||
* drive the RSP. It mirrors the PD port's port/fast3d/gfx_api.h.
|
||||
*
|
||||
* STATUS: scaffolding — the implementation (gfx_pc.cpp etc.) is brought in
|
||||
* during Phase 2 by adapting the PD fast3d. See port/fast3d/README.md.
|
||||
*/
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct GfxRenderingAPI;
|
||||
struct GfxWindowManagerAPI;
|
||||
|
||||
struct GfxDimensions {
|
||||
float internal_mul;
|
||||
uint32_t width, height;
|
||||
float aspect_ratio;
|
||||
};
|
||||
|
||||
struct GfxInitSettings {
|
||||
struct GfxWindowManagerAPI *wapi;
|
||||
struct GfxRenderingAPI *rapi;
|
||||
/* window title / size / fullscreen / etc. — see PD gfx_window_manager_api.h */
|
||||
const char *title;
|
||||
uint32_t width, height;
|
||||
int fullscreen;
|
||||
int vsync;
|
||||
};
|
||||
|
||||
/* Current draw-area dimensions (before any scaling). */
|
||||
extern struct GfxDimensions gfx_current_dimensions;
|
||||
|
||||
void gfx_init(const struct GfxInitSettings *settings);
|
||||
void gfx_destroy(void);
|
||||
|
||||
/* Frame boundary. */
|
||||
void gfx_start_frame(void);
|
||||
void gfx_end_frame(void);
|
||||
|
||||
/* Run the RSP over a display list (the core of the emulation). */
|
||||
void gfx_run(Gfx *commands);
|
||||
|
||||
/* Options. */
|
||||
void gfx_set_target_fps(int fps);
|
||||
void gfx_set_texture_filter(int mode);
|
||||
void gfx_set_mipmap_filter(int mode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GFX_API_H */
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef PORT_AUDIO_H
|
||||
#define PORT_AUDIO_H
|
||||
|
||||
/*
|
||||
* Audio: SDL audio device + buffer queueing.
|
||||
* Modelled on the PD port's port/include/audio.h.
|
||||
*
|
||||
* libaudio (AL) runs on the CPU and produces mixed s16 stereo output. The AI
|
||||
* (osAiSetNextBuffer / osAiGetLength / osAiSetFrequency) is mapped onto this
|
||||
* layer, which queues the mixed samples to the SDL audio device.
|
||||
*/
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initialize the SDL audio device. Returns 0 on success. */
|
||||
int audioInit(void);
|
||||
void audioDestroy(void);
|
||||
|
||||
/* Number of samples (stereo s16 frames) currently queued. */
|
||||
s32 audioGetSamplesBuffered(void);
|
||||
|
||||
/* Queue the next block of mixed samples (len is in bytes). */
|
||||
void audioSetNextBuffer(const s16 *buf, u32 len);
|
||||
|
||||
/* Called at the end of each frame to flush the queued buffer to the device. */
|
||||
void audioEndFrame(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_AUDIO_H */
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef PORT_CONFIG_H
|
||||
#define PORT_CONFIG_H
|
||||
|
||||
/*
|
||||
* INI configuration (ge007.ini).
|
||||
* Modelled on the PD port's port/include/config.h.
|
||||
*
|
||||
* A tiny key=value store. Options register themselves (usually via a
|
||||
* PD_CONSTRUCTOR) and are loaded from / saved to the ini in the data dir.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Load the config file (no-op if it doesn't exist yet). */
|
||||
void configLoad(void);
|
||||
/* Save the current values back to the config file. */
|
||||
void configSave(void);
|
||||
|
||||
/* Register an integer option. min/max are clamping bounds (0 = unbounded). */
|
||||
void configRegisterInt(const char *key, int *value, int min, int max);
|
||||
/* Register a string option (buf must live for the program's lifetime). */
|
||||
void configRegisterString(const char *key, char *value, int bufSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_CONFIG_H */
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef PORT_FS_H
|
||||
#define PORT_FS_H
|
||||
|
||||
/*
|
||||
* Filesystem abstraction over the ROM + data dir.
|
||||
* Modelled on the PD port's port/include/fs.h.
|
||||
*
|
||||
* Provides a small POSIX-ish file API so the port layer (and any game code
|
||||
* that touches files) has a uniform interface across Windows/Linux/macOS.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FS_MAXPATH 512
|
||||
|
||||
typedef struct FSFile FSFile;
|
||||
|
||||
/* Open a file. mode is a fopen-style string ("rb", "wb", "r+b", ...). */
|
||||
FSFile *fsOpen(const char *path, const char *mode);
|
||||
void fsClose(FSFile *f);
|
||||
|
||||
/* Read up to size bytes into buf; returns bytes read (-1 on error/EOF). */
|
||||
int32_t fsRead(FSFile *f, void *buf, int32_t size);
|
||||
/* Write size bytes from buf; returns bytes written (-1 on error). */
|
||||
int32_t fsWrite(FSFile *f, const void *buf, int32_t size);
|
||||
|
||||
/* Seek to offset (whence: SEEK_SET/CUR/END). Returns 0 on success. */
|
||||
int fsSeek(FSFile *f, int32_t offset, int whence);
|
||||
/* Current file position. */
|
||||
int32_t fsTell(FSFile *f);
|
||||
/* File size in bytes. */
|
||||
int32_t fsSize(FSFile *f);
|
||||
|
||||
/* Does the file exist? */
|
||||
int fsExists(const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_FS_H */
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef PORT_INPUT_H
|
||||
#define PORT_INPUT_H
|
||||
|
||||
/*
|
||||
* Input: SDL2 keyboard/mouse/gamepad -> N64 controller structs.
|
||||
* Modelled on the PD port's port/include/input.h.
|
||||
*
|
||||
* The game reads controllers via osContInit / osContStartReadData (src/joy.c).
|
||||
* This layer populates the N64 controller state (buttons, stick) from SDL
|
||||
* events. Default bindings follow the 1964GEPD / Xbox schemes; see the PD
|
||||
* port README for the reference table.
|
||||
*/
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initialize SDL input. Returns 0 on success. */
|
||||
int inputInit(void);
|
||||
void inputDestroy(void);
|
||||
|
||||
/* Poll once per frame; updates the connected-controller state that
|
||||
* osContStartReadData / osContRead will return. */
|
||||
void inputUpdate(void);
|
||||
|
||||
/* Number of controllers currently "connected" (1..4). */
|
||||
int inputGetNumControllers(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_INPUT_H */
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef PORT_MIXER_H
|
||||
#define PORT_MIXER_H
|
||||
|
||||
/*
|
||||
* Audio mixing.
|
||||
* Modelled on the PD port's port/include/mixer.h.
|
||||
*
|
||||
* On the N64 the RSP audio ucode does the final mix / sample-rate handling
|
||||
* before the AI DMA. Since the RSP is emulated in software here, this module
|
||||
* performs that final processing on the CPU and hands the result to audio.c.
|
||||
*
|
||||
* NOTE: the exact responsibilities depend on how GE's libaudio output is
|
||||
* wired (see src/audi.c). This is a placeholder interface to be refined in
|
||||
* Phase 3 (audio).
|
||||
*/
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void mixerInit(void);
|
||||
void mixerDestroy(void);
|
||||
|
||||
/* Process one frame of audio. `in` is the libaudio-mixed buffer, `frames`
|
||||
* the number of stereo frames. Output is queued via audioSetNextBuffer. */
|
||||
void mixerProcess(const s16 *in, u32 frames);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_MIXER_H */
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef PORT_PLATFORM_H
|
||||
#define PORT_PLATFORM_H
|
||||
|
||||
/*
|
||||
* Platform abstraction for the GE PC port.
|
||||
*
|
||||
* Provides the small set of host primitives the port layer needs, with
|
||||
* per-OS implementations selected at compile time. Modelled on the PD port's
|
||||
* port/include/platform.h.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define PLATFORM_WINDOWS 1
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#define PLATFORM_IOS 1
|
||||
#else
|
||||
#define PLATFORM_MACOS 1
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#define PLATFORM_LINUX 1
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__)
|
||||
#define PLATFORM_64BIT 1
|
||||
#endif
|
||||
|
||||
/* Constructor attribute: run before main(). */
|
||||
#if defined(_MSC_VER)
|
||||
#define PD_CONSTRUCTOR
|
||||
#define PD_EXPORT
|
||||
#else
|
||||
#define PD_CONSTRUCTOR __attribute__((constructor))
|
||||
#define PD_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/* Log levels (see system.h). */
|
||||
enum LogLevel {
|
||||
LOG_ERROR = 0,
|
||||
LOG_WARNING,
|
||||
LOG_INFO,
|
||||
LOG_DEBUG,
|
||||
};
|
||||
|
||||
#endif /* PORT_PLATFORM_H */
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef PORT_ROMDATA_H
|
||||
#define PORT_ROMDATA_H
|
||||
|
||||
/*
|
||||
* ROM loading: reads the .z64 ROM from the data dir and exposes its segments.
|
||||
* Modelled on the PD port's port/include/romdata.h.
|
||||
*
|
||||
* The N64 build links assets in from the ROM at fixed addresses. The PC port
|
||||
* instead loads the ROM file and maps the game's data segments (code, data,
|
||||
* assets) into a heap-allocated image, then patches the segment table so the
|
||||
* game's IMAGESEG / asset references resolve into that image.
|
||||
*/
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Load the ROM for the configured ROMID from the data dir.
|
||||
* Returns 0 on success, non-zero on failure.
|
||||
*/
|
||||
int romdataInit(void);
|
||||
void romdataDestroy(void);
|
||||
|
||||
/* Pointer to the loaded ROM image (start of the .z64). */
|
||||
const u8 *romdataGetRom(void);
|
||||
/* Size of the loaded ROM in bytes. */
|
||||
u32 romdataGetRomSize(void);
|
||||
|
||||
/*
|
||||
* Map an N64 virtual address (0xA0000000 RDRAM space) to the corresponding
|
||||
* pointer in the loaded image. Used to resolve asset/segment references.
|
||||
*/
|
||||
const void *romdataMapVa(u32 va);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_ROMDATA_H */
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef PORT_SYSTEM_H
|
||||
#define PORT_SYSTEM_H
|
||||
|
||||
/*
|
||||
* Platform primitives: time, logging, paths, exit.
|
||||
* Modelled on the PD port's port/include/system.h.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* --- Time --------------------------------------------------------------- */
|
||||
|
||||
/* Monotonic microseconds since an arbitrary origin. */
|
||||
uint64_t sysGetMicroseconds(void);
|
||||
/* Wall-clock seconds since the epoch (for timestamps). */
|
||||
int64_t sysGetTime(void);
|
||||
/* Sleep for the given number of microseconds. */
|
||||
void sysSleep(uint32_t micros);
|
||||
|
||||
/* --- Logging ------------------------------------------------------------ */
|
||||
|
||||
void sysLogPrintf(enum LogLevel level, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
|
||||
/* --- Paths -------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Resolve a path relative to the data dir. "$S/" prefixes the data dir,
|
||||
* "$E/" prefixes the executable dir. Returns a pointer to a static buffer.
|
||||
*/
|
||||
const char *sysResolvePath(const char *path);
|
||||
|
||||
/* Return the directory containing the executable. */
|
||||
const char *sysGetExeDir(void);
|
||||
|
||||
/* --- Lifecycle ---------------------------------------------------------- */
|
||||
|
||||
void sysExit(int code);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_SYSTEM_H */
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef PORT_UTILS_H
|
||||
#define PORT_UTILS_H
|
||||
|
||||
/*
|
||||
* Small shared helpers for the port layer.
|
||||
* Modelled on the PD port's port/include/utils.h.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ARRAYCOUNT(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
/* Read a big-endian 16/32-bit value from an (possibly unaligned) buffer. */
|
||||
uint16_t readU16BE(const void *p);
|
||||
uint32_t readU32BE(const void *p);
|
||||
void writeU16BE(void *p, uint16_t v);
|
||||
void writeU32BE(void *p, uint32_t v);
|
||||
|
||||
/* Clamp helpers. */
|
||||
int clampi(int v, int lo, int hi);
|
||||
float clampf(float v, float lo, float hi);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_UTILS_H */
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef PORT_VERSIONINFO_H
|
||||
#define PORT_VERSIONINFO_H
|
||||
|
||||
/* Generated by CMake from versioninfo.h.in — do not edit. */
|
||||
|
||||
#define GE007_VERSION_HASH "@VERSION_HASH@"
|
||||
#define GE007_TARGET_PLATFORM "@TARGET_PLATFORM@"
|
||||
#define GE007_ROMID "@ROMID@"
|
||||
#define GE007_IS_PAL @VERSION_IS_PAL@
|
||||
|
||||
#endif /* PORT_VERSIONINFO_H */
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef PORT_VIDEO_H
|
||||
#define PORT_VIDEO_H
|
||||
|
||||
/*
|
||||
* Video: SDL2 window + OpenGL context + frame pacing.
|
||||
* Modelled on the PD port's port/include/video.h.
|
||||
*
|
||||
* The game's VI (osViSetMode / osViVSyncCallback / osViWaitVSync) is mapped
|
||||
* onto this layer. The software RSP (fast3d) renders into the GL context that
|
||||
* this layer owns.
|
||||
*/
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initialize the window + GL context. Returns 0 on success. */
|
||||
int videoInit(void);
|
||||
void videoDestroy(void);
|
||||
|
||||
/* Frame boundary hooks, driven by the port scheduler (gesched.c). */
|
||||
void videoStartFrame(void);
|
||||
void videoSubmitCommands(Gfx *cmds); /* runs the software RSP on the list */
|
||||
void videoEndFrame(void);
|
||||
|
||||
/* VSync / framerate control. */
|
||||
void videoSetVsync(int enabled);
|
||||
void videoSetFramerateLimit(int fps); /* 0 = unlimited */
|
||||
|
||||
/* Window state queries (kept in sync with the options menu). */
|
||||
int videoGetFullscreen(void);
|
||||
void videoSetFullscreen(int enabled);
|
||||
int videoGetMaximize(void);
|
||||
void videoSetMaximize(int enabled);
|
||||
|
||||
/* Current FPS (measured). */
|
||||
float videoGetFPS(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PORT_VIDEO_H */
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Audio: SDL audio device + buffer queueing.
|
||||
*
|
||||
* libaudio (AL) produces mixed s16 stereo output on the CPU. This module owns
|
||||
* the SDL audio device and queues the mixed samples. osAiSetNextBuffer /
|
||||
* osAiGetLength (libultra.c) route through here.
|
||||
*
|
||||
* Modelled on the PD port's port/src/audio.c (~75 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 3.
|
||||
*/
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "config.h"
|
||||
#include "audio.h"
|
||||
|
||||
static SDL_AudioDeviceID dev = 0;
|
||||
static const s16 *nextBuf = NULL;
|
||||
static u32 nextSize = 0;
|
||||
|
||||
static int bufferSize = 512;
|
||||
static int queueLimit = 8192;
|
||||
|
||||
int audioInit(void)
|
||||
{
|
||||
/* TODO(Phase 3):
|
||||
* - SDL_InitSubSystem(AUDIO)
|
||||
* - open device: 22020 Hz (match GE's OUTPUT_RATE in src/audi.c),
|
||||
* AUDIO_S16SYS, 2 channels
|
||||
* - unpause
|
||||
*/
|
||||
sysLogPrintf(LOG_INFO, "audioInit: TODO (Phase 3)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void audioDestroy(void)
|
||||
{
|
||||
if (dev) { SDL_CloseAudioDevice(dev); dev = 0; }
|
||||
}
|
||||
|
||||
s32 audioGetSamplesBuffered(void)
|
||||
{
|
||||
return dev ? (SDL_GetQueuedAudioSize(dev) / 4) : 0;
|
||||
}
|
||||
|
||||
void audioSetNextBuffer(const s16 *buf, u32 len)
|
||||
{
|
||||
nextBuf = buf;
|
||||
nextSize = len;
|
||||
}
|
||||
|
||||
void audioEndFrame(void)
|
||||
{
|
||||
if (nextBuf && nextSize && dev) {
|
||||
if (audioGetSamplesBuffered() < queueLimit) {
|
||||
SDL_QueueAudio(dev, nextBuf, nextSize);
|
||||
}
|
||||
nextBuf = NULL;
|
||||
nextSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
PD_CONSTRUCTOR static void audioConfigInit(void)
|
||||
{
|
||||
configRegisterInt("Audio.BufferSize", &bufferSize, 0, 1 * 1024 * 1024);
|
||||
configRegisterInt("Audio.QueueLimit", &queueLimit, 0, 1 * 1024 * 1024);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* INI configuration (ge007.ini).
|
||||
*
|
||||
* A tiny key=value store. Options register themselves (usually via a
|
||||
* PD_CONSTRUCTOR) and are loaded from / saved to the ini in the data dir.
|
||||
*
|
||||
* Modelled on the PD port's port/src/config.c (~300 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "config.h"
|
||||
|
||||
#define MAX_OPTIONS 256
|
||||
|
||||
struct IntOption { char key[64]; int *value; int min, max; };
|
||||
struct StrOption { char key[64]; char *value; int bufSize; };
|
||||
|
||||
static struct IntOption intOpts[MAX_OPTIONS];
|
||||
static int numIntOpts = 0;
|
||||
static struct StrOption strOpts[MAX_OPTIONS];
|
||||
static int numStrOpts = 0;
|
||||
|
||||
void configRegisterInt(const char *key, int *value, int min, int max)
|
||||
{
|
||||
if (numIntOpts >= MAX_OPTIONS) return;
|
||||
struct IntOption *o = &intOpts[numIntOpts++];
|
||||
strncpy(o->key, key, sizeof(o->key) - 1);
|
||||
o->value = value;
|
||||
o->min = min;
|
||||
o->max = max;
|
||||
}
|
||||
|
||||
void configRegisterString(const char *key, char *value, int bufSize)
|
||||
{
|
||||
if (numStrOpts >= MAX_OPTIONS) return;
|
||||
struct StrOption *o = &strOpts[numStrOpts++];
|
||||
strncpy(o->key, key, sizeof(o->key) - 1);
|
||||
o->value = value;
|
||||
o->bufSize = bufSize;
|
||||
}
|
||||
|
||||
void configLoad(void)
|
||||
{
|
||||
/* TODO(Phase 1): parse "$S/ge007.ini", apply to registered options. */
|
||||
sysLogPrintf(LOG_INFO, "configLoad: TODO (Phase 1)");
|
||||
}
|
||||
|
||||
void configSave(void)
|
||||
{
|
||||
/* TODO(Phase 1): write all registered options to "$S/ge007.ini". */
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Crash handler / stack traces.
|
||||
*
|
||||
* Catches signals (SIGSEGV etc.) and prints a backtrace + the game's state,
|
||||
* to make the inevitable porting crashes debuggable.
|
||||
*
|
||||
* Modelled on the PD port's port/src/crash.c (~360 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
|
||||
void crashInit(void)
|
||||
{
|
||||
/* TODO(Phase 1):
|
||||
* - install signal handlers (SIGSEGV, SIGFPE, SIGILL, SIGBUS)
|
||||
* - on crash: print backtrace (dbghelp on Windows, backtrace() on POSIX)
|
||||
* - dump relevant game state (current level, frame, etc.)
|
||||
*/
|
||||
sysLogPrintf(LOG_INFO, "crashInit: TODO (Phase 1)");
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Filesystem abstraction over the ROM + data dir.
|
||||
*
|
||||
* A small POSIX-ish file API so the port layer has a uniform interface across
|
||||
* Windows/Linux/macOS.
|
||||
*
|
||||
* Modelled on the PD port's port/src/fs.c (~295 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "fs.h"
|
||||
|
||||
struct FSFile {
|
||||
FILE *f;
|
||||
};
|
||||
|
||||
FSFile *fsOpen(const char *path, const char *mode)
|
||||
{
|
||||
FSFile *f = (FSFile *)calloc(1, sizeof(*f));
|
||||
if (!f) return NULL;
|
||||
f->f = fopen(path, mode);
|
||||
if (!f->f) { free(f); return NULL; }
|
||||
return f;
|
||||
}
|
||||
|
||||
void fsClose(FSFile *f)
|
||||
{
|
||||
if (f) { if (f->f) fclose(f->f); free(f); }
|
||||
}
|
||||
|
||||
int32_t fsRead(FSFile *f, void *buf, int32_t size)
|
||||
{
|
||||
if (!f || !f->f) return -1;
|
||||
return (int32_t)fread(buf, 1, (size_t)size, f->f);
|
||||
}
|
||||
|
||||
int32_t fsWrite(FSFile *f, const void *buf, int32_t size)
|
||||
{
|
||||
if (!f || !f->f) return -1;
|
||||
return (int32_t)fwrite(buf, 1, (size_t)size, f->f);
|
||||
}
|
||||
|
||||
int fsSeek(FSFile *f, int32_t offset, int whence)
|
||||
{
|
||||
if (!f || !f->f) return -1;
|
||||
return fseek(f->f, (long)offset, whence);
|
||||
}
|
||||
|
||||
int32_t fsTell(FSFile *f)
|
||||
{
|
||||
if (!f || !f->f) return -1;
|
||||
return (int32_t)ftell(f->f);
|
||||
}
|
||||
|
||||
int32_t fsSize(FSFile *f)
|
||||
{
|
||||
if (!f || !f->f) return -1;
|
||||
int32_t cur = ftell(f->f);
|
||||
fseek(f->f, 0, SEEK_END);
|
||||
int32_t sz = ftell(f->f);
|
||||
fseek(f->f, cur, SEEK_SET);
|
||||
return sz;
|
||||
}
|
||||
|
||||
int fsExists(const char *path)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f) { fclose(f); return 1; }
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Port RCP scheduler.
|
||||
*
|
||||
* Replaces the hardware task submission in src/sched.c. On the N64,
|
||||
* __scExec() hands a gfx/audio task to the RSP via osSpTaskLoad/StartGo.
|
||||
* Here, instead of writing RSP registers, we:
|
||||
* - gfx task: call videoSubmitCommands(display_list) -> the software RSP
|
||||
* - audio task: feed the mixed buffer to the audio layer
|
||||
* and reproduce the gfx/audio interleaving + VSync pacing the game expects.
|
||||
*
|
||||
* Modelled on the PD port's port/src/pdsched.c (~405 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 2.
|
||||
*
|
||||
* NOTE: GE's src/sched.c has speed-graph / profiling hooks
|
||||
* (speedgraphMarkerHandler) and a slightly different task model than PD.
|
||||
* Reproduce the exact gfx/audio/VSync behaviour; see
|
||||
* docs/PCPortResearch.md §9.3.
|
||||
*/
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
#include <PR/os.h>
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#include "sched.h"
|
||||
#include "fr.h" /* NUM_VIDEO_FRAME_BUFFERS */
|
||||
|
||||
#include "platform.h"
|
||||
#include "video.h"
|
||||
#include "audio.h"
|
||||
#include "system.h"
|
||||
|
||||
/*
|
||||
* Scheduler globals (normally defined in src/sched.c, which is EXCLUDED from
|
||||
* the PC build). init.c's schedulerInitThread() references os_scheduler and
|
||||
* gfxClient, so the port provides them here (matching src/sched.c:70-71).
|
||||
*/
|
||||
OSSched os_scheduler;
|
||||
OSScClient gfxClient[3];
|
||||
|
||||
/*
|
||||
* Scheduler/VI globals (normally defined in src/sched.c, EXCLUDED). fr.c
|
||||
* references these; the port provides them so it links. Values are the
|
||||
* sched.c initializers.
|
||||
*/
|
||||
s32 g_schedViCurrentFrameBuffer = 0;
|
||||
s32 g_ViChangeVideoModes[NUM_VIDEO_FRAME_BUFFERS] = {0, 0};
|
||||
OSViMode g_ViModes[NUM_VIDEO_FRAME_BUFFERS];
|
||||
OSViMode *g_ViModePtrs[NUM_VIDEO_FRAME_BUFFERS];
|
||||
|
||||
/*
|
||||
* Performance counters (normally in src/sched.c). speed_graph.c reads them
|
||||
* via get_counters().
|
||||
*/
|
||||
static u32 g_DisplayPerformanceCounters[4]; // clock, cmc, pipe, tmem
|
||||
u32 *get_counters(void) { return g_DisplayPerformanceCounters; }
|
||||
|
||||
/* Debug stderr control (normally in src/sched.c). boss.c calls it. */
|
||||
void permit_stderr(u32 flag) { (void)flag; }
|
||||
|
||||
/*
|
||||
* Scheduler API (declared in sched.h, normally defined in src/sched.c which
|
||||
* is EXCLUDED from the PC build). The port provides these so engine code that
|
||||
* calls osCreateScheduler/osScAddClient/osScGetCmdQ links. The real work —
|
||||
* driving the software RSP per frame — happens in geschedRunFrame().
|
||||
*/
|
||||
void osCreateScheduler(OSSched *s, void *stack, u8 mode, u32 numFields)
|
||||
{
|
||||
/* TODO(Phase 2): initialise the port scheduler state in *s. */
|
||||
(void)s; (void)stack; (void)mode; (void)numFields;
|
||||
sysLogPrintf(LOG_INFO, "osCreateScheduler: TODO (Phase 2)");
|
||||
}
|
||||
void osScAddClient(OSSched *s, OSScClient *c, OSMesgQueue *msgQ, OSScClient *next)
|
||||
{
|
||||
/* TODO(Phase 2): register a gfx/audio client. */
|
||||
(void)s; (void)c; (void)msgQ; (void)next;
|
||||
}
|
||||
void osScRemoveClient(OSSched *s, OSScClient *c)
|
||||
{
|
||||
(void)s; (void)c;
|
||||
}
|
||||
OSMesgQueue *osScGetCmdQ(OSSched *s)
|
||||
{
|
||||
/* TODO(Phase 2): return the scheduler's command queue. */
|
||||
(void)s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void geschedInit(void)
|
||||
{
|
||||
/* TODO(Phase 2): set up the scheduler + gfx client, mirroring
|
||||
* schedulerInitThread() in src/init.c but with the port's frame hooks. */
|
||||
sysLogPrintf(LOG_INFO, "geschedInit: TODO (Phase 2)");
|
||||
}
|
||||
|
||||
/*
|
||||
* Per-frame driver. Called by the main loop (or the VI vsync callback shim).
|
||||
* Sequence (mirrors PD's pdsched.c):
|
||||
* videoStartFrame()
|
||||
* ... game builds the display list ...
|
||||
* videoSubmitCommands(cmds)
|
||||
* videoEndFrame()
|
||||
*/
|
||||
void geschedRunFrame(void *displayList)
|
||||
{
|
||||
videoStartFrame();
|
||||
videoSubmitCommands((Gfx *)displayList);
|
||||
audioEndFrame();
|
||||
videoEndFrame();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Input: SDL2 keyboard/mouse/gamepad -> N64 controller structs.
|
||||
*
|
||||
* The game reads controllers via osContInit / osContStartReadData (src/joy.c).
|
||||
* This module populates the N64 controller state from SDL events.
|
||||
*
|
||||
* Modelled on the PD port's port/src/input.c (~1550 lines). Default bindings
|
||||
* follow the 1964GEPD / Xbox schemes (see the PD port README table).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 3.
|
||||
*/
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "config.h"
|
||||
#include "input.h"
|
||||
|
||||
static int numControllers = 1;
|
||||
|
||||
int inputInit(void)
|
||||
{
|
||||
/* TODO(Phase 3):
|
||||
* - SDL_InitSubSystem(INPUT)
|
||||
* - open gamepad(s)
|
||||
* - set up relative mouse mode for mouse-look
|
||||
* - load bindings from config
|
||||
*/
|
||||
sysLogPrintf(LOG_INFO, "inputInit: TODO (Phase 3)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void inputDestroy(void)
|
||||
{
|
||||
/* TODO(Phase 3) */
|
||||
}
|
||||
|
||||
void inputUpdate(void)
|
||||
{
|
||||
/* TODO(Phase 3): poll SDL events, write into the controller state that
|
||||
* osContStartReadData / osContRead will return. */
|
||||
}
|
||||
|
||||
int inputGetNumControllers(void)
|
||||
{
|
||||
return numControllers;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* libultra OS shims for the PC port.
|
||||
*
|
||||
* The game code calls the libultra OS API (threads, message queues, timers,
|
||||
* VI, PI, SI, SP, AI, cache, memory). On the PC none of that hardware exists,
|
||||
* so this file provides single-threaded / host-backed implementations.
|
||||
*
|
||||
* Modelled on the PD port's port/src/libultra.c (~490 lines). That file is the
|
||||
* reference implementation to adapt; this stub marks the surface area.
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1 (OS core) and Phase 2
|
||||
* (SP/VI), Phase 3 (AI/SI).
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <PR/os.h>
|
||||
#include <PR/os_internal.h>
|
||||
#include <PR/rcp.h>
|
||||
#include <PR/rdb.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "video.h"
|
||||
#include "audio.h"
|
||||
#include "input.h"
|
||||
#include "fs.h"
|
||||
#include "romdata.h"
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Globals the game expects to exist (normally set by osInitialize). */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
u64 osClockRate = 6250000; /* RSP counter rate (Hz) — see PD port */
|
||||
u32 osMemSize = 16 * 1024 * 1024; /* pretend 16MB RDRAM (+expansion) */
|
||||
/* TV type (normally set from console hardware in os/initialize.c, excluded).
|
||||
* The game reads it to pick PAL/NTSC behaviour (e.g. schedulerInitThread
|
||||
* checks osTvType == OS_TV_MPAL). Set to match the emulated region: PAL
|
||||
* consoles report MPAL, NTSC consoles report NTSC. */
|
||||
#ifdef REFRESH_PAL
|
||||
u32 osTvType = OS_TV_MPAL; /* EU: PAL (0=PAL 1=NTSC 2=MPAL) */
|
||||
#else
|
||||
u32 osTvType = OS_TV_NTSC; /* US/JP: NTSC */
|
||||
#endif
|
||||
s32 osResetType = 0;
|
||||
s32 osViClock = 0;
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Time */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* The R4300 "SP timer" is a free-running counter. Map it to the host clock.
|
||||
* The PD port scales host microseconds into the N64 counter domain so that
|
||||
* osGetTime()-based timing in the game behaves correctly.
|
||||
*/
|
||||
OSTime osGetTime(void)
|
||||
{
|
||||
/* TODO(Phase 1): scale sysGetMicroseconds() into the N64 counter domain. */
|
||||
return (OSTime)sysGetMicroseconds();
|
||||
}
|
||||
|
||||
u32 osGetCount(void)
|
||||
{
|
||||
return (u32)osGetTime();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Threads — single-threaded: create/start are no-ops, the "main" thread is */
|
||||
/* the host thread. The game's scheduler drives everything cooperatively. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osCreateThread(OSThread *t, OSId id, void (*entry)(void *), void *arg,
|
||||
void *sp, OSPri prio)
|
||||
{
|
||||
(void)entry; (void)arg; (void)sp; (void)prio;
|
||||
if (t) { t->id = id; t->state = OS_STATE_STOPPED; }
|
||||
}
|
||||
|
||||
void osStartThread(OSThread *t) { if (t) t->state = OS_STATE_RUNNING; }
|
||||
void osStopThread(OSThread *t) { if (t) t->state = OS_STATE_STOPPED; }
|
||||
void osDestroyThread(OSThread *t) { if (t) t->id = 0; }
|
||||
void osYieldThread(void) { /* cooperative: nothing to do */ }
|
||||
OSPri osGetThreadPri(OSThread *t) { (void)t; return 0; }
|
||||
void osSetThreadPri(OSId id, OSPri prio) { (void)id; (void)prio; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Message queues — used by the scheduler / VI / controller callbacks. */
|
||||
/* Implement a simple ring buffer; see PD port for the exact semantics. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osCreateMesgQueue(OSMesgQueue *mq, OSMesg first, int count)
|
||||
{
|
||||
/* TODO(Phase 1): allocate + init ring buffer. */
|
||||
(void)mq; (void)first; (void)count;
|
||||
}
|
||||
OSMesg osDequeueMesg(OSMesgQueue *mq)
|
||||
{
|
||||
/* TODO(Phase 1) */
|
||||
(void)mq;
|
||||
return NULL;
|
||||
}
|
||||
OSMesg osMesgQueueLast(OSMesgQueue *mq)
|
||||
{
|
||||
/* TODO(Phase 1) */
|
||||
(void)mq;
|
||||
return NULL;
|
||||
}
|
||||
void osEnqueueMesg(OSMesgQueue *mq, OSMesg msg)
|
||||
{
|
||||
/* TODO(Phase 1) */
|
||||
(void)mq; (void)msg;
|
||||
}
|
||||
|
||||
/*
|
||||
* High-level message API — the one the game actually uses (osSendMesg in 13
|
||||
* files, osRecvMesg in 26). The PD port's libultra.c defines these; in the
|
||||
* single-threaded port they are largely no-ops / immediate, but the scheduler
|
||||
* and audio code rely on the semantics, so implement them properly in Phase 1.
|
||||
*/
|
||||
s32 osSendMesg(OSMesgQueue *mq, OSMesg msg, s32 flag)
|
||||
{
|
||||
/* TODO(Phase 1): enqueue msg; return 0 on success. */
|
||||
(void)mq; (void)msg; (void)flag;
|
||||
return 0;
|
||||
}
|
||||
s32 osRecvMesg(OSMesgQueue *mq, OSMesg *msg, s32 flag)
|
||||
{
|
||||
/* TODO(Phase 1): dequeue into *msg; return 0 on success, -1 if empty. */
|
||||
(void)mq; (void)msg; (void)flag;
|
||||
return 0;
|
||||
}
|
||||
void osSetEventMesg(OSEvent e, OSMesgQueue *mq, OSMesg msg)
|
||||
{
|
||||
/* TODO(Phase 1) */
|
||||
(void)e; (void)mq; (void)msg;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* VI (video) — map onto the port video layer. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osViSetMode(OSViMode *vm)
|
||||
{
|
||||
/* TODO(Phase 2): translate the requested mode into a window/GL viewport. */
|
||||
(void)vm;
|
||||
}
|
||||
void osViSetEffect(OSViEffect ve) { (void)ve; }
|
||||
void osViSetSpecialFeatures(u32 f) { (void)f; }
|
||||
void osViVSyncCallback(OSMesgQueue *mq, OSMesg msg)
|
||||
{
|
||||
/* TODO(Phase 2): the port scheduler calls this each frame. */
|
||||
(void)mq; (void)msg;
|
||||
}
|
||||
void osViWaitVSync(void)
|
||||
{
|
||||
/* TODO(Phase 2): block until the next vsync (frame pacing). */
|
||||
}
|
||||
void osViSetSync(OSMesgQueue *mq, OSMesg msg) { (void)mq; (void)msg; }
|
||||
void osViBlack(u8 active)
|
||||
{
|
||||
/* TODO(Phase 2): if active, clear the screen (see PD port). */
|
||||
(void)active;
|
||||
}
|
||||
void osViSetYScale(f32 value) { (void)value; }
|
||||
void osViSetXScale(f32 value) { (void)value; }
|
||||
void osViSetEvent(OSMesgQueue *mq, OSMesg msg, u32 retraceCount)
|
||||
{ (void)mq; (void)msg; (void)retraceCount; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* AI (audio) — map onto the port audio layer. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osAiSetFrequency(u32 hz)
|
||||
{
|
||||
/* TODO(Phase 3): store the output rate; audio.c uses it. */
|
||||
(void)hz;
|
||||
}
|
||||
void osAiSetNextBuffer(void *buf, u32 size)
|
||||
{
|
||||
/* TODO(Phase 3): hand the mixed buffer to audioSetNextBuffer(). */
|
||||
(void)buf; (void)size;
|
||||
}
|
||||
u32 osAiGetLength(void)
|
||||
{
|
||||
/* TODO(Phase 3): return audioGetSamplesBuffered() in the right units. */
|
||||
return 0;
|
||||
}
|
||||
void osAiSetConvert(u32 convert) { (void)convert; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* PI (peripheral) — ROM/cart reads come from the loaded ROM image. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osPiCreateManager(OSMesgQueue *mq, int prio) { (void)mq; (void)prio; }
|
||||
int osPiStartDma(OSPiChannel ch, OSPiMsg *msg, int sync)
|
||||
{
|
||||
/* TODO(Phase 1): service a read from romdata (cart -> host memory). */
|
||||
(void)ch; (void)msg; (void)sync;
|
||||
return 1;
|
||||
}
|
||||
int osPiRawStartDma(int direction, void *src, void *dst, u32 size)
|
||||
{
|
||||
/* TODO(Phase 1): copy between ROM image and host memory. */
|
||||
if (src && dst && size) memcpy(dst, src, size);
|
||||
(void)direction;
|
||||
return 1;
|
||||
}
|
||||
u32 osPiGetStatus(void) { return 0; } /* not busy */
|
||||
|
||||
/*
|
||||
* PI device register read (normally src/libultra/io, EXCLUDED). token.c uses
|
||||
* it to read the cartridge token string. On the PC there is no cartridge
|
||||
* token register; return 0 (no token) so the game proceeds with an empty
|
||||
* token. TODO(Phase 1): optionally back with a value from the ROM/config.
|
||||
*/
|
||||
s32 osPiReadIo(u32 devAddr, u32 *data)
|
||||
{
|
||||
(void)devAddr;
|
||||
if (data) *data = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* SI (controller / eeprom / mempak) — map onto input + file-backed saves. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
s32 osContInit(OSMesgQueue *mesgq, u8 *bitpattern, OSContStatus *data)
|
||||
{
|
||||
/* TODO(Phase 3): report inputGetNumControllers() as connected. */
|
||||
(void)mesgq; (void)bitpattern; (void)data;
|
||||
return 0;
|
||||
}
|
||||
s32 osContStartReadData(OSMesgQueue *mesgq)
|
||||
{
|
||||
/* TODO(Phase 3): snapshot the current SDL input into the controller buf. */
|
||||
(void)mesgq;
|
||||
return 0;
|
||||
}
|
||||
s32 osContStartQuery(OSMesgQueue *mq)
|
||||
{
|
||||
/* TODO(Phase 3) */
|
||||
(void)mq;
|
||||
return 0;
|
||||
}
|
||||
void osContGetQuery(OSContStatus *status)
|
||||
{
|
||||
/* TODO(Phase 3): fill status for each connected controller. */
|
||||
(void)status;
|
||||
}
|
||||
void osContGetReadData(OSContPad *pad)
|
||||
{
|
||||
/* TODO(Phase 3): fill pad from the current SDL input snapshot. */
|
||||
(void)pad;
|
||||
}
|
||||
|
||||
/* EEPROM: back with a file in the data dir (see B2 — saves are EEPROM-based).
|
||||
* TODO(Phase 4): see docs/PCPortResearch.md §7. */
|
||||
int osEepromProbe(OSMesgQueue *mq) { (void)mq; return 0; }
|
||||
int osEepromRead(OSMesgQueue *mq, u16 addr, void *buf, u16 size)
|
||||
{ (void)mq; (void)addr; (void)buf; (void)size; return 0; }
|
||||
int osEepromWrite(OSMesgQueue *mq, u16 addr, void *buf, u16 size)
|
||||
{ (void)mq; (void)addr; (void)buf; (void)size; return 0; }
|
||||
int osEepromLongRead(OSMesgQueue *mq, u16 addr, void *buf, u16 size)
|
||||
{ (void)mq; (void)addr; (void)buf; (void)size; return 0; }
|
||||
int osEepromLongWrite(OSMesgQueue *mq, u16 addr, void *buf, u16 size)
|
||||
{ (void)mq; (void)addr; (void)buf; (void)size; return 0; }
|
||||
|
||||
/* Memory Pak (PFS) + Rumble Pak (motor): accessory detection only (see B2).
|
||||
* The PC has no Memory Pak; report "not plugged in" so the game proceeds
|
||||
* without rumble/mempak. TODO(Phase 4): optionally back PFS with a file. */
|
||||
s32 osPfsInit(OSMesgQueue *queue, OSPfs *pfs, int channel)
|
||||
{ (void)queue; (void)pfs; (void)channel; return PFS_ERR_NOPACK; }
|
||||
s32 osPfsIsPlug(OSMesgQueue *queue, u8 *pattern)
|
||||
{ (void)queue; if (pattern) *pattern = 0; return 0; }
|
||||
s32 osMotorInit(OSMesgQueue *mq, OSPfs *pfs, int channel)
|
||||
{ (void)mq; (void)pfs; (void)channel; return -1; }
|
||||
s32 osMotorStart(OSPfs *pfs) { (void)pfs; return -1; }
|
||||
s32 osMotorStop(OSPfs *pfs) { (void)pfs; return -1; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* SP (RSP) — the port scheduler (gesched.c) drives the software RSP, so */
|
||||
/* these are mostly no-ops / bookkeeping. See gesched.c. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osSpTaskLoad(OSTask *t) { (void)t; /* handled by gesched.c */ }
|
||||
void osSpTaskStartGo(OSTask *t) { (void)t; /* handled by gesched.c */ }
|
||||
void osSpTaskYield(void) { /* handled by gesched.c */ }
|
||||
void osSpFlush(void) { /* no-op */ }
|
||||
void osSpSetFifo(void *fifo, int size, int flags) { (void)fifo; (void)size; (void)flags; }
|
||||
void *osSpGetFifo(void) { return NULL; }
|
||||
int osSpTaskDone(void) { return 1; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* RDP — bypassed (the software RSP emits GL directly). No-ops. */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osDpSetStatus(u32 set, u32 clear) { (void)set; (void)clear; }
|
||||
u32 osDpGetStatus(void) { return 0; }
|
||||
int osDpSetNextBuffer(void *buf, int size) { (void)buf; (void)size; return 1; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Cache — no-op on the host (no split I/D cache to manage). */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osWritebackDCache(void *addr, int size) { (void)addr; (void)size; }
|
||||
void osWritebackDCacheAll(void) { }
|
||||
void osInvalICache(void *addr, int size) { (void)addr; (void)size; }
|
||||
void osInvalDCache(void *addr, int size) { (void)addr; (void)size; }
|
||||
void *osVirtualToPhysical(void *va) { return va; }
|
||||
void *osPhysicalToVirtual(unsigned int pa) { return (void *)pa; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Misc */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void osInitialize(void)
|
||||
{
|
||||
/* The game calls this early. On the PC there is little to do; the port
|
||||
* has already set up video/audio/input/rom in main(). */
|
||||
}
|
||||
|
||||
void osExit(int code) { sysExit(code); }
|
||||
|
||||
u32 osGetFpcCsr(void) { return 0; }
|
||||
void osSetFpcCsr(u32 csr) { (void)csr; }
|
||||
|
||||
/*
|
||||
* FPU CSR accessors used by init.c (the __os* variants are the raw libultra
|
||||
* names; init() saves/restores the FPU control/status register around the
|
||||
* decompress step). Unused on the PC but must link.
|
||||
*/
|
||||
u32 __osGetFpcCsr(void) { return 0; }
|
||||
void __osSetFpcCsr(u32 csr) { (void)csr; }
|
||||
|
||||
/* TLB unmap (libultra/os/unmaptlb.s). The PC has its own MMU; no-op. */
|
||||
void osUnmapTLB(int index) { (void)index; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Timers / interrupts */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
/* Timers: the port runs a single-threaded loop; timers are serviced by the
|
||||
* scheduler. TODO(Phase 1): wire to the port's frame clock. */
|
||||
int osSetTimer(OSTimer *timer, OSTime start, OSTime period,
|
||||
OSMesgQueue *mq, OSMesg msg)
|
||||
{ (void)timer; (void)start; (void)period; (void)mq; (void)msg; return 0; }
|
||||
int osStopTimer(OSTimer *timer) { (void)timer; return 0; }
|
||||
|
||||
/* Interrupt mask: no hardware interrupts on the PC. */
|
||||
OSIntMask osSetIntMask(OSIntMask mask) { (void)mask; return 0; }
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* PI / VI managers referenced by init.c (pi.c / vi.c are not compiled) */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
void piCreateManager(OSMesgQueue *mq, int prio)
|
||||
{
|
||||
/* ROM access is handled by romdata.c; no PI manager needed on the PC. */
|
||||
(void)mq; (void)prio;
|
||||
}
|
||||
void viDebugRemoved(void)
|
||||
{
|
||||
/* N64 debug VI hook; no-op on the PC. */
|
||||
}
|
||||
void viInit(void)
|
||||
{
|
||||
/* VI init (normally src/vi.c, EXCLUDED). boss.c calls it during startup.
|
||||
* TODO(Phase 2): initialise the port video layer here. */
|
||||
}
|
||||
|
||||
/* VI debug message queue (normally src/vi.c, EXCLUDED). fr.c references it. */
|
||||
OSMesgQueue vi_c_debug_MQ;
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* PC entry point for the GoldenEye 007 port.
|
||||
*
|
||||
* Replaces the N64 boot path (boot.s -> init() -> mainproc() -> bossEntry()).
|
||||
* On the PC we:
|
||||
* 1. set up system / config / fs / rom
|
||||
* 2. load the ROM and map its segments
|
||||
* 3. init video (SDL2 + GL), audio, input
|
||||
* 4. hand control to the game's mainproc() / bossEntry()
|
||||
*
|
||||
* Modelled on the PD port's port/src/main.c.
|
||||
*
|
||||
* STATUS: scaffolding stub — fill in during Phase 1.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "config.h"
|
||||
#include "fs.h"
|
||||
#include "romdata.h"
|
||||
#include "video.h"
|
||||
#include "audio.h"
|
||||
#include "input.h"
|
||||
#include "mixer.h"
|
||||
|
||||
/* Defined in the game (src/init.c). The port calls into the real game entry. */
|
||||
extern void mainproc(void *args);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
sysLogPrintf(LOG_INFO, "GoldenEye 007 PC port starting "
|
||||
"(%s, %s)", GE007_ROMID, GE007_VERSION_HASH);
|
||||
|
||||
/* 1. Platform + config + filesystem. */
|
||||
configLoad();
|
||||
|
||||
/* 2. Load the ROM and map segments. */
|
||||
if (romdataInit() != 0) {
|
||||
sysLogPrintf(LOG_ERROR, "Failed to load ROM (expected a .z64 in the "
|
||||
"data/ dir, see README)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 3. Video / audio / input. */
|
||||
if (videoInit() != 0) {
|
||||
sysLogPrintf(LOG_ERROR, "videoInit failed");
|
||||
return 1;
|
||||
}
|
||||
audioInit();
|
||||
mixerInit();
|
||||
inputInit();
|
||||
|
||||
/* 4. Hand control to the game.
|
||||
*
|
||||
* mainproc() is defined in src/init.c, which IS compiled for the PC (see
|
||||
* A3 in docs/PCPortResearch.md). init.c's N64-only init() is compiled but
|
||||
* NOT called — its hardware deps (segment starts, decompress, TLB, FPU
|
||||
* CSR) are stubbed in port/src/n64stubs.c, and the libultra OS calls are
|
||||
* shimmed in port/src/libultra.c. mainproc() itself just sets up the
|
||||
* threads and calls schedulerInitThread() + bossEntry().
|
||||
*/
|
||||
mainproc(NULL);
|
||||
|
||||
/* mainproc() normally does not return; clean up if it does. */
|
||||
inputDestroy();
|
||||
mixerDestroy();
|
||||
audioDestroy();
|
||||
videoDestroy();
|
||||
romdataDestroy();
|
||||
configSave();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Audio mixing.
|
||||
*
|
||||
* On the N64 the RSP audio ucode does final mix / sample handling before the
|
||||
* AI DMA. With the RSP emulated in software, this runs on the CPU.
|
||||
*
|
||||
* Modelled on the PD port's port/src/mixer.c (~720 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 3. The exact interface
|
||||
* depends on how GE's libaudio output is wired (see src/audi.c).
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
#include "mixer.h"
|
||||
#include "audio.h"
|
||||
|
||||
void mixerInit(void)
|
||||
{
|
||||
/* TODO(Phase 3) */
|
||||
}
|
||||
|
||||
void mixerDestroy(void)
|
||||
{
|
||||
/* TODO(Phase 3) */
|
||||
}
|
||||
|
||||
void mixerProcess(const s16 *in, u32 frames)
|
||||
{
|
||||
/* TODO(Phase 3): process + queue via audioSetNextBuffer(). */
|
||||
(void)in; (void)frames;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* N64-boot-specific symbol stubs.
|
||||
*
|
||||
* We compile src/init.c (for mainproc() — see A3 in docs/PCPortResearch.md),
|
||||
* but its N64-only init() is never called on the PC. The compiler still needs
|
||||
* the symbols init() references to resolve at link time, so this file provides
|
||||
* inert stubs for the ones that are NOT part of the libultra OS API (those
|
||||
* live in libultra.c).
|
||||
*
|
||||
* These are the boot/segment/decompress/TLB symbols, normally provided by the
|
||||
* linker script, boot.s, the inflate code, and the tlb_*.s assembly, plus the
|
||||
* rmon (remote monitor) host-communication functions from src/rmon.c.
|
||||
*
|
||||
* STATUS: scaffolding — the stubs let the compiled set link. The init() ones
|
||||
* are never executed on the PC (init() is not called; mainproc() is); the
|
||||
* rmon ones are inert (rmon is the N64 host debugger).
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
/* --- Segment start/end getters (normally linker-script symbols) --------- */
|
||||
/* On the PC the ROM is loaded by romdata.c; these are unused. Return 0. */
|
||||
|
||||
u32 get_csegmentSegmentStart(void) { return 0; }
|
||||
u32 get_cdataSegmentRomStart(void) { return 0; }
|
||||
u32 get_cdataSegmentRomEnd(void) { return 0; }
|
||||
u32 get_inflateSegmentRomStart(void) { return 0; }
|
||||
u32 get_inflateSegmentRomEnd(void) { return 0; }
|
||||
|
||||
/*
|
||||
* Linker segment boundary pointers (normally linker-script symbols, declared
|
||||
* `extern u32 *` in bondgame.h). init() takes their ADDRESSES for a size calc
|
||||
* and a PI DMA; it is compiled but never called on the PC. Dummy pointers so
|
||||
* the symbols exist and link.
|
||||
*/
|
||||
u32 *_codeSegmentRomStart = NULL;
|
||||
u32 *_inflateSegmentRomStart = NULL;
|
||||
u32 *_alt_startSegmentRomStart = NULL;
|
||||
u32 *_alt_startSegmentStart = NULL;
|
||||
|
||||
/* --- Decompressor (src/inflate, not built for PC) ----------------------- */
|
||||
/* init() calls this to unpack the data segment. Unused on the PC. */
|
||||
u32 jump_decompressfile(u32 source, u32 target, u32 buffer)
|
||||
{
|
||||
(void)source; (void)target; (void)buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- TLB (src/tlb_*.s, not built for PC) -------------------------------- */
|
||||
/* The PC has its own MMU; the N64 TLB miss handler is irrelevant. */
|
||||
void initTLBPrepareContext(void) { /* no-op */ }
|
||||
|
||||
/* Address of the N64 TLB-miss handler. init() copies it to K0BASE; unused. */
|
||||
void resolve_TLBaddress_for_InvalidHit(void) { /* no-op */ }
|
||||
|
||||
/* --- Remote monitor (src/rmon.c, not built for PC) ---------------------- */
|
||||
/* rmon is the N64 remote monitor (host debugger); it has no PC equivalent. */
|
||||
/* init.c's rmonCreateThread() starts a thread running rmonMain; stub it as a */
|
||||
/* no-op so the thread (if ever started) does nothing. */
|
||||
void rmonMain(void) { /* no-op: rmon (remote monitor) is N64-only */ }
|
||||
|
||||
/* rmon host I/O + token/status, referenced by game code (indy_commands.c,
|
||||
* indy_comms.c, boss.c, token.c). All inert on the PC. */
|
||||
void osReadHost(void *buffer, u32 size) { (void)buffer; (void)size; }
|
||||
void osWriteHost(void *buffer, u32 size) { (void)buffer; (void)size; }
|
||||
s32 rmonGetToken(void) { return 0; }
|
||||
s32 rmonStatus(void) { return 0; }
|
||||
|
||||
/*
|
||||
* osSyncPrintf is the game's debug/printf channel (also used by the assert()
|
||||
* macros). Route it to stderr so assert failures and debug output are visible
|
||||
* during porting. TODO(Phase 1): integrate with the port's sysLogPrintf.
|
||||
*/
|
||||
void osSyncPrintf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* PRNG — ported verbatim from src/random.s (MIPS assembly, not compiled for
|
||||
* the PC). This is a REAL implementation, not a stub: the game uses it for
|
||||
* genuine gameplay logic, so a no-op would silently corrupt behaviour.
|
||||
*
|
||||
* - randomGetNextFrom() feeds the CRC in src/game/crc.c
|
||||
* - g_randomSeed is persisted in replay state (src/game/ramromreplay.c)
|
||||
* - randomGetNext() drives RANDOMFRAC()/RANDOMGETNEXT_F32() (bondconstants.h)
|
||||
*
|
||||
* The assembly operates on the low 32 bits of the seed (the high 32 bits are
|
||||
* never read), but the seed is stored as a full u64. We mirror each
|
||||
* instruction with explicit masks to guarantee bit-exactness. The initial
|
||||
* seed is the two .words in random.s: 0xAB8D9F77 (hi) / 0x81280783 (lo).
|
||||
*/
|
||||
|
||||
#include <ultra64.h>
|
||||
#include <random.h>
|
||||
|
||||
u64 g_randomSeed = 0xAB8D9F7781280783ULL;
|
||||
|
||||
/*
|
||||
* MIPS64 shift helpers, matching the .s instructions exactly:
|
||||
* dsll32 rd,rs,n -> (rs << n) & 0xFFFFFFFF (low 32 bits kept, hi zeroed)
|
||||
* dsrl32 rd,rs,n -> (rs >> n) & 0xFFFFFFFF (low 32 bits kept, hi zeroed)
|
||||
* Plain `<<`/`>>` on u64 match dsll/dsrl (full 64-bit shifts).
|
||||
*/
|
||||
static inline u64 dsll32(u64 x, int n) { return (x << n) & 0xFFFFFFFFULL; }
|
||||
static inline u64 dsrl32(u64 x, int n) { return (x >> n) & 0xFFFFFFFFULL; }
|
||||
|
||||
/*
|
||||
* Advance the global seed and return the new low 32 bits.
|
||||
* Mirrors randomGetNext in random.s line-by-line.
|
||||
*/
|
||||
u32 randomGetNext(void)
|
||||
{
|
||||
u64 a0 = g_randomSeed;
|
||||
u64 a1, a2;
|
||||
|
||||
a2 = dsll32(a0, 0x1f); /* dsll32 $a2, $a0, 0x1f */
|
||||
a1 = a0 << 0x1f; /* dsll $a1, $a0, 0x1f */
|
||||
a2 = a2 >> 0x1f; /* dsrl $a2, $a2, 0x1f */
|
||||
a1 = dsrl32(a1, 0); /* dsrl32 $a1, $a1, 0 */
|
||||
a0 = dsll32(a0, 0xc); /* dsll32 $a0, $a0, 0xc */
|
||||
a2 = a2 | a1; /* or $a2, $a2, $a1 */
|
||||
a0 = dsrl32(a0, 0); /* dsrl32 $a0, $a0, 0 */
|
||||
a2 = a2 ^ a0; /* xor $a2, $a2, $a0 */
|
||||
a0 = a2 >> 0x14; /* dsrl $a0, $a2, 0x14 */
|
||||
a0 = a0 & 0xfff; /* andi $a0, $a0, 0xfff*/
|
||||
a0 = a0 ^ a2; /* xor $a0, $a0, $a2 */
|
||||
|
||||
g_randomSeed = a0; /* sd $a0, g_randomSeed */
|
||||
return (u32)a0; /* v0 = a0 & 0xFFFFFFFF */
|
||||
}
|
||||
|
||||
/* Set the seed (the .s adds 1 before storing). */
|
||||
void randomSetSeed(u32 seed)
|
||||
{
|
||||
g_randomSeed = (u64)seed + 1; /* daddiu $a0,$a0,1; sd $a0, g_randomSeed */
|
||||
}
|
||||
|
||||
/*
|
||||
* Same transform as randomGetNext, but applied to *seed (a caller-owned u64)
|
||||
* instead of the global. Mirrors randomGetNextFrom in random.s.
|
||||
*/
|
||||
u32 randomGetNextFrom(u64 *seed)
|
||||
{
|
||||
u64 a3 = *seed; /* ld $a3, ($a0) */
|
||||
u64 a1, a2;
|
||||
|
||||
a2 = dsll32(a3, 0x1f); /* dsll32 $a2, $a3, 0x1f */
|
||||
a1 = a3 << 0x1f; /* dsll $a1, $a3, 0x1f */
|
||||
a2 = a2 >> 0x1f; /* dsrl $a2, $a2, 0x1f */
|
||||
a1 = dsrl32(a1, 0); /* dsrl32 $a1, $a1, 0 */
|
||||
a3 = dsll32(a3, 0xc); /* dsll32 $a3, $a3, 0xc */
|
||||
a2 = a2 | a1; /* or $a2, $a2, $a1 */
|
||||
a3 = dsrl32(a3, 0); /* dsrl32 $a3, $a3, 0 */
|
||||
a2 = a2 ^ a3; /* xor $a2, $a2, $a3 */
|
||||
a3 = a2 >> 0x14; /* dsrl $a3, $a2, 0x14 */
|
||||
a3 = a3 & 0xfff; /* andi $a3, $a3, 0xfff*/
|
||||
a3 = a3 ^ a2; /* xor $a3, $a3, $a2 */
|
||||
|
||||
*seed = a3; /* sd $a3, ($a0) */
|
||||
return (u32)a3; /* v0 = a3 & 0xFFFFFFFF */
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* ROM loading: reads the .z64 ROM from the data dir and exposes its segments.
|
||||
*
|
||||
* The N64 build links assets in from the ROM at fixed addresses. The PC port
|
||||
* loads the ROM file into a heap image and maps the game's data segments into
|
||||
* it, then patches the segment table so asset references resolve.
|
||||
*
|
||||
* Modelled on the PD port's port/src/romdata.c (~605 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1.
|
||||
*
|
||||
* NOTE: GE's ROM layout (segments, asset offsets) differs from PD's. The
|
||||
* segment table and asset map must be derived from this decomp's
|
||||
* ge007.ld / imagelist.u.csv / assets/ layout. See docs/PCPortResearch.md.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "fs.h"
|
||||
#include "romdata.h"
|
||||
|
||||
static u8 *rom = NULL;
|
||||
static u32 romSize = 0;
|
||||
|
||||
/* Expected ROM file names per region (place one in the data/ dir). */
|
||||
static const char *romFileName(void)
|
||||
{
|
||||
#if GE007_IS_PAL
|
||||
return "ge007.pal-final.z64";
|
||||
#else
|
||||
return "ge007.ntsc-final.z64";
|
||||
#endif
|
||||
}
|
||||
|
||||
int romdataInit(void)
|
||||
{
|
||||
const char *path = sysResolvePath("$S/" romFileName());
|
||||
|
||||
/* TODO(Phase 1):
|
||||
* - open + read the whole ROM into `rom`
|
||||
* - validate the N64 magic ("Nintendo" @ 0x100) + region
|
||||
* - build the segment map (code/data/assets) from the decomp's layout
|
||||
* - patch the game's segment table so IMAGESEG / asset refs resolve
|
||||
*/
|
||||
sysLogPrintf(LOG_INFO, "romdataInit: loading %s (TODO Phase 1)", path);
|
||||
|
||||
FSFile *f = fsOpen(path, "rb");
|
||||
if (!f) {
|
||||
sysLogPrintf(LOG_ERROR, "romdataInit: cannot open %s", path);
|
||||
return -1;
|
||||
}
|
||||
romSize = (u32)fsSize(f);
|
||||
rom = (u8 *)malloc(romSize);
|
||||
if (!rom || fsRead(f, rom, romSize) != (int32_t)romSize) {
|
||||
sysLogPrintf(LOG_ERROR, "romdataInit: failed to read ROM");
|
||||
fsClose(f);
|
||||
return -1;
|
||||
}
|
||||
fsClose(f);
|
||||
|
||||
if (romSize < 0x104 || memcmp(rom + 0x100, "Nintendo", 8) != 0) {
|
||||
sysLogPrintf(LOG_ERROR, "romdataInit: bad N64 ROM magic");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void romdataDestroy(void)
|
||||
{
|
||||
free(rom);
|
||||
rom = NULL;
|
||||
romSize = 0;
|
||||
}
|
||||
|
||||
const u8 *romdataGetRom(void) { return rom; }
|
||||
u32 romdataGetRomSize(void) { return romSize; }
|
||||
|
||||
const void *romdataMapVa(u32 va)
|
||||
{
|
||||
/* TODO(Phase 1): map an N64 RDRAM VA (0xA0000000+) into the image. */
|
||||
(void)va;
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Platform primitives: time, logging, paths, exit.
|
||||
*
|
||||
* Modelled on the PD port's port/src/system.c (~315 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
|
||||
/* --- Time --------------------------------------------------------------- */
|
||||
|
||||
uint64_t sysGetMicroseconds(void)
|
||||
{
|
||||
/* TODO(Phase 1): use a monotonic clock.
|
||||
* - Windows: QueryPerformanceCounter
|
||||
* - POSIX: clock_gettime(CLOCK_MONOTONIC)
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t sysGetTime(void)
|
||||
{
|
||||
/* TODO(Phase 1): wall clock (time(NULL)). */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sysSleep(uint32_t micros)
|
||||
{
|
||||
/* TODO(Phase 1): Sleep() / usleep(). */
|
||||
(void)micros;
|
||||
}
|
||||
|
||||
/* --- Logging ------------------------------------------------------------ */
|
||||
|
||||
void sysLogPrintf(enum LogLevel level, const char *fmt, ...)
|
||||
{
|
||||
static const char *tags[] = { "ERROR", "WARN ", "INFO ", "DEBUG" };
|
||||
va_list ap;
|
||||
fprintf(stderr, "[%s] ", tags[level]);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/* --- Paths -------------------------------------------------------------- */
|
||||
|
||||
static char exeDir[512] = ".";
|
||||
|
||||
const char *sysGetExeDir(void)
|
||||
{
|
||||
/* TODO(Phase 1): resolve the executable's directory.
|
||||
* - Windows: GetModuleFileName
|
||||
* - Linux: readlink /proc/self/exe
|
||||
* - macOS: _NSGetExecutablePath
|
||||
*/
|
||||
return exeDir;
|
||||
}
|
||||
|
||||
const char *sysResolvePath(const char *path)
|
||||
{
|
||||
static char out[1024];
|
||||
/* TODO(Phase 1): expand "$S/" (data dir) and "$E/" (exe dir) prefixes. */
|
||||
strncpy(out, path, sizeof(out) - 1);
|
||||
out[sizeof(out) - 1] = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
/* --- Lifecycle ---------------------------------------------------------- */
|
||||
|
||||
void sysExit(int code)
|
||||
{
|
||||
exit(code);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* RSP/ASP/GSP microcode segment markers.
|
||||
*
|
||||
* On the N64 these are linker-defined addresses of the RSP/ASP/GSP microcode
|
||||
* embedded in the ROM (see src/rspboot.s, src/gspboot.s, src/aspboot.s — the
|
||||
* .s files are not compiled for the PC). The game references them to build
|
||||
* SP tasks:
|
||||
*
|
||||
* src/audi.c: ucode_boot = (u64*)rspbootTextStart
|
||||
* ucode = (u64*)aspMainTextStart
|
||||
* ucode_data = (u64*)aspMainDataStart
|
||||
* src/game/rsp.c: ucode_boot_size = (s32)gsp3DTextStart - (s32)rspbootTextStart
|
||||
*
|
||||
* The game declares them as `extern long long int <name>[];`.
|
||||
*
|
||||
* STATUS: dummy definitions so the link succeeds.
|
||||
*
|
||||
* - GRAPHICS (Phase 2): fast3d interprets the GBI display list directly and
|
||||
* never executes the gsp3D microcode, so gsp3DTextStart/gsp3DDataStart are
|
||||
* unused metadata. The ucode_boot_size differences compute to garbage but
|
||||
* are never read on the graphics path. Dummies are fine permanently here.
|
||||
*
|
||||
* - AUDIO (Phase 3): this is the one place it could matter. If the port
|
||||
* emulates the ASP by actually RUNNING the aspMain microcode, then
|
||||
* aspMainTextStart/aspMainDataStart must point at the real microcode bytes
|
||||
* in the loaded ROM (follow the PD port's ld/pd.ld RSP_TEXT_SEGMENT model).
|
||||
* If audio is handled entirely on the CPU (bypassing the ASP), these
|
||||
* dummies are fine permanently. DECISION NEEDED in Phase 3.
|
||||
*
|
||||
* TODO(Phase 2/3): wire these to the real microcode in the ROM image if the
|
||||
* corresponding path executes microcode.
|
||||
*/
|
||||
|
||||
/* RSP boot microcode (src/rspboot.s). */
|
||||
long long int rspbootTextStart[1] = {0};
|
||||
long long int rspbootTextEnd[1] = {0};
|
||||
|
||||
/* GSP 3D microcode (src/gspboot.s). Unused by fast3d (see note above). */
|
||||
long long int gsp3DTextStart[1] = {0};
|
||||
long long int gsp3DDataStart[1] = {0};
|
||||
|
||||
/*
|
||||
* ASP audio microcode (src/aspboot.s).
|
||||
* TODO(Phase 3): if the port runs the ASP microcode, point these at the real
|
||||
* microcode bytes in the ROM (see file header note).
|
||||
*/
|
||||
long long int aspMainTextStart[1] = {0};
|
||||
long long int aspMainDataStart[1] = {0};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Small shared helpers for the port layer.
|
||||
*
|
||||
* Modelled on the PD port's port/src/utils.c (~195 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
uint16_t readU16BE(const void *p)
|
||||
{
|
||||
const uint8_t *b = (const uint8_t *)p;
|
||||
return (uint16_t)((b[0] << 8) | b[1]);
|
||||
}
|
||||
|
||||
uint32_t readU32BE(const void *p)
|
||||
{
|
||||
const uint8_t *b = (const uint8_t *)p;
|
||||
return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
|
||||
((uint32_t)b[2] << 8) | (uint32_t)b[3];
|
||||
}
|
||||
|
||||
void writeU16BE(void *p, uint16_t v)
|
||||
{
|
||||
uint8_t *b = (uint8_t *)p;
|
||||
b[0] = (uint8_t)(v >> 8);
|
||||
b[1] = (uint8_t)(v & 0xff);
|
||||
}
|
||||
|
||||
void writeU32BE(void *p, uint32_t v)
|
||||
{
|
||||
uint8_t *b = (uint8_t *)p;
|
||||
b[0] = (uint8_t)(v >> 24);
|
||||
b[1] = (uint8_t)(v >> 16);
|
||||
b[2] = (uint8_t)(v >> 8);
|
||||
b[3] = (uint8_t)(v & 0xff);
|
||||
}
|
||||
|
||||
int clampi(int v, int lo, int hi)
|
||||
{
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
|
||||
float clampf(float v, float lo, float hi)
|
||||
{
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Video: SDL2 window + OpenGL context + frame pacing.
|
||||
*
|
||||
* Owns the GL context that the software RSP (fast3d) renders into. Drives the
|
||||
* frame boundary hooks called by the port scheduler (gesched.c).
|
||||
*
|
||||
* Modelled on the PD port's port/src/video.c (~590 lines).
|
||||
*
|
||||
* STATUS: scaffolding stub — implement during Phase 1 (window + clear) and
|
||||
* Phase 2 (frame pacing, options).
|
||||
*/
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
#include "config.h"
|
||||
#include "video.h"
|
||||
|
||||
/* fast3d entry points (see port/fast3d/gfx_api.h). */
|
||||
extern void gfx_init(const void *settings);
|
||||
extern void gfx_destroy(void);
|
||||
extern void gfx_start_frame(void);
|
||||
extern void gfx_run(void *commands);
|
||||
extern void gfx_end_frame(void);
|
||||
|
||||
static int initDone = 0;
|
||||
static int vidVsync = 1;
|
||||
static int vidFpsLimit = 0;
|
||||
static int vidFullscreen = 0;
|
||||
static int vidMaximize = 0;
|
||||
|
||||
int videoInit(void)
|
||||
{
|
||||
/* TODO(Phase 1):
|
||||
* - SDL_Init(VIDEO)
|
||||
* - create window (title "GoldenEye 007", default 640x480 or config)
|
||||
* - create GL 3.0+ context
|
||||
* - build the GfxInitSettings (window-manager API = SDL2, rendering API
|
||||
* = OpenGL) and call gfx_init()
|
||||
* - apply config: vsync, fps limit, fullscreen, maximize
|
||||
*/
|
||||
sysLogPrintf(LOG_INFO, "videoInit: TODO (Phase 1)");
|
||||
initDone = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void videoDestroy(void)
|
||||
{
|
||||
if (initDone) {
|
||||
gfx_destroy();
|
||||
initDone = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void videoStartFrame(void)
|
||||
{
|
||||
if (initDone) gfx_start_frame();
|
||||
}
|
||||
|
||||
void videoSubmitCommands(Gfx *cmds)
|
||||
{
|
||||
if (initDone) gfx_run(cmds);
|
||||
}
|
||||
|
||||
void videoEndFrame(void)
|
||||
{
|
||||
if (!initDone) return;
|
||||
gfx_end_frame();
|
||||
/* TODO(Phase 2): FPS accounting + framerate limiting. */
|
||||
}
|
||||
|
||||
void videoSetVsync(int enabled) { vidVsync = enabled; /* TODO: SDL_GL_SetSwapInterval */ }
|
||||
void videoSetFramerateLimit(int fps) { vidFpsLimit = fps; /* TODO */ }
|
||||
int videoGetFullscreen(void) { return vidFullscreen; }
|
||||
void videoSetFullscreen(int enabled) { vidFullscreen = enabled; /* TODO */ }
|
||||
int videoGetMaximize(void) { return vidMaximize; }
|
||||
void videoSetMaximize(int enabled) { vidMaximize = enabled; /* TODO */ }
|
||||
float videoGetFPS(void) { return 0.0f; /* TODO */ }
|
||||
|
||||
PD_CONSTRUCTOR static void videoConfigInit(void)
|
||||
{
|
||||
configRegisterInt("Video.Vsync", &vidVsync, 0, 1);
|
||||
configRegisterInt("Video.FramerateLimit", &vidFpsLimit, 0, 240);
|
||||
configRegisterInt("Video.Fullscreen", &vidFullscreen, 0, 1);
|
||||
configRegisterInt("Video.Maximize", &vidMaximize, 0, 1);
|
||||
}
|
||||
Reference in new issue
Block a user