Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
2476d04
Preserve platform when adding discovered games
Pfuenzle Jul 18, 2026
0f74bf3
Add No-Intro ROM verification support
Pfuenzle Jul 18, 2026
ce0a324
Merge remote-tracking branch 'origin/main' into nointro-rom-verification
Pfuenzle Jul 19, 2026
9a847aa
Merge latest main for No-Intro branch
Pfuenzle Jul 19, 2026
ece4a9b
Add Nintendo handheld platform families
Pfuenzle Jul 19, 2026
fc41fd6
Match Nintendo handheld platform specs
Pfuenzle Jul 19, 2026
ec31a0b
Accept handheld ROM media files
Pfuenzle Jul 19, 2026
039ebeb
Parse No-Intro language tags
Pfuenzle Jul 19, 2026
bf5f517
Seed default No-Intro catalog sources
Pfuenzle Jul 19, 2026
b6b4e92
Add numbered No-Intro catalog fields
Pfuenzle Jul 19, 2026
794a6d9
Parse numbered No-Intro snapshots
Pfuenzle Jul 19, 2026
039a6eb
Enrich No-Intro catalogs with release numbers
Pfuenzle Jul 19, 2026
ec18bc6
Rename files with No-Intro numbered names
Pfuenzle Jul 19, 2026
a335533
Add No-Intro catalog status API
Pfuenzle Jul 19, 2026
de27534
Match game components by ROM hash
Pfuenzle Jul 19, 2026
f58f6e3
Show No-Intro matches in components UI
Pfuenzle Jul 19, 2026
474c8ea
Show No-Intro status in metadata settings
Pfuenzle Jul 19, 2026
b69aeb6
Surface Nintendo platform choices in UI
Pfuenzle Jul 19, 2026
431bac8
Seed additional No-Intro catalog sources
Pfuenzle Jul 19, 2026
5a69d03
Model No-Intro variants in catalog plans
Pfuenzle Jul 19, 2026
5db9469
Create No-Intro component slots for games
Pfuenzle Jul 19, 2026
3ab8857
Match No-Intro catalog entries per component
Pfuenzle Jul 19, 2026
a1d857f
Expose file component ownership in the API
Pfuenzle Jul 19, 2026
f9e1d84
Hide duplicate base rows for No-Intro variants
Pfuenzle Jul 19, 2026
535757a
Show component ownership in game files
Pfuenzle Jul 19, 2026
adc48d1
Match No-Intro variants by alternate titles
Pfuenzle Jul 19, 2026
94a74db
Add No-Intro 3DS variant catalog support
Pfuenzle Jul 19, 2026
c1cbdfd
Add specific Nintendo and Sony platform families
Pfuenzle Jul 19, 2026
7c7f644
Expand No-Intro catalog source defaults
Pfuenzle Jul 19, 2026
cbc2c8d
Restrict No-Intro game component platform matching
Pfuenzle Jul 19, 2026
339ca47
Recognize Sony No-Intro ROM paths
Pfuenzle Jul 19, 2026
227823c
Display No-Intro variant labels robustly
Pfuenzle Jul 19, 2026
ddbbdba
Surface added platform choices in forms
Pfuenzle Jul 19, 2026
dc52f87
Merge branch 'pr153' into nointro-green
claude Jul 25, 2026
883fb17
Fix stylelint property order in GameFileEditorRow.css
claude Jul 25, 2026
c515cd4
Don't seed the broken DAT-o-MATIC catalog sources
claude Jul 26, 2026
42e0ada
Scale No-Intro component reconciliation
Pfuenzle Jul 27, 2026
43256b6
Tolerate unreadable No-Intro files during verification
Pfuenzle Jul 27, 2026
9fe634e
Harden DAT-o-MATIC numbered catalog fetches
Pfuenzle Jul 27, 2026
c2c961d
Preserve No-Intro variant filenames under Gamarr profile
Pfuenzle Jul 27, 2026
36d7972
Show richer metadata in component rows
Pfuenzle Jul 27, 2026
f3608ce
Prefer original No-Intro filenames for variant naming
Pfuenzle Jul 27, 2026
67d909b
Fix No-Intro unit test regressions
Pfuenzle Jul 27, 2026
b779d8a
Merge main into nointro-rom-verification
claude Jul 27, 2026
9dbf405
Skip needless ADVANsCEne download; log the DAT-o-MATIC fallback
claude Jul 27, 2026
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
103 changes: 103 additions & 0 deletions docs/rom-verification-adr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# ADR, No-Intro ROM verification architecture contract

## Status

Approved for Todo 1 planning and implementation follow-up.

## Context

Gamarr needs a generic No-Intro verification design for ROM libraries that works across No-Intro-backed systems without bending current game file behavior into ROM-specific rules. The architecture scope is generic No-Intro systems, with Game Boy Advance and Nintendo DS used as the first validation examples because they already show the ZIP-heavy and mixed ZIP plus raw shapes that v1 must handle. Switch is deferred because its TitleID-driven metadata model is a separate problem.

## Decision summary

Gamarr will add a separate ROM inventory and verification aggregate for No-Intro-backed systems. It will not implicitly reuse `GameFileId`, `HasFile`, or existing wanted semantics. This ADR acts as the migration guard for those legacy concepts. ZIP verification is read-only and does not imply extraction or import overhaul. Catalog truth comes from automatic pinned upstream DAT or catalog sync with visible source, version, and last-sync metadata, plus a manual refresh action.

## Data model boundary

The No-Intro feature owns a separate ROM inventory and verification aggregate.

That aggregate is responsible for:

- catalog source identity, pinned upstream release metadata, sync timestamps, and sync failure state
- canonical catalog entries and their hash truth
- managed library membership for verification roots and system scope
- per-library-file verification results for raw files and ZIP-contained ROM payloads
- orthogonal duplicate and missing-set reporting
- expected filename resolution for the selected rename profile

The existing game and game-file model remains separate.

- Do not implicitly reuse `GameFileId` as the identity of a verified ROM record.
- Do not implicitly reuse `HasFile` as proof that a ROM is verified or present in a managed verification set.
- Do not reuse existing wanted semantics. `Missing` for ROM verification is a separate verification-set concept and not a replacement for existing wanted or monitored flows.

If later implementation needs any compatibility bridge, it must be an explicit migration path, not hidden semantic reuse.

## Verification status table

| Status | Scope | Meaning | Notes |
| --- | --- | --- | --- |
| Verified | per file | The ROM payload hash matches a loaded No-Intro catalog entry and the filename matches the selected rename profile | Name checks are profile-relative |
| Name mismatch | per file | The ROM payload hash matches a loaded No-Intro catalog entry, but the filename or path shape differs from the selected rename profile | `Name mismatch` is relative to the selected rename profile |
| Unknown | per file | The ROM payload hash does not match any loaded No-Intro catalog entry | Not a naming issue |
| Bad dump | per file | The ROM payload hash matches a known bad, overdump, headered, or otherwise non-good catalog entry, or a deterministic bad-dump rule | Takes precedence over Verified in summary views |
| Duplicate | orthogonal flag | More than one managed library file resolves to the same canonical No-Intro entry | Does not replace the per-file verification state |
| Missing | verification set | A catalog entry expected by the managed verification set is absent from the managed library scope | `Missing` is scoped to the managed verification set |

Summary precedence for the primary per-file state is: Bad dump, Unknown, Name mismatch, Verified. Duplicate and Missing remain orthogonal signals.

## ZIP/raw matching rules

The authoritative verification unit is the normalized catalog ROM entry derived from raw ROM bytes.

- Loose files are hashed from the raw ROM payload.
- ZIP verification is read-only and does not imply extraction, import, or archive-management overhaul.
- ZIP-contained matching hashes the selected ROM payload inside the archive against the same catalog truth used for loose files.
- Raw and ZIP matches converge on the same canonical ROM entry when the payload bytes are the same.
- V1 must classify ambiguous or unsupported multi-member archives deterministically instead of guessing.
- A successful ZIP match does not grant any separate import semantics, extracted-file lifecycle, or ownership change.

## V1 enablement matrix

| Area | V1 decision |
| --- | --- |
| Architecture scope | Generic No-Intro systems architecture scope |
| Initial validation examples | GBA and Nintendo DS |
| Catalog source | Automatic pinned upstream DAT or catalog sync |
| Catalog visibility | Show source, version, last-sync, last-attempt, and refresh failure state |
| Refresh behavior | Keep prior good catalog data visible on failed refresh, allow manual refresh |
| Verification inputs | Raw ROM files and ZIP-contained ROM payloads |
| Naming behavior | Keep current Gamarr naming as default, add No-Intro-aware profile evaluation |
| Missing scope | Managed verification set only |
| ZIP behavior | Read-only verification only |
| Switch | Deferred |

## Automatic upstream catalog policy

The approved owner decision is automatic pinned upstream DAT or catalog sync, not an unresolved default.

- The system stores the upstream source identity, pinned version or revision, last successful sync, last attempted sync, and failure state.
- The UI and API must expose visible source, version, and last-sync details.
- Manual refresh is available.
- Failed refresh attempts must not silently replace or clear the prior good catalog snapshot.

## Validation examples

Game Boy Advance and Nintendo DS are the first validation examples for v1 because they prove the generic design against real library shapes.

- GBA validates ZIP-heavy libraries, numbered by-id naming, and non-retail subsets.
- Nintendo DS validates mixed raw `.nds` and ZIP libraries plus additional subset folders.
- These examples validate the architecture. They do not narrow the architecture to only those two systems.

## Explicit non-goals

- No implicit reuse of `GameFileId`, `HasFile`, or existing wanted semantics.
- No archive extraction workflow change.
- No ROM import overhaul.
- No forced bulk rename of existing ROM libraries.
- No assumption that current folder names are authoritative catalog truth.
- No Switch implementation in v1. Switch is deferred.

## Consequences

This boundary keeps today’s game and file behavior stable while making room for ROM-specific verification truth, duplicate detection, missing-set reporting, and profile-relative naming checks. It also keeps ZIP support narrow and safe, because verification stays read-only in v1.
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ describe('AddNewGameModalContent', () => {
expect(
screen.getByTestId('form-input-qualityProfileId')
).toBeInTheDocument();
expect(screen.getByTestId('form-input-platform')).toBeInTheDocument();
expect(screen.getByTestId('form-input-tags')).toBeInTheDocument();
});

Expand Down
18 changes: 18 additions & 0 deletions frontend/src/AddGame/AddNewGame/AddNewGameModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,26 @@ const platformOptions = [
{ key: 'linux', value: 'Linux' },
{ key: 'mac', value: 'macOS' },
{ key: 'playStation', value: 'PlayStation' },
{ key: 'sonyPS3', value: 'Sony PlayStation 3' },
{ key: 'sonyPSP', value: 'Sony PSP' },
{ key: 'sonyPSVita', value: 'Sony PlayStation Vita' },
{ key: 'xbox', value: 'Xbox' },
{ key: 'nintendo', value: 'Nintendo' },
{ key: 'nintendoSwitch', value: 'Nintendo Switch' },
{ key: 'nintendoWiiU', value: 'Nintendo Wii U' },
{ key: 'nintendoWii', value: 'Nintendo Wii' },
{ key: 'nintendo3DS', value: 'Nintendo 3DS' },
{ key: 'nintendoDSi', value: 'Nintendo DSi' },
{ key: 'nintendoDS', value: 'Nintendo DS' },
{ key: 'nintendoGBA', value: 'Nintendo Game Boy Advance' },
{ key: 'nintendoGBC', value: 'Nintendo Game Boy Color' },
{ key: 'nintendoGB', value: 'Nintendo Game Boy' },
{ key: 'nintendoNES', value: 'Nintendo Entertainment System' },
{ key: 'nintendoSNES', value: 'Super Nintendo Entertainment System' },
{ key: 'nintendoN64', value: 'Nintendo 64' },
{ key: 'nintendoFDS', value: 'Family Computer Disk System' },
{ key: 'nintendoVirtualBoy', value: 'Virtual Boy' },
{ key: 'nintendoPokemonMini', value: 'Pokemon Mini' },
];

function AddNewGameModalContent(props: AddNewGameModalContentProps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface DiscoverGameDefaults {
qualityProfileId: number;
minimumAvailability: string;
searchForGame: boolean;
platform: string;
tags: number[];
}

Expand Down Expand Up @@ -87,6 +88,7 @@ function AddNewDiscoverGameModalContentConnector({
qualityProfileId,
minimumAvailability,
searchForGame,
platform,
tags,
} = settings;

Expand All @@ -107,6 +109,7 @@ function AddNewDiscoverGameModalContentConnector({
qualityProfileId: qualityProfileId.value,
minimumAvailability: minimumAvailability.value,
searchForGame: searchForGame.value,
platform: platform.value,
tags: tags.value,
})
);
Expand All @@ -121,6 +124,7 @@ function AddNewDiscoverGameModalContentConnector({
qualityProfileId,
minimumAvailability,
searchForGame,
platform,
tags,
onModalClose,
]);
Expand All @@ -140,6 +144,7 @@ function AddNewDiscoverGameModalContentConnector({
qualityProfileId={qualityProfileId}
minimumAvailability={minimumAvailability}
searchForGame={searchForGame}
platform={platform}
tags={tags}
folder={folder}
onModalClose={onModalClose}
Expand Down
Loading
Loading