diff --git a/port/include/video.h b/port/include/video.h index 8da5cf5c..346e2210 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -48,6 +48,10 @@ void videoResetTextureCache(void); /* Current FPS (measured). */ float videoGetFPS(void); +/* Snapshot live window geometry into the config vars (call before configSave + * on a clean exit). No-op if the window isn't up. */ +void videoSaveWindowState(void); + #ifdef __cplusplus } #endif diff --git a/port/src/main.c b/port/src/main.c index e0e11171..71992d1d 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -38,10 +38,59 @@ extern void mainproc(void *args); extern OSThread mainThread; /* src/init.c:75 */ +/* name:number pairs for the 21 solo levels (matches tools_pc/level_sweep.sh + * and playtest.sh --list). boss.c decodes -level_XX as d0*10 + d1 - 0x210. */ +static const struct { const char *name; const char *num; } kSoloLevels[] = { + {"Dam","33"}, {"Facility","34"}, {"Runway","35"}, {"Surface1","36"}, + {"Bunker1","09"}, {"Silo","20"}, {"Frigate","26"}, {"Surface2","43"}, + {"Bunker2","27"}, {"Statue","22"}, {"Archives","24"}, {"Streets","29"}, + {"Depot","30"}, {"Train","25"}, {"Jungle","37"}, {"Control","23"}, + {"Caverns","39"}, {"Cradle","41"}, {"Aztec","28"}, {"Egypt","32"}, + {"Cuba","54"}, +}; + +static void portPrintVersion(void) +{ + printf("GoldenEye 007 PC port\n" + " rom : %s\n" + " platform : %s\n" + " build : %s\n", + GE007_ROMID, GE007_TARGET_PLATFORM, GE007_VERSION_HASH); +} + +static void portPrintHelp(const char *argv0) +{ + portPrintVersion(); + printf("\nusage: %s [options] [-level_XX]\n\n" + " --help this message\n" + " --version build id only\n" + " -level_XX boot straight into a solo level (per-level\n" + " memory pools are auto-injected)\n\n" + "config: ge007.ini in the data dir (written on first run).\n\n" + "solo levels (-level_XX):\n", argv0 ? argv0 : "ge007"); + for (size_t i = 0; i < sizeof(kSoloLevels) / sizeof(kSoloLevels[0]); ++i) { + printf(" %-10s -level_%s\n", kSoloLevels[i].name, kSoloLevels[i].num); + } +} + +static void portAtExit(void) +{ + /* Clean-exit only (exit(0) from videoPumpEvents). Crash/fatal paths call + * abort(), which does not run atexit handlers. */ + videoSaveWindowState(); + configSave(); +} + int main(int argc, char **argv) { sysSetArgs(argc, argv); + if (sysArgCheck("--version")) { portPrintVersion(); return 0; } + if (sysArgCheck("--help") || sysArgCheck("-h")) { + portPrintHelp(argv[0]); + return 0; + } + sysLogPrintf(LOG_INFO, "GoldenEye 007 PC port starting " "(%s, %s)", GE007_ROMID, GE007_VERSION_HASH); @@ -50,6 +99,7 @@ int main(int argc, char **argv) /* 1. Platform + config + filesystem. */ configLoad(); + atexit(portAtExit); /* persist config + window geometry on clean exit */ /* 2. Load the ROM and map segments. */ if (romdataInit() != 0) { diff --git a/port/src/video.c b/port/src/video.c index 7e3f56bc..70e6cc1b 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -57,6 +57,18 @@ static int cfgMSAA = 1; /* 1/2/4/8 samples; 1 = off static int cfgTexFilter = 1; /* 0 = nearest (crisp), 1 = bilinear */ static int cfgFullscreen = 0; /* 0 = windowed, 1 = borderless fullscreen */ +/* + * [Window] persistence. Defaults are sentinels that reproduce the old + * hardcoded behaviour (native size, centered): W/H = 0 -> native, X/Y = -1 -> + * let SDL centre the window. videoSaveWindowState() writes the live geometry + * back into these on a clean exit (see main.c's atexit handler). + */ +static int cfgWinW = 0; +static int cfgWinH = 0; +static int cfgWinX = -1; +static int cfgWinY = -1; +static int cfgWinMax = 0; + PD_CONSTRUCTOR static void videoConfigInit(void) { configRegisterInt("Video.VSync", &cfgVSync, 0, 1); @@ -64,6 +76,11 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.MSAA", &cfgMSAA, 1, 8); configRegisterInt("Video.TextureFilter", &cfgTexFilter, 0, 1); configRegisterInt("Video.Fullscreen", &cfgFullscreen, 0, 1); + configRegisterInt("Window.Width", &cfgWinW, 0, 16384); + configRegisterInt("Window.Height", &cfgWinH, 0, 16384); + configRegisterInt("Window.X", &cfgWinX, -1, 16384); + configRegisterInt("Window.Y", &cfgWinY, -1, 16384); + configRegisterInt("Window.Maximized", &cfgWinMax, 0, 1); } static u32 frames = 0; @@ -88,19 +105,23 @@ int videoInit(void) /* MSAA: snap the requested sample count down to a supported power of two. */ gfx_msaa_level = cfgMSAA >= 8 ? 8 : cfgMSAA >= 4 ? 4 : cfgMSAA >= 2 ? 2 : 1; + int winW = cfgWinW > 0 ? cfgWinW : GE_NATIVE_W; + int winH = cfgWinH > 0 ? cfgWinH : GE_NATIVE_H; + int havePos = (cfgWinX >= 0 && cfgWinY >= 0); + struct GfxInitSettings set = { .wapi = wmAPI, .rapi = renderingAPI, .window_settings = { .title = "GoldenEye 007", - .width = GE_NATIVE_W, - .height = GE_NATIVE_H, - .x = 100, - .y = 100, + .width = winW, + .height = winH, + .x = havePos ? cfgWinX : 100, + .y = havePos ? cfgWinY : 100, .fullscreen = cfgFullscreen != 0, .fullscreen_is_exclusive = false, - .maximized = false, - .centered = true, + .maximized = cfgWinMax != 0, + .centered = !havePos, .allow_hidpi = false, }, }; @@ -292,6 +313,37 @@ float videoGetFPS(void) return vidAvgFPS; } +/* + * Snapshot the current window geometry into the [Window] / [Video] config + * vars so the next configSave() persists it. Called from main.c's atexit + * handler (runs on the host thread, which owns the window). A maximized or + * fullscreen window keeps its last restored size/pos on disk; only the + * flag is updated. + */ +void videoSaveWindowState(void) +{ + if (!initDone || !wmAPI) { + return; + } + + int32_t fs = wmAPI->get_fullscreen_state ? wmAPI->get_fullscreen_state() : 0; + int32_t mx = wmAPI->get_maximized_state ? wmAPI->get_maximized_state() : 0; + cfgFullscreen = fs ? 1 : 0; + cfgWinMax = mx ? 1 : 0; + + if (!fs && !mx && wmAPI->get_dimensions) { + uint32_t w = 0, h = 0; + int32_t x = 0, y = 0; + wmAPI->get_dimensions(&w, &h, &x, &y); + if (w > 0 && h > 0) { + cfgWinW = (int)w; + cfgWinH = (int)h; + cfgWinX = x < 0 ? 0 : x; + cfgWinY = y < 0 ? 0 : y; + } + } +} + void videoUpdateNativeResolution(s32 w, s32 h) { if (w <= 0 || h <= 0) {