Skip to content

fix: handle unformatted memory cards on save restore#2384

Open
Moyasee wants to merge 7 commits into
mainfrom
fix/LBX-807
Open

fix: handle unformatted memory cards on save restore#2384
Moyasee wants to merge 7 commits into
mainfrom
fix/LBX-807

Conversation

@Moyasee

@Moyasee Moyasee commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

When submitting this pull request, I confirm the following (please check the boxes):

  • I have read the Hydra documentation.
  • I have checked that there are no duplicate pull requests related to this request.
  • I have considered, and confirm that this submission is valuable to others.
  • I accept that this submission may not be used and the pull request may be closed at the discretion of the maintainers.

Fill in the PR content:

Closes LBX-807.

Restoring a cloud save into an unformatted PS1/PS2 memory card used to fail with a confusing generic "not a writable card" error. This handles it gracefully.

What changed:

  • Added inspectPs1Card / inspectPs2Card helpers that classify a card file as formatted, unformatted, or unreadable (PS2 checks the superblock magic, PS1 checks the "MC" directory header).
  • The restore modals (desktop + big picture) probe the selected card and, when it is unformatted, show a clear message guiding the user to format it in their emulator's memory card manager and disable the Restore button so the failure never happens.
  • The PS1/PS2 import functions now short-circuit on an unformatted card before touching it (no leftover backup file) and return a typed reason the UI can map to the message.
  • Every restore failure path is now logged to error.txt with context (platform, save id, card path, reason), not just thrown exceptions.

New IPC inspectMemcard exposes the card inspection to the renderer. New translation key cloud_restore_unformatted added for en, pt-BR, ru, es.

Restoring a cloud save into an unformatted PS1/PS2 memory card failed
with a generic "not a writable card" error. Detect the unformatted card
before writing, block the restore with guidance to format it first, and
log every restore failure path to error.txt.
@Moyasee Moyasee added the bug Something isn't working label Jun 21, 2026
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds graceful handling for unformatted PS1/PS2 memory cards during cloud save restore. When a user selects an unformatted card, the UI now shows a clear warning, disables the Restore button, and prevents the main process from creating a leftover backup file.

  • New inspectPs1Card / inspectPs2Card helpers classify a card as formatted, unformatted, or unreadable; both writer functions now short-circuit before touching the backup when the card is unformatted.
  • A new inspectMemcard IPC handler exposes card inspection to the renderer; both the standard and big-picture restore modals probe the selected card and show an inline warning with a disabled Restore button when unformatted.
  • A new cloud_restore_unformatted translation key is added for en, pt-BR, ru, and es; previously hardcoded English toast strings in the big-picture callers are replaced with t() calls.

Confidence Score: 5/5

Safe to merge — all three issues flagged in the prior review round have been addressed, and no new defects were found in this revision.

The format-inspection logic is self-contained (open → partial read → close, with a catch that returns unreadable), backup creation is correctly skipped for unformatted cards, the in-flight disable guard (!selectedFormat) closes the race window, and toast strings now go through t() in every caller.

No files require special attention.

Important Files Changed

Filename Overview
src/main/services/emulators/ps1-memory-card/ps1-memory-card.ts Adds inspectPs1Card that opens the file, probes findDataOffset, and returns a typed format state; probe-length calculation correctly covers all wrapper-header offsets.
src/main/services/emulators/ps2-memory-card/ps2-memory-card.ts Adds inspectPs2Card that delegates to readSuperblock and maps the result to the three-state format enum; error path correctly catches all exceptions.
src/main/services/emulators/ps1-memory-card/ps1-memory-card-writer.ts Pre-check via inspectPs1Card now avoids creating a backup for unformatted cards; the existing findDataOffset guard is kept as defence-in-depth and now also carries reason: unformatted.
src/main/services/emulators/ps2-memory-card/ps2-memory-card-writer.ts Same pattern as PS1 writer: inspectPs2Card pre-check added before backup creation; reason field added to Ps2ImportResult.
src/main/events/emulators/inspect-memcard.ts New IPC handler that delegates to inspectPs2Card or inspectPs1Card; since EmulationSavePlatform is ps1
src/main/events/emulators/restore-emulation-save.ts Now forwards result.reason in the return value and logs failures with structured context (platform, saveId, card path, reason).
src/renderer/src/pages/settings/emulation/emulation-save-modals.tsx Adds format-state effect with cancellation guard, inline unformatted hint, and disables Restore while inspection is in-flight (!selectedFormat) or card is unformatted.
src/big-picture/src/pages/settings/emulation/emulation-cloud-restore-modal.tsx Big-picture counterpart of the renderer modal; same inspection effect, hint display, and disabled-button logic; .catch handler keeps format state consistent on IPC errors.
src/types/emulator.types.ts Adds MemcardFormatState and MemcardRestoreErrorReason type aliases; extends MemcardRestoreResult with an optional reason field.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant R as Renderer / Big-Picture UI
    participant P as Preload (contextBridge)
    participant I as inspectMemcard IPC
    participant W as importMcs/PsuIntoCard
    participant FS as File System

    R->>P: inspectMemcard(platform, cardFilePath)
    P->>I: ipcRenderer.invoke("inspectMemcard", ...)
    I->>FS: inspectPs1Card / inspectPs2Card (open + partial read)
    FS-->>I: bytes
    I-->>P: "formatted" | "unformatted" | "unreadable"
    P-->>R: MemcardFormatState

    alt "state === "unformatted""
        R->>R: show hint, disable Restore button
    else "state === null (in-flight)"
        R->>R: disable Restore button
    else "state === "formatted""
        R->>R: enable Restore button
        R->>P: restoreEmulationSave(platform, saveId, cardFilePath, bytes)
        P->>W: importMcsIntoCard / importPsuIntoCard
        W->>FS: inspectPs1Card / inspectPs2Card (pre-check)
        FS-->>W: "formatted" | "unformatted"
        alt "pre-check === "unformatted""
            W-->>P: "{ ok: false, reason: "unformatted" } (no backup created)"
        else "pre-check === "formatted""
            W->>FS: copyFile (backup)
            W->>FS: readFile + write
            W-->>P: "{ ok: true | false, reason? }"
        end
        P-->>R: MemcardRestoreResult
        alt result.ok
            R->>R: showSuccessToast
        else "result.reason === "unformatted""
            R->>R: showErrorToast(cloud_restore_unformatted)
        else other failure
            R->>R: showErrorToast(cloud_restore_failed)
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant R as Renderer / Big-Picture UI
    participant P as Preload (contextBridge)
    participant I as inspectMemcard IPC
    participant W as importMcs/PsuIntoCard
    participant FS as File System

    R->>P: inspectMemcard(platform, cardFilePath)
    P->>I: ipcRenderer.invoke("inspectMemcard", ...)
    I->>FS: inspectPs1Card / inspectPs2Card (open + partial read)
    FS-->>I: bytes
    I-->>P: "formatted" | "unformatted" | "unreadable"
    P-->>R: MemcardFormatState

    alt "state === "unformatted""
        R->>R: show hint, disable Restore button
    else "state === null (in-flight)"
        R->>R: disable Restore button
    else "state === "formatted""
        R->>R: enable Restore button
        R->>P: restoreEmulationSave(platform, saveId, cardFilePath, bytes)
        P->>W: importMcsIntoCard / importPsuIntoCard
        W->>FS: inspectPs1Card / inspectPs2Card (pre-check)
        FS-->>W: "formatted" | "unformatted"
        alt "pre-check === "unformatted""
            W-->>P: "{ ok: false, reason: "unformatted" } (no backup created)"
        else "pre-check === "formatted""
            W->>FS: copyFile (backup)
            W->>FS: readFile + write
            W-->>P: "{ ok: true | false, reason? }"
        end
        P-->>R: MemcardRestoreResult
        alt result.ok
            R->>R: showSuccessToast
        else "result.reason === "unformatted""
            R->>R: showErrorToast(cloud_restore_unformatted)
        else other failure
            R->>R: showErrorToast(cloud_restore_failed)
        end
    end
Loading

Reviews (3): Last reviewed commit: "fix: stop hint styles leaking into emula..." | Re-trigger Greptile

Comment thread src/renderer/src/pages/settings/emulation/emulation-save-modals.tsx Outdated
Comment thread src/renderer/src/pages/settings/emulation/emulation-save-modals.tsx Outdated
Moyasee added 2 commits June 21, 2026 16:34
Disable the Restore button while card inspection is in flight, treat a
rejected inspectMemcard promise as an unreadable card, and route the
big-picture restore error toasts through i18n instead of hardcoded
English strings.
Route the onRestoreSuccess toasts through cloud_restore_success so they
match the now-translated error path and the renderer modal, instead of a
hardcoded English string.
@Moyasee

Moyasee commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review again

The earlier hint rule was inserted into the middle of a shared
comma-separated selector group, which split it and applied the warning
colour to .emulator-detail and .emulation-settings__scan-modal. Restore
the original four-selector focus block and keep .emu-save-modal__hint as
its own rule.
@Moyasee

Moyasee commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

Moyasee and others added 3 commits June 22, 2026 20:54
PS2 cards now point users at PCSX2's auto-format-on-save behaviour
instead of the generic memory card manager guidance, which does not
apply to PCSX2. PS1 keeps the generic message via a separate key.
Add a cancelled flag to the getMemcardRestoreTargets effect in both
restore modals so an in-flight fetch from a previous platform/save can't
resolve late and overwrite the current targets and selection.
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant