Skip to content

fix: handle PlayStation ISO edge cases#13

Merged
wizzomafizzo merged 5 commits into
mainfrom
fix/playstation-iso-edge-cases
Jul 7, 2026
Merged

fix: handle PlayStation ISO edge cases#13
wizzomafizzo merged 5 commits into
mainfrom
fix/playstation-iso-edge-cases

Conversation

@wizzomafizzo

@wizzomafizzo wizzomafizzo commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

  • scan ISO headers in block-sized chunks and add malformed path table bounds checks
  • add streaming ISO file walking and use it for PSP/PlayStation root file scans
  • extract PlayStation serials from SYSTEM.CNF boot paths

Tests

  • go test ./identifier ./iso9660
  • go test -race -timeout=10m ./...
  • make lint

Summary by CodeRabbit

  • New Features

    • Improved PlayStation and PSP disc image detection with faster ISO file scanning and early exit when required files are found.
    • Added enhanced PlayStation serial extraction using SYSTEM.CNF when available, with improved fallback behavior.
    • Introduced support for callback-based ISO file walking to streamline lookups.
  • Bug Fixes

    • More reliable ISO9660 parsing, including robust primary descriptor discovery across chunk boundaries.
    • Added stricter validation for path tables and directory records to prevent incorrect reads on malformed/tricky images.
  • Tests

    • Expanded coverage with new ISO building utilities and additional negative/edge-case scenarios.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b948abfc-0141-4be1-8781-10c83bf61a6f

📥 Commits

Reviewing files that changed from the base of the PR and between d58f9bb and cf2ab38.

📒 Files selected for processing (2)
  • internal/testiso/testiso.go
  • internal/testiso/testiso_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/testiso/testiso.go

📝 Walkthrough

Walkthrough

This PR adds chunked ISO9660 PVD scanning, stricter path-table validation, callback-based file walking, and path-based reads. PSP and PlayStation identifiers now use WalkFiles and SYSTEM.CNF parsing. A new internal testiso package builds minimal ISO images for tests.

Changes

ISO9660 Core Traversal and Validation

Layer / File(s) Summary
Chunked PVD offset discovery
iso9660/iso9660.go, iso9660/iso9660_test.go
ISO9660 init now scans for the PVD with bounded chunk reads and overlap handling, with tests for constrained reads and boundary-crossing header scans.
Path table bounds and parent validation
iso9660/iso9660.go, iso9660/iso9660_test.go
Path-table parsing now validates bounds and parent indices, and negative tests cover malformed images.
WalkFiles-based file iteration
iso9660/iso9660.go, iso9660/iso9660_test.go
IterFiles and ReadFileByPath now run through WalkFiles, and tests cover early stop plus directory-record read failures.

Identifier Serial and Root-File Discovery

Layer / File(s) Summary
PSP identifier WalkFiles adoption
identifier/psp.go, identifier/psp_test.go
identifyPSPFromISO stops at UMD_DATA.BIN through WalkFiles, wraps walk errors, and is covered by an ISO-backed identification test.
PlayStation root info and SYSTEM.CNF serial extraction
identifier/psx.go, identifier/psx_test.go
PlayStation identification now uses shared root-info helpers, optional walk/read interfaces, filename normalization, and SYSTEM.CNF parsing, with tests for walker and iterator paths.
Test lint suppression cleanup
identifier/ps2_test.go
The duplicated-test lint suppression comment is removed.

Test ISO Fixture Builder

Layer / File(s) Summary
testiso package
internal/testiso/testiso.go, internal/testiso/testiso_test.go
A new internal package builds minimal ISO9660 images and adds tests for image creation, record writing, and record-length helpers.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant identifyPlayStation
  participant playStationRootInfo
  participant playStationRootInfoWalk
  participant serialFromSystemCNFFile
  identifyPlayStation->>playStationRootInfo: resolve rootFiles and serial
  playStationRootInfo->>playStationRootInfoWalk: use WalkFiles when available
  playStationRootInfoWalk->>serialFromSystemCNFFile: read SYSTEM.CNF
  serialFromSystemCNFFile-->>playStationRootInfoWalk: serial or empty
  playStationRootInfoWalk-->>playStationRootInfo: rootFiles, serial
  playStationRootInfo-->>identifyPlayStation: metadata
Loading
sequenceDiagram
  participant ISO9660.init
  participant findPVDOffset
  participant readHeaderChunk
  participant parsePathTable
  ISO9660.init->>findPVDOffset: locate PVD offset
  findPVDOffset->>readHeaderChunk: scan header chunks
  readHeaderChunk-->>findPVDOffset: chunk data or empty
  findPVDOffset-->>ISO9660.init: offset or error
  ISO9660.init->>parsePathTable: parse path table
  parsePathTable->>parsePathTable: validate offset, size, parents
Loading

Possibly related PRs

  • ZaparooProject/go-gameid#12: Shares the PlayStation disc-identification path and serial extraction logic updated in identifier/psx.go.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing PlayStation ISO edge cases.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/playstation-iso-edge-cases

Comment @coderabbitai help to get the list of available commands.

@wizzomafizzo wizzomafizzo changed the title Fix PlayStation ISO edge cases fix: handle PlayStation ISO edge cases Jul 7, 2026
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.78431% with 49 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
iso9660/iso9660.go 74.50% 15 Missing and 11 partials ⚠️
identifier/psx.go 82.14% 5 Missing and 5 partials ⚠️
internal/testiso/testiso.go 88.76% 5 Missing and 5 partials ⚠️
identifier/psp.go 62.50% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
iso9660/iso9660_test.go (1)

174-187: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Force the fallback scan path in the chunked scan test.

The current ISO places the PVD at the standard sector, so standardPVDOffset() succeeds with a tiny read and the chunked fallback scan is not exercised. Offset the image so this test fails if the fallback loop regresses.

Proposed adjustment
-	data := createMinimalISO("TEST", "PLAYSTATION", "")
+	data := append(make([]byte, 512), createMinimalISO("TEST", "PLAYSTATION", "")...)
 	iso, err := OpenReader(maxReadReaderAt{data: data, maxRead: 2048}, int64(len(data)))
identifier/psx_test.go (1)

90-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the WalkFiles-based dispatch path.

TestSerialFromSystemCNF only exercises the pure serialFromSystemCNF string parser. Neither playStationRootInfo's dispatch nor playStationRootInfoWalk (SYSTEM.CNF discovery via WalkFiles, rootFiles completeness) are exercised end-to-end. A mock implementing rootFileWalker/isoFileReader (alongside the existing mockPlayStationISO) would catch regressions like early-exit truncation of rootFiles or SYSTEM.CNF-vs-IterFiles fallback inconsistencies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@identifier/psx_test.go` around lines 90 - 112, Add an end-to-end test that
exercises the WalkFiles dispatch path rather than only serialFromSystemCNF,
using a mock that implements rootFileWalker and isoFileReader alongside
mockPlayStationISO. Verify playStationRootInfo and playStationRootInfoWalk
discover SYSTEM.CNF through WalkFiles, correctly populate rootFiles, and still
fall back consistently to IterFiles when needed, so regressions like early-exit
truncation or lookup path mismatches are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@identifier/psx.go`:
- Around line 211-222: The closure parameter in serialFromSystemCNF’s
strings.FieldsFunc callback uses the single-character name r, which violates the
naming guideline. Rename that rune parameter to a longer descriptive name (for
example, runeValue) and update the unicode.IsLetter/IsDigit checks accordingly,
keeping the logic in serialFromSystemCNF unchanged.
- Around line 164-189: The early return in playStationRootInfoWalk is truncating
rootFiles because WalkFiles stops as soon as serial is found; update the
callback to always continue walking so every root entry is appended before
returning. Keep the serial detection logic in playStationRootInfoWalk (including
SYSTEM.CNF and serialFromRootFile), but remove the dependency on serial == ""
for stopping the walk so identifyPlayStation gets a complete, order-independent
root_files list.
- Around line 137-162: The fallback path in playStationRootInfo only checks
filenames via serialFromRootFile, so it misses SYSTEM.CNF-based serial
extraction when the ISO does not implement rootFileWalker. Update the IterFiles
branch to also try reading and parsing SYSTEM.CNF through the iso reader path
(as playStationRootInfoWalk does via serialFromSystemCNFFile), then keep the
existing root-file fallback and serial discovery flow. While touching this loop,
rename the single-letter file variable f to a descriptive name that follows the
project’s naming guideline.

In `@iso9660/iso9660_test.go`:
- Around line 160-171: The maxReadReaderAt.ReadAt method uses the one-letter
receiver r, which violates the variable naming guideline. Rename the receiver in
maxReadReaderAt.ReadAt to a two-character-or-longer name that matches the struct
context, and update all references within the method accordingly.

In `@iso9660/iso9660.go`:
- Around line 450-465: In WalkFiles, the directory traversal currently swallows
ReadAt failures by breaking out of the loop, which can hide truncated/corrupt
ISO errors and make callers like ReadFileByPath see ErrFileNotFound instead.
Update the ReadAt handling in the directory record parsing path to return the
underlying error instead of breaking, including the lenBuf read and the recBuf
read, so invalid directory data surfaces back to callers. Use the WalkFiles loop
and the iso.reader.ReadAt calls as the fix points.

---

Nitpick comments:
In `@identifier/psx_test.go`:
- Around line 90-112: Add an end-to-end test that exercises the WalkFiles
dispatch path rather than only serialFromSystemCNF, using a mock that implements
rootFileWalker and isoFileReader alongside mockPlayStationISO. Verify
playStationRootInfo and playStationRootInfoWalk discover SYSTEM.CNF through
WalkFiles, correctly populate rootFiles, and still fall back consistently to
IterFiles when needed, so regressions like early-exit truncation or lookup path
mismatches are caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 90267b75-1151-47d1-9603-903ca85038bf

📥 Commits

Reviewing files that changed from the base of the PR and between 5e81fb0 and 5d2ccc6.

📒 Files selected for processing (5)
  • identifier/psp.go
  • identifier/psx.go
  • identifier/psx_test.go
  • iso9660/iso9660.go
  • iso9660/iso9660_test.go

Comment thread identifier/psx.go
Comment thread identifier/psx.go
Comment thread identifier/psx.go
Comment thread iso9660/iso9660_test.go Outdated
Comment thread iso9660/iso9660.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
iso9660/iso9660_test.go (2)

172-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

No bounds check on file data size vs block size.

writeISOTestFileRecord copies file.data directly into a single blockSize-sized slot (data[fileLBA*blockSize:]) with no check that len(file.data) <= blockSize. If a future test uses a file payload larger than 2048 bytes, this will silently overwrite the next file's block without any error.
[optional_refactor]

🛡️ Proposed defensive check
 func createMinimalISOWithFiles(volumeID string, files []isoTestFile) []byte {
 	const blockSize = 2048
 	base := createMinimalISO(volumeID, "SYS", "PUB")
 	totalBlocks := 20 + len(files)
 	data := make([]byte, totalBlocks*blockSize)
 	copy(data, base)

 	pvdOffset := 16 * blockSize
 	binary.LittleEndian.PutUint32(data[pvdOffset+80:], mustUint32(totalBlocks))
 	binary.BigEndian.PutUint32(data[pvdOffset+84:], mustUint32(totalBlocks))

 	recordOffset := 19*blockSize + 68
 	for idx, file := range files {
+		if len(file.data) > blockSize {
+			panic("test ISO file data exceeds block size")
+		}
 		fileLBA := 20 + idx
 		writeISOTestFileRecord(data[recordOffset:], fileLBA, len(file.data), file.name)
 		copy(data[fileLBA*blockSize:], file.data)
 		recordOffset += isoTestDirectoryRecordLength(file.name)
 	}

 	return data
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iso9660/iso9660_test.go` around lines 172 - 177, The file data write in the
loop over files can overflow a single block because
`copy(data[fileLBA*blockSize:], file.data)` assumes `len(file.data)` always fits
within `blockSize`. Add a defensive size check before writing each entry in this
test helper, and fail the test explicitly if `file.data` exceeds one block so
`writeISOTestFileRecord` and the `files` setup cannot silently corrupt the next
block.

155-216: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate test-helper logic across packages.

mustUint32, mustByte, isoTestDirectoryRecordLength, and the record-writing helpers here are near-identical duplicates of mustUint32, mustByte, directoryRecordLength, and writeIdentifierRecord in identifier/iso_test_helpers_test.go. Since these are test-only helpers in two separate packages, sharing them cleanly would require introducing an internal test-support package, but the current duplication increases the maintenance surface for any future ISO9660 layout tweaks.
[recommended_refactor]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iso9660/iso9660_test.go` around lines 155 - 216, The ISO test helpers here
duplicate the same record-length, byte/uint32 bounds, and directory-record
writing logic already present in the identifier test helpers. Refactor the
shared behavior used by createMinimalISOWithFiles, writeISOTestFileRecord,
isoTestDirectoryRecordLength, mustUint32, and mustByte into a single
test-support helper location that both packages can use, and update these tests
to call the shared helpers instead of maintaining near-identical copies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@iso9660/iso9660_test.go`:
- Around line 172-177: The file data write in the loop over files can overflow a
single block because `copy(data[fileLBA*blockSize:], file.data)` assumes
`len(file.data)` always fits within `blockSize`. Add a defensive size check
before writing each entry in this test helper, and fail the test explicitly if
`file.data` exceeds one block so `writeISOTestFileRecord` and the `files` setup
cannot silently corrupt the next block.
- Around line 155-216: The ISO test helpers here duplicate the same
record-length, byte/uint32 bounds, and directory-record writing logic already
present in the identifier test helpers. Refactor the shared behavior used by
createMinimalISOWithFiles, writeISOTestFileRecord, isoTestDirectoryRecordLength,
mustUint32, and mustByte into a single test-support helper location that both
packages can use, and update these tests to call the shared helpers instead of
maintaining near-identical copies.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 19ba0fb5-ac50-4266-9a5c-2572136ccbf7

📥 Commits

Reviewing files that changed from the base of the PR and between 5d2ccc6 and 1410846.

📒 Files selected for processing (6)
  • identifier/iso_test_helpers_test.go
  • identifier/ps2_test.go
  • identifier/psp.go
  • identifier/psp_test.go
  • identifier/psx_test.go
  • iso9660/iso9660_test.go
💤 Files with no reviewable changes (1)
  • identifier/ps2_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • identifier/psp.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
identifier/psx_test.go (1)

216-232: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Prefer element-wise slice comparison over joined-string comparison.

Comparing rootFiles via strings.Join(..., " / ") can produce false equality if any element itself contains " / " (e.g., after future filename normalization changes). Using reflect.DeepEqual or slices.Equal avoids this edge case and gives clearer diff output on failure.

♻️ Proposed fix
-	wantRootFiles := []string{"SYSTEM.CNF", "README.TXT"}
-	if strings.Join(rootFiles, " / ") != strings.Join(wantRootFiles, " / ") {
-		t.Errorf("rootFiles = %v, want %v", rootFiles, wantRootFiles)
-	}
+	wantRootFiles := []string{"SYSTEM.CNF", "README.TXT"}
+	if !slices.Equal(rootFiles, wantRootFiles) {
+		t.Errorf("rootFiles = %v, want %v", rootFiles, wantRootFiles)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@identifier/psx_test.go` around lines 216 - 232, The rootFiles assertion in
assertPlayStationRootInfo compares slices by joining them into a string, which
can hide element-level mismatches. Update this check to use an element-wise
slice comparison such as slices.Equal or reflect.DeepEqual while keeping the
existing wantRootFiles expectation, so failures in assertPlayStationRootInfo
show the actual differing elements clearly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@identifier/psx_test.go`:
- Around line 216-232: The rootFiles assertion in assertPlayStationRootInfo
compares slices by joining them into a string, which can hide element-level
mismatches. Update this check to use an element-wise slice comparison such as
slices.Equal or reflect.DeepEqual while keeping the existing wantRootFiles
expectation, so failures in assertPlayStationRootInfo show the actual differing
elements clearly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd9043a8-b37b-4359-83e1-f0c20dfac4ff

📥 Commits

Reviewing files that changed from the base of the PR and between 1410846 and 6e691fd.

📒 Files selected for processing (4)
  • identifier/psx.go
  • identifier/psx_test.go
  • iso9660/iso9660.go
  • iso9660/iso9660_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • iso9660/iso9660_test.go
  • identifier/psx.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/testiso/testiso.go`:
- Around line 92-101: Guard the root directory space in testISOSetup before
calling WriteFileRecord and copy: the loop that advances recordOffset in
internal/testiso/testiso.go can overflow the single-block root directory region.
Add a bounds check using rootOffset, BlockSize, and
DirectoryRecordLength(file.Name) so setup fails early if the file records would
spill past the advertised root directory, keeping the fixture from corrupting
payload data.
- Around line 119-131: The writeRecord helper currently writes fixed offsets and
copies the name into record without verifying the buffer is large enough, so
callers of WriteDirectoryRecord and WriteFileRecord can panic. Add a
length/capacity check at the start of writeRecord (using
DirectoryRecordLength(name) or the required minimum size) and fail through
tb.Fatal/tb.Fatalf with a clear message before any indexing or copy occurs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cf4066a-97a5-4f4a-a0d4-46592f632d83

📥 Commits

Reviewing files that changed from the base of the PR and between 6e691fd and d58f9bb.

📒 Files selected for processing (4)
  • identifier/psp_test.go
  • identifier/psx_test.go
  • internal/testiso/testiso.go
  • iso9660/iso9660_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • identifier/psp_test.go
  • identifier/psx_test.go
  • iso9660/iso9660_test.go

Comment thread internal/testiso/testiso.go
Comment thread internal/testiso/testiso.go
@wizzomafizzo
wizzomafizzo merged commit c8ff526 into main Jul 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant