forked from Ports/goldeneye-pc-port
D257: Game.AllUnlocked option — all levels, 007 mode, full cheat menu (#79)
Port-layer only (no game-code edits): - main.c seeds debug_enable_all_levels_flag + debug_007_unlock_flag after configLoad() when Game.AllUnlocked is set (same class as the existing GE_UNLOCK_ALL getenv hook). - libultra.c eep shim patches the save block at read time (the exact fileValidateSaves pattern: block 4, 5x96 bytes): sets all 20 progression-gated cheat-unlock bits per slot and fills every empty stage/difficulty completion time with the max (0x3FF), preserving existing player times, then recomputes each slot's CRC with the game's own fileGenerateCRC so validation passes. The completion-time half is required: with any SP cheat active, g_AppendCheatSinglePlayer makes mission select accept only STAGESTATUS_COMPLETED levels, so an unlock-only save locks every level once a cheat is turned on. - optionsoverlay.c: 'All unlocked' toggle row (default ON for this cut). Verified: fresh-eep GE_SAVELOG shows all 5 slots stored==calc; user playtest confirms all levels at each difficulty, 007 mode, populated cheat menu, cheats working, mission select intact with all cheats on.
This commit is contained in:
1 parent
c51f01ad14
commit
c62803f890
5 files changed
+166
No files matched your search
@@ -534,6 +534,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
|||||||
| D254 | **Linux crash handler destroyed its own logs: `ge007.crash.log` only ever contained the SIGABRT block, never the original SEGV.** The POSIX `crashHandler` (`port/src/crash.c`) finished by calling `sysFatalError()` → `abort()` → SIGABRT, which re-entered the *same* handler (SIGABRT was one of the installed signals); the second pass reopened the log with `"wb"` and overwrote the first pass's SEGV block — faulting PC, signal, everything. The Deck firefight crash (D255) came back as a bare `SIGNAL: 6` abort record for exactly this reason, forcing offline disassembly of the CI binary to recover what the log should have said. **Fix (port-only):** re-entrancy guard (`inCrashHandler`, second pass `_exit(128+sig)` immediately) + terminate directly with `_exit()` instead of `sysFatalError()` (the Windows path already terminated directly for the same reason) + the Linux block now dumps the faulting context: `FAULT ADDR` (`si_addr`) and the full general register set (Rax/Rcx/Rdx/Rsi/Rdi/R8–R11, Rsp/Rbp) from the `ucontext`, so a field crash now names the bad pointer on its face. | FIXED — needs one Deck crash to prove the new format in the field. |
|
| D254 | **Linux crash handler destroyed its own logs: `ge007.crash.log` only ever contained the SIGABRT block, never the original SEGV.** The POSIX `crashHandler` (`port/src/crash.c`) finished by calling `sysFatalError()` → `abort()` → SIGABRT, which re-entered the *same* handler (SIGABRT was one of the installed signals); the second pass reopened the log with `"wb"` and overwrote the first pass's SEGV block — faulting PC, signal, everything. The Deck firefight crash (D255) came back as a bare `SIGNAL: 6` abort record for exactly this reason, forcing offline disassembly of the CI binary to recover what the log should have said. **Fix (port-only):** re-entrancy guard (`inCrashHandler`, second pass `_exit(128+sig)` immediately) + terminate directly with `_exit()` instead of `sysFatalError()` (the Windows path already terminated directly for the same reason) + the Linux block now dumps the faulting context: `FAULT ADDR` (`si_addr`) and the full general register set (Rax/Rcx/Rdx/Rsi/Rdi/R8–R11, Rsp/Rbp) from the `ucontext`, so a field crash now names the bad pointer on its face. | FIXED — needs one Deck crash to prove the new format in the field. |
|
||||||
| D255 | **Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working — user got past the opening crouch tile).** Crash during gunplay; abort-unwind backtrace points at `sub_GAME_7F09BAC4` (`vtxstore.c:158`) ← `sub_GAME_7F09BBBC` (`:220`) ← `lvlManageMpGame` (`lv.c:1302`) ← `bossMainloop` (`boss.c:567`). Disassembling the exact CI binary at the fault PC (`0x200db135`) gives `mov 0x14(%rax),%edi; mov 0x10(%rdi),%rax`: the function walks the active PropRecord list keeping `type == 1` — which is **PROP_TYPE_OBJ, not CHR** (the decompiler cast to the union's first member and misnamed the field `ChrRecord.chrflags`; it is actually `ObjectRecord.model` at `0x14`) — then dereferences `model->obj`. Two port-relevant wrinkles: (a) the load is **32-bit zero-extended** because the decomp types the field through the `CHRFLAG` enum, so a model pointer ≥ 4 GB would truncate to garbage; (b) the loop skips records whose `model->obj` is NULL but *dereferences* `model` unconditionally, so an active object with a NULL/freed `model` faults — plausible in a firefight where destructibles (crates etc.) die. Not reproduced on WSL after equivalent play. **Next:** the D254 handler now captures registers at fault time (`rax` = the ObjectRecord, `rdi` = its model value) — one more Deck crash identifies which case: tiny/zero `rdi` → destroy-path ordering; plausible-but-wrong `rdi` → stale/reused record or truncation. | OPEN — awaiting register capture from the Deck. |
|
| D255 | **Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working — user got past the opening crouch tile).** Crash during gunplay; abort-unwind backtrace points at `sub_GAME_7F09BAC4` (`vtxstore.c:158`) ← `sub_GAME_7F09BBBC` (`:220`) ← `lvlManageMpGame` (`lv.c:1302`) ← `bossMainloop` (`boss.c:567`). Disassembling the exact CI binary at the fault PC (`0x200db135`) gives `mov 0x14(%rax),%edi; mov 0x10(%rdi),%rax`: the function walks the active PropRecord list keeping `type == 1` — which is **PROP_TYPE_OBJ, not CHR** (the decompiler cast to the union's first member and misnamed the field `ChrRecord.chrflags`; it is actually `ObjectRecord.model` at `0x14`) — then dereferences `model->obj`. Two port-relevant wrinkles: (a) the load is **32-bit zero-extended** because the decomp types the field through the `CHRFLAG` enum, so a model pointer ≥ 4 GB would truncate to garbage; (b) the loop skips records whose `model->obj` is NULL but *dereferences* `model` unconditionally, so an active object with a NULL/freed `model` faults — plausible in a firefight where destructibles (crates etc.) die. Not reproduced on WSL after equivalent play. **Next:** the D254 handler now captures registers at fault time (`rax` = the ObjectRecord, `rdi` = its model value) — one more Deck crash identifies which case: tiny/zero `rdi` → destroy-path ordering; plausible-but-wrong `rdi` → stale/reused record or truncation. | OPEN — awaiting register capture from the Deck. |
|
||||||
| D256 | **Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing `data/` — reported as "saves do not work at all" on Steam Deck.** `romdataInit` finds the ROM in `$S/` (CWD `data/`) *or* `$E/` (exe dir) *or* `./`, but `sysResolvePath`'s POSIX `$S` branch (used by `ge007.eep` and `ge007.ini`) only ever produced a CWD-relative path — its "fallback" was `./data/`, identical to the primary. Launch from any other CWD (Steam non-steam-game shortcut, terminal in `~`): ROM loads via the exe-dir candidate, game runs, but every EEPROM/config write hits `fopen(..., "wb")` on a nonexistent `./data/` and fails with a stderr-only "cannot write" — saves never persist, and **every launch also starts with default settings** (F10 changes don't stick), which presents as "draw distance/LOD is low" on levels like Dam where the user had raised `Video.DrawDistance` on another machine. The Windows branch already did CWD-else-exedir; the POSIX branch now mirrors it (`readlink("/proc/self/exe")`) and creates the directory best-effort so first-run writes don't fail. **Verified on WSL:** with exe+`data/` in `/tmp/fb2` and CWD=`/`, config/eeprom/ROM all resolve into `/tmp/fb2/data/` and a 25 s run completes clean; the pre-fix binary in the same layout fails to find the ROM at all. **Caveat:** if the user's Deck launch *was* from the bundle dir (CWD had `data/`), this fix does not explain their save failure — need the save-flow detail (does the memory-card screen appear? slot confirm? "saved" message?) before closing the report. Side note found while testing: a `data/` without the generated sidecars (`pccg-*/`, `pcmodels-*/` from `tools_pc/d69_emit.py`/`d43_emit.py`) starts fine but faults in `load_bg_file` (`bg.c:865`) on level load — the reserve functions warn "stage loads will fault" and return 0; expected degraded mode, not a new bug. | FIXED (path half) — save-flow detail still owed from the user. |
|
| D256 | **Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing `data/` — reported as "saves do not work at all" on Steam Deck.** `romdataInit` finds the ROM in `$S/` (CWD `data/`) *or* `$E/` (exe dir) *or* `./`, but `sysResolvePath`'s POSIX `$S` branch (used by `ge007.eep` and `ge007.ini`) only ever produced a CWD-relative path — its "fallback" was `./data/`, identical to the primary. Launch from any other CWD (Steam non-steam-game shortcut, terminal in `~`): ROM loads via the exe-dir candidate, game runs, but every EEPROM/config write hits `fopen(..., "wb")` on a nonexistent `./data/` and fails with a stderr-only "cannot write" — saves never persist, and **every launch also starts with default settings** (F10 changes don't stick), which presents as "draw distance/LOD is low" on levels like Dam where the user had raised `Video.DrawDistance` on another machine. The Windows branch already did CWD-else-exedir; the POSIX branch now mirrors it (`readlink("/proc/self/exe")`) and creates the directory best-effort so first-run writes don't fail. **Verified on WSL:** with exe+`data/` in `/tmp/fb2` and CWD=`/`, config/eeprom/ROM all resolve into `/tmp/fb2/data/` and a 25 s run completes clean; the pre-fix binary in the same layout fails to find the ROM at all. **Caveat:** if the user's Deck launch *was* from the bundle dir (CWD had `data/`), this fix does not explain their save failure — need the save-flow detail (does the memory-card screen appear? slot confirm? "saved" message?) before closing the report. Side note found while testing: a `data/` without the generated sidecars (`pccg-*/`, `pcmodels-*/` from `tools_pc/d69_emit.py`/`d43_emit.py`) starts fine but faults in `load_bg_file` (`bg.c:865`) on level load — the reserve functions warn "stage loads will fault" and return 0; expected degraded mode, not a new bug. | FIXED (path half) — save-flow detail still owed from the user. |
|
||||||
|
| D257 | **New `Game.AllUnlocked` option (F10, default ON for this cut): skip N64 progression entirely — all 21 solo levels selectable at every difficulty, 007 mode available, and the cheat menu fully populated. Port-layer only; no game-code edits.** Three mechanisms, all in `port/`: (1) `main.c` seeds the two live `LEFTOVERDEBUG` RAM flags after `configLoad()` — `debug_enable_all_levels_flag` (all levels) and `debug_007_unlock_flag` (007 mode; both in `src/game/debugmenu_handler.c`, same class as the existing `GE_UNLOCK_ALL` getenv hook in the getter); (2) the eep shim (`libultra.c geEepromPatchAllCheats`) patches the save block at read time — special-casing `fileValidateSaves`'s exact pattern (block 4, 5×96 bytes) — setting all 20 progression-gated cheat-unlock bits per slot (`unlocked_cheats_1/2/3`; the cheat menu row on mode-select only renders when ≥1 is set, `front.c interface_menu06_modesel`) **and** filling every *empty* stage/difficulty completion-time field with the max time 0x3FF (existing player times preserved); both are then CRC'd per slot with the game's own `fileGenerateCRC` or `fileValidateSaves` would wipe the slots. (3) `optionsoverlay.c` row so users can toggle OFF for faithful progression. **Why the completion-time half is required (found by user repro):** when ANY single-player cheat is active, `g_AppendCheatSinglePlayer` makes mission-select (`get_highest_unlocked_difficulty_for_level`, front.c) accept only levels with status `STAGESTATUS_COMPLETED (3)`, not merely `UNLOCKED (1)` — AllUnlocked's debug-flag path returns 1, so enabling a cheat locked every level ("could only do Silo"). Reading the save as a fully-completed campaign satisfies both branches. Time bit-math mirrors `fileGet/SetSaveStageDifficultyTime` verbatim (10-bit fields, difficulties 0–2; 007 times are virtual). **Pitfall hit during implementation:** the first mirror of `save_data` used `times[80]` instead of `(SP_LEVEL_MAX-1)*4 = 76`, making `sizeof` 100≠96 so the block-read size check silently never matched — now enforced by a compile-time `ge_save_slot_size_check`. **Semantics:** unlocked cheats/completions persist to `ge007.eep` on any subsequent save write (self-consistent; mirrors N64 where completing a level permanently unlocks its cheat). No *active* cheats are enabled (invincibility/all-guns stay OFF); weapons remain per-mission pickups. **Verified:** fresh eep — all 5 slots validate `stored==calc` under `GE_SAVELOG=1`; user playtest: all levels selectable at each difficulty, 007 mode present, cheat menu populated, cheats toggle and work (time-modifying cheats untested by user), mission select intact with all cheats ON. | FIXED — verified by user playtest; PR open. |
|
||||||
|
|
||||||
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
||||||
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
|
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
|
||||||
|
|||||||
@@ -1188,6 +1188,126 @@ static void geEepromStore(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* D257: Game.AllUnlocked -- mirror of src/game/file.h save_data (96 bytes;
|
||||||
|
* keep in sync). Two things are patched in at read time:
|
||||||
|
* - the progression-gated cheat-unlock bits (front.c
|
||||||
|
* frontCheckIfCheatIsUnlocked) test per-LEVEL bits in
|
||||||
|
* unlocked_cheats_1/2/3 that normal play sets when each level is
|
||||||
|
* completed, so a fresh save has an empty cheat menu;
|
||||||
|
* - every stage/difficulty completion time. When ANY single-player cheat
|
||||||
|
* is active, g_AppendCheatSinglePlayer makes the mission-select screen
|
||||||
|
* (front.c get_highest_unlocked_difficulty_for_level) only accept levels
|
||||||
|
* whose status is STAGESTATUS_COMPLETED (3), not merely UNLOCKED (1) --
|
||||||
|
* so AllUnlocked must read as a fully-completed campaign or enabling a
|
||||||
|
* cheat locks every level. Existing (nonzero) player times are kept;
|
||||||
|
* only empty slots get the max time (0x3FF). */
|
||||||
|
typedef struct ge_save_slot {
|
||||||
|
s32 chksum1;
|
||||||
|
s32 chksum2;
|
||||||
|
u8 completion_bitflags;
|
||||||
|
u8 flag_007;
|
||||||
|
u8 music_vol;
|
||||||
|
u8 sfx_vol;
|
||||||
|
u16 options;
|
||||||
|
u8 unlocked_cheats_1;
|
||||||
|
u8 unlocked_cheats_2;
|
||||||
|
u8 unlocked_cheats_3;
|
||||||
|
u8 padding;
|
||||||
|
u8 times[76]; /* (SP_LEVEL_MAX-1)*4: 19 levels x 4 difficulties */
|
||||||
|
} ge_save_slot; /* sizeof == 96 == save_data (file.h) -- enforced below */
|
||||||
|
|
||||||
|
/* If this ever fires, the mirror drifted from save_data and the AllUnlocked
|
||||||
|
* cheat patch silently no-ops (the block-read size check stops matching). */
|
||||||
|
typedef char ge_save_slot_size_check[(sizeof(ge_save_slot) == 96) ? 1 : -1];
|
||||||
|
|
||||||
|
/* Game-side CRC (src/game/crc.c) over [completion_bitflags, next slot),
|
||||||
|
* stored in the slot's own chksum1/2 -- exactly what fileValidateSaves
|
||||||
|
* re-checks after the block read. */
|
||||||
|
extern void fileGenerateCRC(u8 *addressA, u8 *addressB, void *retval);
|
||||||
|
|
||||||
|
extern s32 portAllUnlocked; /* port/src/video.c */
|
||||||
|
|
||||||
|
/* Bit math mirrors fileGetSaveStageDifficultyTime / fileSetDifficultyStageTime
|
||||||
|
* (src/game/file2.c): 10-bit time fields, offset = (difficulty*20+level)*10,
|
||||||
|
* difficulties 0..2 (agent/secret/00) only -- 007 times are virtual. */
|
||||||
|
static u32 geSaveGetTime(const u8 *t, s32 difficulty, s32 level)
|
||||||
|
{
|
||||||
|
s32 offset = (difficulty * 20 + level) * 10;
|
||||||
|
s32 index = offset >> 3;
|
||||||
|
switch (7 - (offset & 7)) {
|
||||||
|
case 7: return ((u32)(t[index] & 0xFF) << 2) | ((u32)(t[index + 1] & 0xc0) >> 6);
|
||||||
|
case 5: return ((u32)(t[index] & 0x3f) << 4) | ((u32)(t[index + 1] & 0xf0) >> 4);
|
||||||
|
case 3: return ((u32)(t[index] & 0x0f) << 6) | ((u32)(t[index + 1] & 0xfc) >> 2);
|
||||||
|
case 1: return ((u32)(t[index] & 0x03) << 8) | (u32)(t[index + 1] & 0xFF);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void geSaveSetTime(u8 *t, s32 difficulty, s32 level, u32 newtime)
|
||||||
|
{
|
||||||
|
s32 offset = (difficulty * 20 + level) * 10;
|
||||||
|
s32 index = offset >> 3;
|
||||||
|
switch (7 - (offset & 7)) {
|
||||||
|
case 7:
|
||||||
|
t[index] = (u8)((t[index] & 0x00) | ((newtime >> 2) & 0xFF));
|
||||||
|
t[index + 1] = (u8)((t[index + 1] & 0x3F) | ((newtime << 6) & 0xC0));
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
t[index] = (u8)((t[index] & 0xC0) | ((newtime >> 4) & 0x3F));
|
||||||
|
t[index + 1] = (u8)((t[index + 1] & 0x0F) | ((newtime << 4) & 0xF0));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
t[index] = (u8)((t[index] & 0xF0) | ((newtime >> 6) & 0x0F));
|
||||||
|
t[index + 1] = (u8)((t[index + 1] & 0x03) | ((newtime << 2) & 0xFC));
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
t[index] = (u8)((t[index] & 0xFC) | ((newtime >> 8) & 0x03));
|
||||||
|
t[index + 1] = (u8)(newtime & 0xFF);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void geEepromPatchAllCheats(u8 *buf)
|
||||||
|
{
|
||||||
|
/* buf holds the five save slots read from block 4. The sixth local slot
|
||||||
|
* is only a CRC end-boundary for the last one (fileGenerateCRC reads
|
||||||
|
* [A, B), so its contents are irrelevant). */
|
||||||
|
static ge_save_slot slots[6];
|
||||||
|
memcpy(slots, buf, sizeof(ge_save_slot) * 5);
|
||||||
|
memset(&slots[5], 0, sizeof(ge_save_slot));
|
||||||
|
|
||||||
|
int changed = 0;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
/* Cheat ids are level ids 0..19 (CHEAT_INPUT_BUFFER_SIZE == 20):
|
||||||
|
* bits 0-7 in _1, 8-15 in _2, 16-19 in the low nibble of _3. */
|
||||||
|
if (slots[i].unlocked_cheats_1 != 0xFF ||
|
||||||
|
slots[i].unlocked_cheats_2 != 0xFF ||
|
||||||
|
(slots[i].unlocked_cheats_3 & 0x0F) != 0x0F) {
|
||||||
|
slots[i].unlocked_cheats_1 = 0xFF;
|
||||||
|
slots[i].unlocked_cheats_2 = 0xFF;
|
||||||
|
slots[i].unlocked_cheats_3 |= 0x0F;
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
/* Fully-completed campaign: fill empty completion times with the
|
||||||
|
* max (0x3FF). Needed so mission select still works while any cheat
|
||||||
|
* is active (see header comment); player records are preserved. */
|
||||||
|
for (s32 diff = 0; diff < 3; diff++) {
|
||||||
|
for (s32 lvl = 0; lvl < 20; lvl++) {
|
||||||
|
if (geSaveGetTime(slots[i].times, diff, lvl) == 0) {
|
||||||
|
geSaveSetTime(slots[i].times, diff, lvl, 0x3FF);
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changed) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
fileGenerateCRC(&slots[i].completion_bitflags, (u8 *)&slots[i + 1],
|
||||||
|
&slots[i]);
|
||||||
|
memcpy(buf, slots, sizeof(ge_save_slot) * 5);
|
||||||
|
}
|
||||||
|
|
||||||
static s32 geEepromRW(u8 block, u8 *buf, int nbytes, int write)
|
static s32 geEepromRW(u8 block, u8 *buf, int nbytes, int write)
|
||||||
{
|
{
|
||||||
u32 off = (u32)block * 8;
|
u32 off = (u32)block * 8;
|
||||||
@@ -1198,6 +1318,11 @@ static s32 geEepromRW(u8 block, u8 *buf, int nbytes, int write)
|
|||||||
geEepromStore();
|
geEepromStore();
|
||||||
} else {
|
} else {
|
||||||
memcpy(buf, s_eeprom + off, nbytes);
|
memcpy(buf, s_eeprom + off, nbytes);
|
||||||
|
/* fileValidateSaves' block read: five save slots from block 4. */
|
||||||
|
if (portAllUnlocked && block == 4 &&
|
||||||
|
nbytes == (int)(sizeof(ge_save_slot) * 5)) {
|
||||||
|
geEepromPatchAllCheats(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,33 @@ int main(int argc, char **argv)
|
|||||||
configLoad();
|
configLoad();
|
||||||
atexit(portAtExit); /* persist config + window geometry on clean exit */
|
atexit(portAtExit); /* persist config + window geometry on clean exit */
|
||||||
|
|
||||||
|
/* 1a. D257: Game.AllUnlocked (default ON) -- seed the game's own RAM
|
||||||
|
* unlock flags so mission select offers every solo level at every
|
||||||
|
* difficulty plus 007 mode, with no save data required. Both are
|
||||||
|
* plain s32 globals in src/game/debugmenu_handler.c (compiled because
|
||||||
|
* the PC build defines LEFTOVERDEBUG); file2.c's
|
||||||
|
* fileIsStageUnlockedAtDifficulty() and front.c's 007-mode gate OR
|
||||||
|
* them in ahead of the EEPROM completion bits. Port-layer memory
|
||||||
|
* writes only -- no game-logic edits (AGENTS rule 2), same class as
|
||||||
|
* the existing GE_UNLOCK_ALL getenv hook in the getter. No active
|
||||||
|
* cheats (invincibility / all guns) are enabled; weapons remain
|
||||||
|
* per-mission pickups as on the N64. Separately, the eep shim
|
||||||
|
* (libultra.c geEepromPatchAllCheats) sets every progression-gated
|
||||||
|
* cheat-unlock bit in the save block at read time (per-slot CRC
|
||||||
|
* recomputed via the game's own fileGenerateCRC), so the cheat
|
||||||
|
* menu is fully populated without completed levels. */
|
||||||
|
{
|
||||||
|
extern s32 portAllUnlocked; /* port/src/video.c */
|
||||||
|
extern s32 debug_enable_all_levels_flag; /* src/game/debugmenu_handler.c */
|
||||||
|
extern s32 debug_007_unlock_flag; /* ditto */
|
||||||
|
if (portAllUnlocked) {
|
||||||
|
debug_enable_all_levels_flag = 1;
|
||||||
|
debug_007_unlock_flag = 1;
|
||||||
|
sysLogPrintf(LOG_INFO, "all-unlocked: RAM unlock flags seeded "
|
||||||
|
"(Game.AllUnlocked=1)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 2. Load the ROM and map segments. */
|
/* 2. Load the ROM and map segments. */
|
||||||
if (romdataInit() != 0) {
|
if (romdataInit() != 0) {
|
||||||
sysLogPrintf(LOG_ERROR, "Failed to load ROM (expected a .z64 in the "
|
sysLogPrintf(LOG_ERROR, "Failed to load ROM (expected a .z64 in the "
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ static struct Row rows[] = {
|
|||||||
/* D216: read once at title-stage load (lv.c) -- toggling it takes effect
|
/* D216: read once at title-stage load (lv.c) -- toggling it takes effect
|
||||||
* the next time the title screen is reached. */
|
* the next time the title screen is reached. */
|
||||||
{ "Game.SkipIntro", "Skip intro", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 },
|
{ "Game.SkipIntro", "Skip intro", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 },
|
||||||
|
/* D257: everything-unlocked goodie (default ON). Consumed at startup by
|
||||||
|
* main.c -- applies from the next launch. */
|
||||||
|
{ "Game.AllUnlocked", "All unlocked", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 },
|
||||||
};
|
};
|
||||||
#define NUM_ROWS ((int)(sizeof(rows) / sizeof(rows[0])))
|
#define NUM_ROWS ((int)(sizeof(rows) / sizeof(rows[0])))
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,15 @@ s32 portSkipIntro = 0;
|
|||||||
* original damage flash. */
|
* original damage flash. */
|
||||||
s32 portNoHitFlash = 0;
|
s32 portNoHitFlash = 0;
|
||||||
|
|
||||||
|
/* D257: Game.AllUnlocked — ship-with-everything-unlocked goodie, ON by
|
||||||
|
* default for this cut. Consumed once at startup by main.c, which sets the
|
||||||
|
* game's own RAM unlock flags (debug_enable_all_levels_flag /
|
||||||
|
* debug_007_unlock_flag in src/game/debugmenu_handler.c, live because the PC
|
||||||
|
* build defines LEFTOVERDEBUG) — port-layer memory writes only, no game-code
|
||||||
|
* edits. Takes effect at launch; toggling it mid-session applies next run.
|
||||||
|
* 0 = faithful N64 progression (levels unlock as you complete them). */
|
||||||
|
s32 portAllUnlocked = 1;
|
||||||
|
|
||||||
/* D211: Video.FovScale as a multiplier on the render FOV. Applied game-side
|
/* D211: Video.FovScale as a multiplier on the render FOV. Applied game-side
|
||||||
* at the guPerspectiveF chokepoint (src/fr.c) so it lands BEFORE the CPU
|
* at the guPerspectiveF chokepoint (src/fr.c) so it lands BEFORE the CPU
|
||||||
* pre-multiplies projection x view into the combined world matrix — the
|
* pre-multiplies projection x view into the combined world matrix — the
|
||||||
@@ -200,6 +209,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
|
|||||||
configRegisterFloat("Game.ScreenShakeIntensity", &portScreenShakeScale, 0.0f, 10.0f);
|
configRegisterFloat("Game.ScreenShakeIntensity", &portScreenShakeScale, 0.0f, 10.0f);
|
||||||
configRegisterInt("Game.SkipIntro", &portSkipIntro, 0, 1);
|
configRegisterInt("Game.SkipIntro", &portSkipIntro, 0, 1);
|
||||||
configRegisterInt("Game.NoHitFlash", &portNoHitFlash, 0, 1);
|
configRegisterInt("Game.NoHitFlash", &portNoHitFlash, 0, 1);
|
||||||
|
configRegisterInt("Game.AllUnlocked", &portAllUnlocked, 0, 1);
|
||||||
configRegisterInt("Video.VSync", &cfgVSync, 0, 1);
|
configRegisterInt("Video.VSync", &cfgVSync, 0, 1);
|
||||||
configRegisterInt("Video.FpsCap", &cfgFpsCap, 0, 1000);
|
configRegisterInt("Video.FpsCap", &cfgFpsCap, 0, 1000);
|
||||||
configRegisterInt("Video.MSAA", &cfgMSAA, 1, 8);
|
configRegisterInt("Video.MSAA", &cfgMSAA, 1, 8);
|
||||||
|
|||||||
Reference in new issue
Block a user