Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions include/opl.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,7 @@ extern int gOSDLanguageEnable;
extern int gOSDLanguageSource;

extern int showCfgPopup;
extern int showNetDhcpPopup; // boot toast: UDP transport selected while IP Type = DHCP (needs static IP)
extern int showHddReconcilePopup; // boot toast: APA + exFAT(BDM) HDD were both enabled -- one was auto-disabled (#154)
extern int showNetDhcpPopup; // boot toast: UDP transport selected while IP Type = DHCP (needs static IP)

#ifdef IGS
#define IGS_VERSION "0.1"
Expand Down
12 changes: 12 additions & 0 deletions modules/hdd/xhdd/xhdd.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ static int xhddDevctl(iop_file_t *fd, const char *name, int cmd, void *arg, unsi
if (buflen % 512 != 0)
return -EINVAL;

// Re-probe before the transfer: a boot-time atad init that raced drive spin-up leaves a
// dead devinfo behind, and this devctl is the EE side's readiness gate (the
// hddDetectNonSonyFileSystem probe). sceAtaInit is a latched no-op once a probe has
// succeeded, so a healthy path pays one call; a dead one gets a real second attempt
// (with an atad whose init is retryable) instead of an unconditional -1 for the session.
devinfo = sceAtaInit(fd->unit);
if (devinfo == NULL || !devinfo->exists) {
devinfo = sceAtaInit(fd->unit); // one bounded retry
if (devinfo == NULL || !devinfo->exists)
return -ENODEV;
}

return sceAtaDmaTransfer(fd->unit, buf, 0, buflen / 512, ATA_DIR_READ);
}

Expand Down
22 changes: 5 additions & 17 deletions src/gui.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ static void guiShowNotifications(void)
int y = 10;
int yadd = 35;

if (showPartPopup || showThmPopup || showLngPopup || showCfgPopup || showNetDhcpPopup || showHddReconcilePopup) {
if (showPartPopup || showThmPopup || showLngPopup || showCfgPopup || showNetDhcpPopup) {
if (!popupTimer) {
popupTimer = clock() + 5000 * (CLOCKS_PER_SEC / 1000);
sfxPlay(SFX_MESSAGE);
Expand Down Expand Up @@ -351,18 +351,11 @@ static void guiShowNotifications(void)
y += yadd;
}

// One-time notice set at config load (#154): APA + exFAT(BDM) internal HDD were both
// enabled and one was auto-disabled. Rendered here (not toasted from _loadConfig) so _l()
// resolves AFTER the language pack loads -- Gemini review of #167.
if (showHddReconcilePopup)
guiRenderNotifications(_l(_STR_HDD_BACKEND_RECONCILED), y);

if (clock() >= popupTimer) {
guiResetNotifications();
showPartPopup = 0;
showCfgPopup = 0;
showNetDhcpPopup = 0;
showHddReconcilePopup = 0;
}
}
}
Expand Down Expand Up @@ -1231,13 +1224,8 @@ void guiShowNetConfig(void)
static int guiDeviceUpdater(int modified)
{
if (modified) {
int hddMode, bdmHdd;
diaGetInt(diaDeviceConfig, CFG_HDDMODE, &hddMode);
diaGetInt(diaDeviceConfig, CFG_ENABLEBDMHDD, &bdmHdd);
// BDM HDD (GPT/MBR) and the APA HDD mode are mutually exclusive; keep the
// two interlocked live now that both live on the same page.
diaSetEnabled(diaDeviceConfig, CFG_HDDMODE, !bdmHdd);
diaSetEnabled(diaDeviceConfig, CFG_ENABLEBDMHDD, hddMode == 0);
// APA + BDM-HDD interlock REMOVED (coexistence directive 2026-07-21): both rows stay
// enabled; per-drive arbitration lives in hddDetectNonSonyFileSystem, not the dialog.

// Network 3-row live logic. Row 1 Start (Off/Manual/Auto), Row 2 Protocol (SMB/UDPFS/UDPBD),
// Row 3 Access (Files/IMG). While Start=Off, grey Protocol + Access. Lock Access to Files for
Expand Down Expand Up @@ -1291,8 +1279,8 @@ void guiShowDeviceConfig(void)
diaSetInt(diaDeviceConfig, CFG_ENABLEILK, gEnableILK);
diaSetInt(diaDeviceConfig, CFG_ENABLEMX4SIO, gEnableMX4SIO);
diaSetInt(diaDeviceConfig, CFG_ENABLEBDMHDD, gEnableBdmHDD);
diaSetEnabled(diaDeviceConfig, CFG_ENABLEBDMHDD, !gHDDStartMode);
diaSetEnabled(diaDeviceConfig, CFG_HDDMODE, !gEnableBdmHDD);
diaSetEnabled(diaDeviceConfig, CFG_ENABLEBDMHDD, 1); // coexists with APA (directive 2026-07-21)
diaSetEnabled(diaDeviceConfig, CFG_HDDMODE, 1);
// Network: 3 orthogonal rows seeded from the authoritative gNetworkProtocol + gNetStartMode.
// Row 1 Start: Off/Manual/Auto == gNetStartMode (START_MODE_*)
// Row 2 Protocol: SMB(0)/UDPFS(1)/UDPBD(2) -- OFF and UDPFSBD both collapse to their protocol
Expand Down
40 changes: 36 additions & 4 deletions src/hddsupport.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ static unsigned char hddHDProKitDetected = 0;
static unsigned char hddModulesLoadCount = 0;
static unsigned char hddModulesLoaded = 0;
static unsigned char hddSupportModulesLoaded = 0;
// One toast per failure streak: hddUpdateGameList now RETRIES the support-module load every refresh
// while it keeps failing, and re-toasting the same error box each pass would bury the UI. Reset on
// success so a later, different failure toasts again.
static unsigned char hddSupportErrToasted = 0;

static char *hddPrefix = "pfs0:";
static hdl_games_list_t hddGames;
Expand Down Expand Up @@ -331,8 +335,12 @@ void hddLoadSupportModules(void)
// just looks like a dead drive for the whole session. Surface it. The 1 (genuine MBR/GPT/exFAT)
// branch stays silent on purpose -- that is the NORMAL coexistence case for BDM-HDD users and
// must not toast at every boot.
if (nonSony < 0)
if (nonSony < 0 && !hddSupportErrToasted) {
setErrorMessageWithCode(_STR_HDD_NOT_CONNECTED_ERROR, ERROR_HDD_NOT_DETECTED);
hddSupportErrToasted = 1;
} else if (nonSony > 0) {
hddSupportErrToasted = 0; // probe SUCCEEDED (genuine MBR/GPT) -- a future failure is new news
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return;
}

Expand All @@ -341,26 +349,36 @@ void hddLoadSupportModules(void)
int ret = sysLoadModuleBuffer(&ps2hdd_irx, size_ps2hdd_irx, sizeof(hddarg), hddarg);
if (ret < 0) {
LOG("HDD: No HardDisk Drive detected.\n");
setErrorMessageWithCode(_STR_HDD_NOT_CONNECTED_ERROR, ERROR_HDD_MODULE_HDD_FAILURE);
if (!hddSupportErrToasted) {
setErrorMessageWithCode(_STR_HDD_NOT_CONNECTED_ERROR, ERROR_HDD_MODULE_HDD_FAILURE);
hddSupportErrToasted = 1;
}
return;
}

// Check if a HDD unit is connected
if (hddCheck() < 0) {
LOG("HDD: No HardDisk Drive detected.\n");
setErrorMessageWithCode(_STR_HDD_NOT_CONNECTED_ERROR, ERROR_HDD_NOT_DETECTED);
if (!hddSupportErrToasted) {
setErrorMessageWithCode(_STR_HDD_NOT_CONNECTED_ERROR, ERROR_HDD_NOT_DETECTED);
hddSupportErrToasted = 1;
}
return;
}

LOG("[PS2FS]:\n");
ret = sysLoadModuleBuffer(&ps2fs_irx, size_ps2fs_irx, sizeof(pfsarg), pfsarg);
if (ret < 0) {
LOG("HDD: HardDisk Drive not formatted (PFS).\n");
setErrorMessageWithCode(_STR_HDD_NOT_FORMATTED_ERROR, ERROR_HDD_MODULE_PFS_FAILURE);
if (!hddSupportErrToasted) {
setErrorMessageWithCode(_STR_HDD_NOT_FORMATTED_ERROR, ERROR_HDD_MODULE_PFS_FAILURE);
hddSupportErrToasted = 1;
}
return;
}

hddSupportModulesLoaded = 1;
hddSupportErrToasted = 0; // recovered -- a future, different failure gets its own toast
LOG("HDDSUPPORT modules loaded\n");

if (gOPLPart[0] == '\0')
Expand Down Expand Up @@ -566,6 +584,20 @@ static int hddNeedsUpdate(item_list_t *itemList)

static int hddUpdateGameList(item_list_t *itemList)
{
// Self-heal (wLaunchELF-R3Z3N parity: latch only on success, retry per use): a boot-time first
// touch that raced drive spin-up used to leave the APA page EMPTY for the whole session -- the
// one-shot hddInitModules never retried, and nothing else reloads the support stack. Both calls
// are idempotent (hddLoadModules dedupes via ALREADYLOADED and its failed count is retryable
// post-#241; hddLoadSupportModules no-ops once hddSupportModulesLoaded), so tab entry / refresh
// becomes a real second attempt. Runs on the IO worker like the original init.
if (!hddSupportModulesLoaded) {
// Only chase the support stack when the base modules are actually resident -- calling it
// anyway made a failed base load toast TWICE (base failure + the doomed non-Sony probe's)
// on the first pass (Gemini + CodeRabbit review of #249, vetted).
if (hddLoadModulesReady())
hddLoadSupportModules();
}
Comment on lines +593 to +599

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The return value of hddLoadModulesReady() is ignored here. If the base modules fail to load, calling hddLoadSupportModules() unconditionally will trigger a second, redundant error toast (ERROR_HDD_NOT_DETECTED on top of ERROR_HDD_IF_NOT_DETECTED). You should guard the call to hddLoadSupportModules() with a check on hddLoadModulesReady().

Suggested change
if (!hddSupportModulesLoaded) {
hddLoadModulesReady();
hddLoadSupportModules();
}
if (!hddSupportModulesLoaded) {
if (hddLoadModulesReady()) {
hddLoadSupportModules();
}
}


Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (vcdViewActive(itemList->mode))
// Reuse the session's built list on view flips; hddBuildVcdGameList runs only when never
// built, invalidated (first-disc-only change), or freed by teardown (hddFreeVcdGameList).
Expand Down
40 changes: 12 additions & 28 deletions src/opl.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ int gFadeDelay;
int toggleSfx;
int showCfgPopup;
// Boot toast (rendered by guiShowNotifications alongside showCfgPopup):
int showNetDhcpPopup; // a UDP transport is selected but IP Type is DHCP -- ministack needs a static IP
int showHddReconcilePopup; // APA + exFAT(BDM) HDD were both enabled -- one was auto-disabled at load (#154)
int showNetDhcpPopup; // a UDP transport is selected but IP Type is DHCP -- ministack needs a static IP
#ifdef PADEMU
int gEnablePadEmu;
int gPadEmuSettings;
Expand Down Expand Up @@ -1704,26 +1703,14 @@ static void _loadConfig()
configGetInt(configOPL, CONFIG_OPL_ENABLE_ILINK, &gEnableILK);
configGetInt(configOPL, CONFIG_OPL_ENABLE_MX4SIO, &gEnableMX4SIO);
configGetInt(configOPL, CONFIG_OPL_ENABLE_BDMHDD, &gEnableBdmHDD);
// #120 audit F-12: APA (gHDDStartMode) and BDM-ATA (gEnableBdmHDD) are mutually exclusive by
// design, but the Device-Settings interlock only guards values changed THROUGH the dialog. A
// legacy, hand-edited or cross-version config can load both, and then BOTH internal-HDD
// stacks initialize against the one drive. Normalize at load: the backend matching the boot
// device wins; otherwise the APA start mode (the older, more deliberate setting) is kept.
// configSetInt the loser so the next save persists the reconciled pair.
if (gEnableBdmHDD && gHDDStartMode != START_MODE_DISABLED) {
if (gBootDirBdmType == BDM_TYPE_ATA)
gHDDStartMode = START_MODE_DISABLED;
else
gEnableBdmHDD = 0;
configSetInt(configOPL, CONFIG_OPL_HDD_MODE, gHDDStartMode);
configSetInt(configOPL, CONFIG_OPL_ENABLE_BDMHDD, gEnableBdmHDD);
// #154 forensics: this reconciliation was SILENT -- a user whose internal-exFAT (or
// APA) page vanished after a hand-edit/cross-version config had no clue why. Flag it
// for the notification popup (same pattern as showNetDhcpPopup): the render site
// resolves _l() per frame, so the message localizes correctly even though the
// language pack loads AFTER this point (applyConfig at the end of _loadConfig).
showHddReconcilePopup = 1;
}
// APA (gHDDStartMode) and BDM-ATA (gEnableBdmHDD) COEXIST -- maintainer directive
// 2026-07-21, overruling the old #120-audit F-12 exclusivity that force-disabled one here.
// There is no driver conflict to arbitrate: OPL's "ps2atad" is the SDK's ata_bd build,
// which registers the drive with BDM while exporting the atad library ps2hdd links
// against -- one shared stack serves both, the same approach wLaunchELF-R3Z3N, NHDDL and
// POPSLoader ship. Per-DRIVE arbitration stays where it belongs:
// hddDetectNonSonyFileSystem() keeps APA off an MBR/GPT/exFAT drive (corruption guard),
// and BDM only publishes massN: where a FAT/exFAT partition actually exists.
int udpbdKeyPresent = configGetInt(configOPL, CONFIG_OPL_ENABLE_UDPBD, &gEnableUDPBD);
configGetInt(configOPL, CONFIG_OPL_NET_BOOT_PROTOCOL, &gNetBootProtocol);
// Unified network-protocol selector (single SMAP NIC -> at most one transport per session).
Expand Down Expand Up @@ -1815,12 +1802,9 @@ static void _loadConfig()
if (gBootDirBdmType == BDM_TYPE_ATA && !gEnableBdmHDD) {
gEnableBdmHDD = 1;
configSetInt(configGetByType(CONFIG_OPL), CONFIG_OPL_ENABLE_BDMHDD, gEnableBdmHDD);
// #120 audit F-12: don't leave a loaded APA start mode fighting the just-enabled BDM-ATA
// backend (the drive we booted from is exFAT, so APA is definitionally wrong for it).
if (gHDDStartMode != START_MODE_DISABLED) {
gHDDStartMode = START_MODE_DISABLED;
configSetInt(configGetByType(CONFIG_OPL), CONFIG_OPL_HDD_MODE, gHDDStartMode);
}
// A loaded APA start mode is left ALONE (coexistence directive 2026-07-21): a second,
// APA-formatted drive is a real setup, and on this single exFAT boot drive the
// hddDetectNonSonyFileSystem guard makes the APA leg a harmless no-op anyway.
}
}

Expand Down