diff --git a/include/system.h b/include/system.h index 1ad033e23..965a7eeeb 100644 --- a/include/system.h +++ b/include/system.h @@ -17,6 +17,9 @@ int sysGetDiscID(char *discID); void sysInitDev9(void); void sysShutdownDev9(void); void sysReset(); +// Deferred halves of the old sysReset module set (lazy boot, 2026-07-21). Idempotent one-shots. +void sysLoadAudioModules(void); +void sysLoadLaunchModules(void); void sysExecExit(void); int sysLaunchDisc(void); // boot the physical PS2 disc in the drive; <0 (stays in OPL) on failure void sysPowerOff(void); diff --git a/src/opl.c b/src/opl.c index 31e307086..48e62ef9e 100644 --- a/src/opl.c +++ b/src/opl.c @@ -3000,6 +3000,12 @@ static void deferredInit(void) guiSetBootStatusSticky(_l(_STR_BOOT_BUILDING_MENU)); // boot-step localizer (IO thread) -- reaching // here means the device init chain cleared; see gui.c + // Launch-support modules (isofs + genvmc), moved OFF sysReset's boot-critical path (lazy boot, + // 2026-07-21). Loaded here on the IO worker: resident long before the menu becomes interactive, + // so no launch ever loads a module mid-sequence (Delta-4 doctrine). The autolaunch fork, which + // never reaches this handler, calls the same idempotent one-shot synchronously. + sysLoadLaunchModules(); + // inform GUI main init part is over struct gui_update_t *id = guiOpCreate(GUI_INIT_DONE); if (id) @@ -3049,6 +3055,9 @@ static void deferredAudioInit(void) int ret; guiSetBootStatusSticky(_l(_STR_BOOT_LOADING_SOUNDS)); // boot-step localizer (IO thread) -- see gui.c + // libsd + audsrv, moved OFF sysReset's boot-critical path (lazy boot, 2026-07-21): this handler + // is their consumers' natural seam -- audioInit binds audsrv right below. + sysLoadAudioModules(); audioInit(); ret = sfxInit(1); if (ret < 0) @@ -3341,6 +3350,10 @@ int main(int argc, char *argv[]) } if (argc >= 5) { + // The autolaunch legs skip deferredInit (which loads these for the GUI path), and their + // launch prep needs isofs (ISO probing/PS2Logo) + genvmc (per-game VMC validation) resident + // BEFORE the launch sequence starts -- never load modules inside it (Delta-4 doctrine). + sysLoadLaunchModules(); /* argv[0] boot path argv[1] game->startup argv[2] str to u32 game->start_sector diff --git a/src/system.c b/src/system.c index 3c0acade5..8c01e6a2c 100644 --- a/src/system.c +++ b/src/system.c @@ -62,6 +62,9 @@ extern unsigned int size_eesync_irx; #define MAX_MODULES 64 static void *g_sysLoadedModBuffer[MAX_MODULES]; +// Deferred-loader latches (audio + launch-support one-shots below); cleared by sysReset. +static unsigned char sysAudioModsLoaded = 0; +static unsigned char sysLaunchModsLoaded = 0; static s32 sysLoadModuleLock = -1; #define ELF_MAGIC 0x464c457f @@ -240,6 +243,10 @@ void sysReset() // clears modules list memset(g_sysLoadedModBuffer, 0, sizeof(g_sysLoadedModBuffer)); + // ...and the deferred-loader latches: after an IOP reboot NOTHING is resident, so the audio and + // launch-support one-shots below must reload on their next call (CodeRabbit review of #252). + sysAudioModsLoaded = 0; + sysLaunchModsLoaded = 0; // load modules LOG("[IOMANX]:\n"); @@ -258,14 +265,13 @@ void sysReset() sysLoadModuleBuffer(&poweroff_irx, size_poweroff_irx, 0, NULL); LOG("[USBD]:\n"); sysLoadModuleBuffer(&usbd_irx, size_usbd_irx, 0, NULL); - LOG("[ISOFS]:\n"); - sysLoadModuleBuffer(&isofs_irx, size_isofs_irx, 0, NULL); - LOG("[GENVMC]:\n"); - sysLoadModuleBuffer(&genvmc_irx, size_genvmc_irx, 0, NULL); - LOG("[LIBSD]:\n"); - sysLoadModuleBuffer(&libsd_irx, size_libsd_irx, 0, NULL); - LOG("[AUDSRV]:\n"); - sysLoadModuleBuffer(&audsrv_irx, size_audsrv_irx, 0, NULL); + // isofs/genvmc/libsd/audsrv are NO LONGER loaded here (POPSLoader-shape lazy boot, maintainer + // directive 2026-07-21): they serialized four module DMAs onto the boot-critical path of EVERY + // flavor while nothing needs them before the menu. libsd+audsrv load in deferredAudioInit (their + // consumers' existing deferred seam); isofs+genvmc load via sysLoadLaunchModules() -- from + // deferredInit on the IO worker for the GUI path (resident long before any user launch, never AT + // launch per the Delta-4 no-module-loads-in-the-launch-sequence doctrine), and synchronously on + // the autolaunch fork which skips deferredInit entirely. #ifdef PADEMU int ds3pads = 1; // only one pad enabled @@ -287,6 +293,36 @@ void sysReset() poweroffSetCallback(&poweroffHandler, NULL); } +// Deferred halves of the old sysReset module set (see the comment there). Both are idempotent +// one-shots that latch ONLY on full success (a transient load failure stays retryable -- the exact +// latch-on-failure class the 2026-07 atad/mmce fixes eradicated; CodeRabbit review of #252), and +// sysReset() clears the latches so an in-process IOP reboot reloads everything. Audio pair = +// deferredAudioInit's first act; launch pair = deferredInit (IO worker, GUI path) or the autolaunch +// fork (synchronous -- it skips deferredInit). +void sysLoadAudioModules(void) +{ + if (sysAudioModsLoaded) + return; + LOG("[LIBSD]:\n"); + int r0 = sysLoadModuleBuffer(&libsd_irx, size_libsd_irx, 0, NULL); + LOG("[AUDSRV]:\n"); + int r1 = sysLoadModuleBuffer(&audsrv_irx, size_audsrv_irx, 0, NULL); + if (r0 >= 0 && r1 >= 0) + sysAudioModsLoaded = 1; +} + +void sysLoadLaunchModules(void) +{ + if (sysLaunchModsLoaded) + return; + LOG("[ISOFS]:\n"); + int r0 = sysLoadModuleBuffer(&isofs_irx, size_isofs_irx, 0, NULL); + LOG("[GENVMC]:\n"); + int r1 = sysLoadModuleBuffer(&genvmc_irx, size_genvmc_irx, 0, NULL); + if (r0 >= 0 && r1 >= 0) + sysLaunchModsLoaded = 1; +} + static void poweroffHandler(void *arg) { sysPowerOff();