Promote the validated drive-captured 32-sector game lead-in, retain compact XGD1 closure evidence, correct executable-relative Redump DAT discovery, and remove transient probe artifacts.
-# CMake and generic build output
-/CMakeCache.txt
-/CMakeFiles/
-/Makefile
-/cmake_install.cmake
-/install_manifest.txt
-/BUILD/
+# CMake and generated build trees
+CMakeCache.txt
+CMakeFiles
+Makefile
+cmake_install.cmake
+install_manifest.txt
/build/
/build-*/
-/out/
-
-# Windows/MSVC output
-/.vs/
/build-msvc32/
-/friidump.exe
-/friidump.pdb
-/friidump.ilk
-/friidump.exp
-/friidump.lib
-/friidump_msvc32_build_results.zip
+
+# Compiler and executable outputs
*.obj
+*.o
+*.a
+*.so
+*.dll
+*.exe
+*.pdb
+*.ilk
-# Runtime logs and generated media artifacts
+# Runtime logs and generated disc outputs at repository root
/friidump.log
-*.log
-*.iso
-*.xiso
-*.raw
-*.pfi.bin
-*.dmi.bin
-*.redump.json
-*.media.json
-*.xdvd.json
+/*.iso
+/*.xiso
+/*.raw
+/*.iso.log
+/*.media.json
+/*.xdvd.json
+/*.pfi.bin
+/*.dmi.bin
+/*.ad_c0.bin
+/*_build_results.zip
-.DS_Store
-Thumbs.db
+# Local test/evidence bundles
+/*-live-test-*.zip
+/*-results.zip
3,820,880 sectors, layer break LBA 1,913,776, DVD start PSN 0x30000, and
Xbox/game start PSN 0x60600.
+FriiDump 0.5.3.11 adds a read-only evidence mode for the unresolved synthetic
+XGD1 ranges:
+
+```powershell
+friidump -d <drive> --xgd1-layout-probe xgd1-layout-probe.json
+```
+
+This mode creates no ISO. On the GDR-8050L challenge-handshake profile it
+establishes locked/video and unlocked/game states, reads selected boundary LBAs
+with READ(10) and READ(12), verifies the exact expected capacities, and writes
+an atomic JSON report containing SCSI status/sense evidence, SHA-1, nullable
+failed-read classifications, explicit command comparability, and the complete
+sector data for successful reads. The current pregame padding, postgame padding, and 32-sector
+game lead-in remain unresolved; probe success must not be represented as an
+exact Redump image.
+
On a GDR-8050L, FriiDump follows the original dumper's state order and timing:
primary handshake, media-cycle, re-handshake, RefreshVolume plus full settle
delays, metadata/XBE probe, media-cycle back to the visible DVD-video view,
FriiDump 0.5.3.9 also closes the Xbox live-summary boundary: the copied GDR-8050L path now returns title, media ID, finalized output sectors, and elapsed evidence to the final validation summary, and final STOP UNIT ownership is singular.
-FriiDump 0.5.3.10 checks each successfully completed ISO against the canonical
-Redump DAT selected for the detected or forced disc type. Verification begins
-only after CRC32, MD5, SHA-1, and SHA-256 have been finalized.
+FriiDump checks each successfully completed ISO against the canonical Redump
+DAT selected for the detected or forced disc type. Verification begins only
+after CRC32, MD5, SHA-1, and SHA-256 have been finalized.
Stable DAT paths:
For the GDR-8050L/cross-flashed GDR-8163B full-ISO path, the report identifies
`GDR-8050L reference finalized full-file hashes` as the hash source. FriiDump
also records that XGD1 acquisition success and exact Redump hash identity are
-separate claims. The current reconstruction contains documented synthetic
-ranges, so a clean physical read can legitimately produce `NO EXACT MATCH`.
-That result must not be promoted to archive identity.
+separate claims. The current reconstruction contains unresolved zero-filled pregame and
+postgame spans, so a clean physical read can legitimately produce
+`NO EXACT MATCH`. That result must not be promoted to archive identity.
Options:
-- `--redump-dat-dir <dir>` selects another directory containing the three stable
- DAT filenames above.
+- `--redump-dat-dir <dir>` explicitly selects a directory containing the
+ three stable DAT filenames above. Without it, FriiDump checks `redump_dat`
+ beside the executable and then under the current working directory.
- `--redump-report <file>` writes the atomic machine-readable evidence report.
- `--no-redump-verify` disables DAT lookup for a run.
- `--nohash` also prevents DAT verification because the required hashes are not
calculated.
+- `--xgd1-layout-probe <file>` is a separate read-only Windows diagnostic for
+ the GDR-8050L challenge-handshake profile. It cannot be combined with image
+ output, conversion, all-methods, or HLDS 0xE7 probe options.
Example:
```
The DAT files are data inputs, not compiled into the executable. Replace the
-three files in `redump_dat` with newer Redump exports while keeping the stable
-filenames, or point `--redump-dat-dir` at an alternate set.
+three files in the executable-relative `redump_dat` directory with newer Redump
+exports while keeping the stable filenames, or point `--redump-dat-dir` at an
+alternate set. The current-working-directory lookup is retained as a fallback
+for source-tree and legacy workflows.
+
+FriiDump 0.5.3.12 adds a second read-only XGD1 evidence mode for the user's
+modified GDR-8050L firmware:
+
+ --xgd1-raw-id-probe <report.json>
+
+The probe establishes the locked/video and unlocked/game views, performs
+controlled 16-sector READ(12) cache fills, then uses the accepted HIT 0xE7
+memdump command at cache base 0x80000000 to capture the selected 2064-byte raw
+sector. The report records the raw ID field and decodes its 24-bit physical
+sector number. This is intended to test logical-to-physical geometry; it does
+not read inaccessible filler sectors, does not modify the current XGD1 image
+layout, and requires the modified 0xE7 firmware profile.
+
+The 0.5.3.11 live logical probe found 17 successful READ(10)/READ(12) pairs,
+all byte-identical. Both active capacities rejected the sampled out-of-range
+LBAs with 05/21/00. Therefore ordinary logical reads do not supply the current
+synthetic pregame/postgame filler bytes.
+
+FriiDump 0.5.3.13 corrects the raw-ID probe after the first live v68 run
+showed that arbitrary request LBAs could leave cache base 0x80000000 pointing
+at an earlier or block-start window. The corrected probe aligns every request
+to a 16-sector block, issues the proven zero-transfer READ(12) cache flush,
+and dumps only the target slot's 12-byte header and four-byte EDC. It validates
+layer bits and raw/normalized PSNs against the current full-output geometry.
+Raw-cache user-data equality is no longer treated as evidence because Method 8
+reconstructs that field from READ(12) data before unscrambling. Geometry may be
+resolved by a complete match; inaccessible filler bytes remain unresolved.
+
+FriiDump 0.5.3.14 drive-captured XGD1 game lead-in
+----------------------------------------------------
+
+The 0.5.3.13 live cache-aligned raw-ID run matched all 13 selected locked/video
+and unlocked/game samples. It proved that unlocked source LBA 0..31 occupies
+full-output LBA 198144..198175 and immediately precedes the XDVDFS header at
+unlocked source LBA 32. FriiDump therefore no longer synthesizes the 32-sector
+game lead-in for the GDR-8050L redump-style path.
+
+The reference Xbox path now reads unlocked source LBA 0..31 strictly before
+copying source LBA 32..3431263. The native libfriidump path uses the same source
+mapping and retains per-sector zero-fill only as an explicitly counted fallback
+for a genuinely unreadable lead-in sector. Pregame and postgame physical
+locations are resolved, but their inaccessible content remains zero-filled and
+must not be described as exact without independent evidence.
+
+
+FriiDump 0.5.3.15 release cleanup
+-----------------------------------
+
+The 0.5.3.15 release candidate consolidates the XGD1 investigation into a
+repository-ready tree. Local build logs, runner transcripts, package rehearsals,
+duplicate stdout/stderr captures, and full logical-sector byte dumps are not
+retained as source authority. Compact evidence is stored under
+`docs/evidence/xgd1`.
+
+Automatic DAT discovery is now executable-relative first, with the historical
+current-working-directory lookup retained as a fallback. An explicit
+`--redump-dat-dir` remains authoritative.
+
+The reference and native full-output mappings remain those validated in
+0.5.3.14. No XGD1 output bytes changed in this cleanup.
+2026-07-18 - FriiDump 0.5.3.15 XGD1 live closure and release cleanup
+
+ * Closed the 0.5.3.14 hardware validation on a modified GDR-8050L revision
+ 0012. The 3,820,880-sector output completed successfully and the
+ drive-captured 32-sector game lead-in matched the independent 0.5.3.13
+ geometry evidence at both boundaries.
+ * Recorded final output hashes: CRC32 cc5f0e71, MD5
+ 1207b2090701ba1f221d486c83c863e7, SHA-1
+ 5ea9b2262ba951afd9a5d2d44cd2b62a4d9decc7, and SHA-256
+ 5995f823c6d67b9e3e3b3e1c2b54e1e036793188bec6b5be4220f559305f53b9.
+ * Confirmed NO EXACT MATCH against 2,689 Microsoft Xbox DAT entries. This
+ does not invalidate acquisition: the inaccessible pregame and postgame
+ contents remain zero-filled unresolved placeholders.
+ * Made automatic DAT discovery executable-relative first, with the historical
+ current-working-directory lookup retained as a fallback. Explicit
+ --redump-dat-dir remains authoritative.
+ * Corrected sidecar wording: hardware geometry validation is complete, while
+ filler content remains unresolved.
+ * Replaced 71 local build/probe/debug artifacts with a compact evidence
+ directory containing the final geometry samples, machine-readable closure
+ record, media-layout evidence, and completed live summary.
+ * Added a C resolver test and expanded .gitignore coverage for build outputs,
+ runtime logs, and generated disc images.
+ * No XGD1 output-layout bytes changed after the accepted 0.5.3.14 live dump.
+
+2026-07-18 - FriiDump 0.5.3.14 drive-captured XGD1 game lead-in
+
+ * Promoted the successful 0.5.3.13 live geometry result: all 13 selected
+ locked/video and unlocked/game raw-sector IDs matched the expected
+ full-output mapping.
+ * Replaced the GDR-8050L full-output synthetic game lead-in with drive reads
+ from unlocked source LBA 0..31 at output LBA 198144..198175.
+ * Kept XDVDFS/game data at unlocked source LBA 32..3431263 and output LBA
+ 198176..3629407.
+ * Updated .redump.json, .media.json, and .xdvd.json layout evidence to record
+ the drive-captured lead-in source and the 0.5.3.13 physical-geometry proof.
+ * Pregame and postgame physical locations are now documented as geometry
+ verified, while their inaccessible content remains zero-filled and
+ unresolved.
+ * No claim of Redump identity is made until a new 0.5.3.14 full-disc dump is
+ hashed and compared with the canonical DAT.
+
+2026-07-18 - FriiDump 0.5.3.13 XGD1 cache-aligned raw-ID geometry correction
+
+ * Corrected the v68 raw-ID probe to mirror the proven modified-GDR-8050L
+ Method 8 cache contract: zero-transfer READ(12) cache flush, 16-sector
+ block-aligned streaming fill, and 2064-byte slot indexing.
+ * Removed raw-cache user-data equality as a geometry criterion. Method 8
+ reconstructs sectors from cache header/EDC plus READ(12) data before
+ unscrambling, so equality is not expected.
+ * Added explicit full-output LBA mapping, layer-bit decoding, 24-bit layer-1
+ PSN normalization, expected raw/normalized PSNs, and per-sample geometry
+ verdicts.
+ * Schema 2 reports now set physical_geometry_resolved only when all 13
+ selected locked/video and unlocked/game samples match exactly.
+ * Retained the v68 live run as negative/diagnostic evidence; it is not a
+ geometry promotion. No reconstruction bytes or filler policy changed.
+
+2026-07-18 - FriiDump 0.5.3.12 XGD1 raw-sector ID geometry probe
+
+ * Live validation of 0.5.3.11 confirmed that sampled READ(10) and READ(12)
+ sectors are identical in both locked/video and unlocked/game states.
+ * The locked view ends at LBA 6991 and the unlocked view ends at LBA
+ 3431263; ordinary logical reads expose no alternate source for the
+ documented synthetic pregame or postgame ranges.
+ * Added --xgd1-raw-id-probe for modified GDR-8050L firmware with HIT 0xE7
+ memdump support. It performs controlled READ(12) cache fills, captures the
+ corresponding 2064-byte raw cache sectors at 0x80000000, and records the
+ decoded DVD physical-sector number from each raw ID field.
+ * XGD1 probe modes now measure real elapsed time and suppress the unrelated
+ HLDS GC/Wii validation summary that previously reported misleading
+ seed/dump/STOP fields for a successful diagnostic run.
+ * No reconstruction mapping changed. Raw IDs are geometry evidence only;
+ inaccessible filler bytes remain unresolved.
+
+2026-07-18 - FriiDump 0.5.3.11 read-only XGD1 logical-boundary probe
+
+- Adds `--xgd1-layout-probe <file>` for the GDR-8050L challenge-handshake
+ profile on Windows.
+- Establishes and verifies the locked/video and unlocked/game views using
+ READ CAPACITY(10), then probes selected XGD1 boundary LBAs with one-sector
+ READ(10) and READ(12) commands.
+- Records per-command success, Win32 error, SCSI status, sense key/ASC/ASCQ,
+ nullable nonzero-byte/all-zero fields for failed reads, SHA-1, complete
+ 2048-byte sector data for successful reads, and explicit READ(10)/READ(12)
+ comparability in an atomic JSON report.
+- Includes the current output-layout constants and probes the visible video
+ boundaries, synthetic pregame/postgame boundaries, game lead-in/XDVDFS
+ boundary, output layer break mapping, and each reported-capacity edge.
+- Restores the entry locked/video state when the probe began locked; an
+ already-unlocked entry remains in the game view.
+- Keeps the existing 0.5.3.10 ISO/XISO output unchanged. The probe explicitly
+ leaves the pregame padding, postgame padding, and game lead-in unresolved
+ until live evidence supports a source mapping.
+- Uses only logical read commands, the established authentication handshake,
+ tray transitions, volume guard, and STOP UNIT cleanup. No firmware write,
+ flash, erase, 3B06, 3B07, or FLASHUP path was added or changed.
+
2026-07-18 - FriiDump 0.5.3.10 state-aware GDR-8050L handshake cleanup
- Replaces the copied reference bridge's unconditional unlock -> tray cycle ->
- output LBA 0..6831: visible DVD-video L0, read from source LBA 0..6831;
- output LBA 6832..198143: pregame padding, currently zero-filled;
-- output LBA 198144..198175: 32-sector game lead-in; FriiDump attempts to read
- these sectors from the active unlocked/game view and zero-fills only unreadable
- sectors;
+- output LBA 198144..198175: 32-sector game lead-in, read from unlocked source
+ LBA 0..31; the native libfriidump path zero-fills only a genuinely unreadable
+ sector and records the fallback count;
- output LBA 198176..3629407: unlocked game/XDVDFS data, normally read from
source LBA 32 on GDR-8050L, or from the detected XDVDFS source LBA on other
supported profiles;
lead-in. For single-layer/homebrew-style media it treats the XDVDFS VolumeSize
field as a byte count and converts it to 2048-byte sectors.
-Both Xbox output styles attempt to read their 32-sector game lead-in from the
-active drive view and zero-fill only sectors that are not readable through the
-selected drive/profile.
+Both Xbox output styles preserve the 32-sector game lead-in from unlocked
+source LBA 0..31. The GDR-8050L reference path fails the dump if this proven
+range cannot be captured; the native libfriidump path retains a counted
+per-sector zero-fill fallback for other profiles or transient read failures.
Supported-drive notes
- `<name>.redump.json` - JSON metadata describing the layout, hashes, drive
profile, PFI/DMI filenames, and lead-in capture status.
-For the GDR-8050L redump path, the 32-sector game lead-in follows the original
-dumper's option-1 behavior and is written as zero-fill before copying unlocked
-XDVDFS data from source LBA `32`. For non-8050L Xbox strategies, FriiDump still
-attempts to read lead-in sectors when the active drive/profile can expose them,
-and zero-fills only unreadable lead-in sectors.
+For the GDR-8050L redump path, the 32-sector game lead-in is captured from
+unlocked source LBA `0..31` before copying XDVDFS data from source LBA `32`.
+FriiDump 0.5.3.13 proved this placement with cache-aligned raw-sector IDs. The
+native libfriidump path retains per-sector zero-fill only for a genuinely
+unreadable lead-in sector and records the read/zero-fill counts; the reference
+Xbox path treats failure to capture the proven lead-in as a dump failure.
FriiDump also records a `discimagecreator_reference` section in the JSON. The
redump-style constants are checked against DiscImageCreator's Original
`--xiso` is a separate output mode and cannot be combined with `-i`, `-r`, or
`-a` in one run.
+## Read-only XGD1 logical-boundary probe
+
+FriiDump 0.5.3.11 adds a Windows-only diagnostic that does not create or alter
+an ISO:
+
+```powershell
+friidump -d f: --xgd1-layout-probe "docs\debug\xgd1-layout-probe.json"
+```
+
+The mode is currently restricted to the GDR-8050L challenge-handshake profile.
+It performs the following evidence sequence:
+
+1. Record the entry READ CAPACITY(10) state.
+2. Cycle the tray and verify the exact 6,992-sector locked/video capacity.
+3. Probe selected boundary LBAs with one-sector READ(10) and READ(12).
+4. Apply the established full Xbox handshake and verify the unlocked/game
+ capacity.
+5. Probe the corresponding unlocked-source and capacity-edge LBAs.
+6. Restore the locked/video entry state when the run began locked.
+7. Atomically publish the JSON report and issue STOP UNIT.
+
+Each command record includes the Win32 result, SCSI status, sense
+key/ASC/ASCQ, all-zero/nonzero classification, SHA-1, and full 2048-byte sector
+data when the read succeeds. Expected out-of-range failures are evidence and
+do not by themselves invalidate the report.
+
+This is a logical-read probe, not a physical-sector dumper. It does not assert
+that inaccessible filler is zero, does not replace the current synthetic
+ranges, and does not promote the reconstructed XGD1 image to Redump identity.
+
## GDR-8050L handshake sequence
The GDR-8050L path performs this sequence when switching from visible DVD-video view
Use `--redump-report <file>` to publish the same atomic schema-2 evidence record
used by GameCube/Wii paths.
-The current XGD1 image map contains documented synthetic zero-filled ranges.
+The current XGD1 image map contains unresolved zero-filled pregame and postgame ranges.
Accordingly, `NO EXACT MATCH` is a valid evidence result even when all readable
sectors were acquired successfully. The verifier reports acquisition evidence
and archive identity as separate claims. `-X` remains outside DAT verification
GDR-3120L remains on the separate `FF 08 01` vendor lock/unlock path. Forced Xbox mode on non-supported Xbox unlock profiles is rejected rather than routed through FriiDump GC/Wii fallback methods.
+
+## XGD1 cache-aligned raw-ID geometry correction (0.5.3.13)
+
+The first 0.5.3.12 hardware report contained a mixture of valid physical IDs
+and stale/block-start cache entries. The corrected probe now performs the same
+zero-transfer READ(12) flush and 16-sector block alignment used by the proven
+modified-GDR-8050L Method 8 path. It dumps only the raw 12-byte header and
+four-byte EDC for the selected slot, decodes the layer bit, normalizes layer-1
+PSNs by 24-bit complement, and compares each sample with its mapped full-output
+LBA.
+
+The live 0.5.3.13 run matched all 13 selected samples and set
+`physical_geometry_resolved` true. The result proves the physical placement of
+the visible video ranges, the full unlocked game range, and the omitted
+pregame/postgame spans. It does not establish the byte content of the omitted
+filler spans.
+
+## Drive-captured XGD1 game lead-in (0.5.3.14)
+
+The successful 0.5.3.13 geometry report established these adjacent mappings:
+
+- unlocked source LBA `0..31` -> output LBA `198144..198175`, raw PSN
+ `0x060600..0x06061F`;
+- unlocked source LBA `32` -> output LBA `198176`, raw PSN `0x060620`, where
+ the `MICROSOFT*XBOX*MEDIA` XDVDFS descriptor begins.
+
+FriiDump 0.5.3.14 therefore replaces the historical synthetic 32-sector
+game-lead-in write with drive capture from source LBA `0..31`. This changes the
+full XGD1 output bytes and requires a new live full-disc dump plus Redump DAT
+comparison. The 191,312-sector pregame and postgame spans retain zero-filled
+placeholder content: their physical geometry is proven, but their inaccessible
+byte content is not.
+
## Logging
The original Xbox reference logger is now the shared FriiDump run logger. Every
The compatibility field `requires_media_transition` is also `false`; it refers
to authentication/unlock acceptance, not to restoring the locked video view.
+
+## XGD1 raw-sector ID geometry probe (0.5.3.12)
+
+Live 0.5.3.11 evidence closed the ordinary logical-read branch: all 17
+successful READ(10)/READ(12) pairs were identical, the locked view stopped at
+6991, and the unlocked view stopped at 3431263. Output-space LBAs outside
+those capacities returned ILLEGAL REQUEST / LBA OUT OF RANGE (05/21/00).
+This means neither READ opcode exposes a hidden logical source for the
+synthetic pregame or postgame ranges.
+
+`--xgd1-raw-id-probe <file>` is the next read-only geometry probe. On the
+modified GDR-8050L profile it fills the drive cache with a controlled 16-sector
+READ(12), dumps the selected 2064-byte raw sector through `E7 48 49 54 01`, and
+decodes bytes 1..3 of the raw DVD ID as the physical-sector number. Adjacent
+locked and unlocked boundary samples can establish whether the active logical
+views map linearly or jump across omitted physical regions.
+
+Raw physical IDs do not reveal the contents of inaccessible filler sectors.
+No zero-filled range may be promoted to exact merely because its geometry is
+confirmed.
+
+
+## 0.5.3.15 live closure
+
+The required 0.5.3.14 hardware run completed on the modified GDR-8050L
+revision `0012`.
+
+The drive-captured source LBA `0..31` lead-in was independently verified at
+output LBA `198144..198175`, and source LBA `32` remained the XDVDFS boundary at
+output LBA `198176`.
+
+Final output:
+
+- sectors: `3,820,880`;
+- bytes: `7,825,162,240`;
+- SHA-256:
+ `5995f823c6d67b9e3e3b3e1c2b54e1e036793188bec6b5be4220f559305f53b9`.
+
+The Microsoft Xbox DAT scan returned `NO EXACT MATCH`. Pregame and postgame
+geometry is proven, but their inaccessible content remains zero-filled and
+unresolved.
+
+Without `--redump-dat-dir`, DAT discovery checks the executable-relative
+`redump_dat` directory before the current working directory. This prevents
+runner working directories from hiding the packaged DAT set.
--- /dev/null
+{
+ "schema": "project-frankenstein-friidump-xgd1-closure.v1",
+ "generated_utc": "2026-07-18T17:00:00Z",
+ "software": {
+ "friidump_version_under_test": "0.5.3.14",
+ "cleanup_release_candidate": "0.5.3.15",
+ "base_commit_before_cleanup": "6e43cd3fd4f7324a177331a01d653b4c19c4bca7"
+ },
+ "hardware": {
+ "drive_model": "HL-DT-ST DVD-ROM GDR8050L",
+ "firmware_revision": "0012",
+ "modified_firmware_e7_memdump": true
+ },
+ "logical_boundary_probe": {
+ "locked_visible_sectors": 6992,
+ "unlocked_visible_sectors": 3431264,
+ "read10_read12_successful_pairs": 17,
+ "read10_read12_all_equal": true,
+ "ordinary_reads_expose_pregame_or_postgame_filler": false
+ },
+ "physical_geometry_probe": {
+ "schema_version": 2,
+ "required_states_verified": true,
+ "geometry_validation_failures": 0,
+ "physical_geometry_resolved": true,
+ "geometry_model": {
+ "dvd_start_psn": 196608,
+ "game_output_start_lba": 198144,
+ "layer_break_lba": 1913776,
+ "layer_zero_last_normalized_psn": 2110383,
+ "layer_one_raw_psn_is_24bit_complement": true
+ },
+ "sample_count": 13,
+ "samples": [
+ {
+ "state": "locked_video",
+ "lba": 0,
+ "label": "locked_logical_start",
+ "cache_fill_lba": 0,
+ "cache_index": 0,
+ "layer_number": 0,
+ "physical_sector_number": 196608,
+ "normalized_physical_sector_number": 196608,
+ "output_lba": 0,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 196608,
+ "expected_normalized_physical_sector_number": 196608,
+ "geometry_matches_expected": true,
+ "logical_sha1": "605db3fdbaff4ba13729371ad0c4fbab3889378e",
+ "raw_header_hex": "20030000d6f5000000000000",
+ "raw_edc_hex": "0560250d"
+ },
+ {
+ "state": "locked_video",
+ "lba": 31,
+ "label": "locked_early_lba_31",
+ "cache_fill_lba": 16,
+ "cache_index": 15,
+ "layer_number": 0,
+ "physical_sector_number": 196639,
+ "normalized_physical_sector_number": 196639,
+ "output_lba": 31,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 196639,
+ "expected_normalized_physical_sector_number": 196639,
+ "geometry_matches_expected": true,
+ "logical_sha1": "605db3fdbaff4ba13729371ad0c4fbab3889378e",
+ "raw_header_hex": "2003001ff7cb000000000000",
+ "raw_edc_hex": "83903453"
+ },
+ {
+ "state": "locked_video",
+ "lba": 32,
+ "label": "locked_early_lba_32",
+ "cache_fill_lba": 32,
+ "cache_index": 0,
+ "layer_number": 0,
+ "physical_sector_number": 196640,
+ "normalized_physical_sector_number": 196640,
+ "output_lba": 32,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 196640,
+ "expected_normalized_physical_sector_number": 196640,
+ "geometry_matches_expected": true,
+ "logical_sha1": "3e4eb33f37c052df89b0ccc4e239828c3b9556aa",
+ "raw_header_hex": "20030020b6b5000000000000",
+ "raw_edc_hex": "0b23613f"
+ },
+ {
+ "state": "locked_video",
+ "lba": 6831,
+ "label": "video_l0_last",
+ "cache_fill_lba": 6816,
+ "cache_index": 15,
+ "layer_number": 0,
+ "physical_sector_number": 203439,
+ "normalized_physical_sector_number": 203439,
+ "output_lba": 6831,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 203439,
+ "expected_normalized_physical_sector_number": 203439,
+ "geometry_matches_expected": true,
+ "logical_sha1": "36661a56261adccc33185e383fb022c0e36da109",
+ "raw_header_hex": "20031aaf7cea000000000000",
+ "raw_edc_hex": "cf771b52"
+ },
+ {
+ "state": "locked_video",
+ "lba": 6832,
+ "label": "video_l1_first",
+ "cache_fill_lba": 6832,
+ "cache_index": 0,
+ "layer_number": 1,
+ "physical_sector_number": 16573776,
+ "normalized_physical_sector_number": 203439,
+ "output_lba": 3820720,
+ "expected_layer_number": 1,
+ "expected_physical_sector_number": 16573776,
+ "expected_normalized_physical_sector_number": 203439,
+ "geometry_matches_expected": true,
+ "logical_sha1": "605db3fdbaff4ba13729371ad0c4fbab3889378e",
+ "raw_header_hex": "01fce550135b000000000000",
+ "raw_edc_hex": "d8734d6e"
+ },
+ {
+ "state": "locked_video",
+ "lba": 6991,
+ "label": "locked_video_last",
+ "cache_fill_lba": 6976,
+ "cache_index": 15,
+ "layer_number": 1,
+ "physical_sector_number": 16573935,
+ "normalized_physical_sector_number": 203280,
+ "output_lba": 3820879,
+ "expected_layer_number": 1,
+ "expected_physical_sector_number": 16573935,
+ "expected_normalized_physical_sector_number": 203280,
+ "geometry_matches_expected": true,
+ "logical_sha1": "775869f6db440ccfd5f1cc826a725c745ffda9d4",
+ "raw_header_hex": "01fce5efcf38000000000000",
+ "raw_edc_hex": "84c7dc79"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 0,
+ "label": "unlocked_source_start",
+ "cache_fill_lba": 0,
+ "cache_index": 0,
+ "layer_number": 0,
+ "physical_sector_number": 394752,
+ "normalized_physical_sector_number": 394752,
+ "output_lba": 198144,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 394752,
+ "expected_normalized_physical_sector_number": 394752,
+ "geometry_matches_expected": true,
+ "logical_sha1": "22815399cde9e12d539d64cec38f332830994614",
+ "raw_header_hex": "6e060600deb0000000000000",
+ "raw_edc_hex": "60ae4fb1"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 31,
+ "label": "unlocked_leadin_last",
+ "cache_fill_lba": 16,
+ "cache_index": 15,
+ "layer_number": 0,
+ "physical_sector_number": 394783,
+ "normalized_physical_sector_number": 394783,
+ "output_lba": 198175,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 394783,
+ "expected_normalized_physical_sector_number": 394783,
+ "geometry_matches_expected": true,
+ "logical_sha1": "b9aee89323d1fa0071c13d736b3536251a4773d2",
+ "raw_header_hex": "6e06061fff8e000000000000",
+ "raw_edc_hex": "e873673b"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 32,
+ "label": "xdfs_source_start",
+ "cache_fill_lba": 32,
+ "cache_index": 0,
+ "layer_number": 0,
+ "physical_sector_number": 394784,
+ "normalized_physical_sector_number": 394784,
+ "output_lba": 198176,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 394784,
+ "expected_normalized_physical_sector_number": 394784,
+ "geometry_matches_expected": true,
+ "logical_sha1": "827a963f0b7db9749bf127525c45f84cf18ef666",
+ "raw_header_hex": "6e060620bef0000000000000",
+ "raw_edc_hex": "5f747f7e"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 33,
+ "label": "xdfs_source_next",
+ "cache_fill_lba": 32,
+ "cache_index": 1,
+ "layer_number": 0,
+ "physical_sector_number": 394785,
+ "normalized_physical_sector_number": 394785,
+ "output_lba": 198177,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 394785,
+ "expected_normalized_physical_sector_number": 394785,
+ "geometry_matches_expected": true,
+ "logical_sha1": "cdda4c9956920f00e23cea7a48a4e7a74244b565",
+ "raw_header_hex": "6e060621bdf2000000000000",
+ "raw_edc_hex": "fe1f7ff9"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 1715631,
+ "label": "mapped_layer_break_source_minus_one",
+ "cache_fill_lba": 1715616,
+ "cache_index": 15,
+ "layer_number": 0,
+ "physical_sector_number": 2110383,
+ "normalized_physical_sector_number": 2110383,
+ "output_lba": 1913775,
+ "expected_layer_number": 0,
+ "expected_physical_sector_number": 2110383,
+ "expected_normalized_physical_sector_number": 2110383,
+ "geometry_matches_expected": true,
+ "logical_sha1": "ad29c120fb69e05dceec32ef76674d8d4c38c739",
+ "raw_header_hex": "6e2033af66b4000000000000",
+ "raw_edc_hex": "8fc60c80"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 1715632,
+ "label": "mapped_layer_break_source",
+ "cache_fill_lba": 1715632,
+ "cache_index": 0,
+ "layer_number": 1,
+ "physical_sector_number": 14666832,
+ "normalized_physical_sector_number": 2110383,
+ "output_lba": 1913776,
+ "expected_layer_number": 1,
+ "expected_physical_sector_number": 14666832,
+ "expected_normalized_physical_sector_number": 2110383,
+ "geometry_matches_expected": true,
+ "logical_sha1": "99b55f2cc7da36729f1597a5f6b28131e8be6417",
+ "raw_header_hex": "4fdfcc500905000000000000",
+ "raw_edc_hex": "f420ba4b"
+ },
+ {
+ "state": "unlocked_game",
+ "lba": 3431263,
+ "label": "unlocked_source_last",
+ "cache_fill_lba": 3431248,
+ "cache_index": 15,
+ "layer_number": 1,
+ "physical_sector_number": 16382463,
+ "normalized_physical_sector_number": 394752,
+ "output_lba": 3629407,
+ "expected_layer_number": 1,
+ "expected_physical_sector_number": 16382463,
+ "expected_normalized_physical_sector_number": 394752,
+ "geometry_matches_expected": true,
+ "logical_sha1": "de52cf1b22c636d9f360e4faeaad55271dff459d",
+ "raw_header_hex": "4ff9f9ffb101000000000000",
+ "raw_edc_hex": "79dfff25"
+ }
+ ]
+ },
+ "drive_captured_game_leadin": {
+ "unlocked_source_start_lba": 0,
+ "sector_count": 32,
+ "output_start_lba": 198144,
+ "output_end_lba": 198175,
+ "nonzero_bytes": 65266,
+ "sha1": "e5cd93fe0f7c11c93878d2cb9d909de1c333978c",
+ "sha256": "0ada0cffb6fe9ff506955cb2a16a7f33a4ff9ae8360e9c352a4fcc843abf5af2",
+ "first_sector_sha1": "22815399cde9e12d539d64cec38f332830994614",
+ "last_sector_sha1": "b9aee89323d1fa0071c13d736b3536251a4773d2",
+ "xdfs_output_lba": 198176,
+ "xdfs_first_sector_sha1": "827a963f0b7db9749bf127525c45f84cf18ef666",
+ "xdfs_magic": "MICROSOFT*XBOX*MEDIA",
+ "source": "drive_read10",
+ "verified": true
+ },
+ "full_output": {
+ "title": "Red Faction II",
+ "media_id": "TQ00501A",
+ "sector_size": 2048,
+ "sector_count": 3820880,
+ "byte_count": 7825162240,
+ "hashes": {
+ "crc32": "cc5f0e71",
+ "md5": "1207b2090701ba1f221d486c83c863e7",
+ "sha1": "5ea9b2262ba951afd9a5d2d44cd2b62a4d9decc7",
+ "sha256": "5995f823c6d67b9e3e3b3e1c2b54e1e036793188bec6b5be4220f559305f53b9"
+ },
+ "layout": {
+ "layout_type": "original_xbox_xgd1_redump_style_2048",
+ "layers": 2,
+ "layer_break_lba": 1913776,
+ "video_l0_start_lba": 0,
+ "video_l0_sector_count": 6832,
+ "pregame_padding_start_lba": 6832,
+ "pregame_padding_sector_count": 191312,
+ "game_output_start_lba": 198144,
+ "game_leadin_unlocked_source_start_lba": 0,
+ "game_leadin_source_sector_count": 32,
+ "game_leadin_source": "drive_read10",
+ "game_unlocked_source_start_lba": 32,
+ "game_unlocked_source_sector_count": 3431232,
+ "game_xiso_leadin_sector_count": 32,
+ "xdfs_volume_lba_within_game_region": 32,
+ "game_sector_count": 3431264,
+ "postgame_padding_start_lba": 3629408,
+ "postgame_padding_sector_count": 191312,
+ "video_l1_start_lba": 3820720,
+ "video_l1_sector_count": 160,
+ "legacy_contiguous_visible_sector_count": 198144,
+ "drive_locked_visible_sector_count": 6992,
+ "drive_reported_unlocked_sector_count": 3431264,
+ "reconstructed_output_sector_count": 3820880
+ }
+ },
+ "redump_verification": {
+ "dat_name": "Microsoft - Xbox.dat",
+ "dat_sha256": "e7a76ba732b7b978148e93ecd92befd3b59ef8e850eca10241390d405f7f4116",
+ "entries_scanned": 2689,
+ "status": "NO MATCH",
+ "overall": "NO EXACT MATCH",
+ "exact_matches": 0,
+ "size_matches": 2626,
+ "crc32_matches": 0,
+ "md5_matches": 0,
+ "sha1_matches": 0,
+ "detail": "No entry matched all required fields and no hash-correlated candidate was found."
+ },
+ "evidence_boundary": {
+ "pregame_physical_geometry_resolved": true,
+ "postgame_physical_geometry_resolved": true,
+ "pregame_content_resolved": false,
+ "postgame_content_resolved": false,
+ "current_filler_policy": "zero-filled unresolved placeholder",
+ "exact_redump_identity_established": false
+ }
+}
--- /dev/null
+state,lba,label,cache_fill_lba,cache_index,layer_number,physical_sector_number,normalized_physical_sector_number,output_lba,expected_layer_number,expected_physical_sector_number,expected_normalized_physical_sector_number,geometry_matches_expected,logical_sha1,raw_header_hex,raw_edc_hex
+locked_video,0,locked_logical_start,0,0,0,196608,196608,0,0,196608,196608,True,605db3fdbaff4ba13729371ad0c4fbab3889378e,20030000d6f5000000000000,0560250d
+locked_video,31,locked_early_lba_31,16,15,0,196639,196639,31,0,196639,196639,True,605db3fdbaff4ba13729371ad0c4fbab3889378e,2003001ff7cb000000000000,83903453
+locked_video,32,locked_early_lba_32,32,0,0,196640,196640,32,0,196640,196640,True,3e4eb33f37c052df89b0ccc4e239828c3b9556aa,20030020b6b5000000000000,0b23613f
+locked_video,6831,video_l0_last,6816,15,0,203439,203439,6831,0,203439,203439,True,36661a56261adccc33185e383fb022c0e36da109,20031aaf7cea000000000000,cf771b52
+locked_video,6832,video_l1_first,6832,0,1,16573776,203439,3820720,1,16573776,203439,True,605db3fdbaff4ba13729371ad0c4fbab3889378e,01fce550135b000000000000,d8734d6e
+locked_video,6991,locked_video_last,6976,15,1,16573935,203280,3820879,1,16573935,203280,True,775869f6db440ccfd5f1cc826a725c745ffda9d4,01fce5efcf38000000000000,84c7dc79
+unlocked_game,0,unlocked_source_start,0,0,0,394752,394752,198144,0,394752,394752,True,22815399cde9e12d539d64cec38f332830994614,6e060600deb0000000000000,60ae4fb1
+unlocked_game,31,unlocked_leadin_last,16,15,0,394783,394783,198175,0,394783,394783,True,b9aee89323d1fa0071c13d736b3536251a4773d2,6e06061fff8e000000000000,e873673b
+unlocked_game,32,xdfs_source_start,32,0,0,394784,394784,198176,0,394784,394784,True,827a963f0b7db9749bf127525c45f84cf18ef666,6e060620bef0000000000000,5f747f7e
+unlocked_game,33,xdfs_source_next,32,1,0,394785,394785,198177,0,394785,394785,True,cdda4c9956920f00e23cea7a48a4e7a74244b565,6e060621bdf2000000000000,fe1f7ff9
+unlocked_game,1715631,mapped_layer_break_source_minus_one,1715616,15,0,2110383,2110383,1913775,0,2110383,2110383,True,ad29c120fb69e05dceec32ef76674d8d4c38c739,6e2033af66b4000000000000,8fc60c80
+unlocked_game,1715632,mapped_layer_break_source,1715632,0,1,14666832,2110383,1913776,1,14666832,2110383,True,99b55f2cc7da36729f1597a5f6b28131e8be6417,4fdfcc500905000000000000,f420ba4b
+unlocked_game,3431263,unlocked_source_last,3431248,15,1,16382463,394752,3629407,1,16382463,394752,True,de52cf1b22c636d9f360e4faeaad55271dff459d,4ff9f9ffb101000000000000,79dfff25
--- /dev/null
+FriiDump XGD1 drive-captured lead-in live closure
+Result: PASS
+Validated FriiDump version: 0.5.3.14
+Drive: HL-DT-ST DVD-ROM GDR8050L revision 0012
+Output sectors: 3820880
+Output bytes: 7825162240
+CRC32: cc5f0e71
+MD5: 1207b2090701ba1f221d486c83c863e7
+SHA-1: 5ea9b2262ba951afd9a5d2d44cd2b62a4d9decc7
+SHA-256: 5995f823c6d67b9e3e3b3e1c2b54e1e036793188bec6b5be4220f559305f53b9
+Lead-in unlocked source LBA: 0..31
+Lead-in output LBA: 198144..198175
+Lead-in nonzero bytes: 65266
+Lead-in SHA-1: e5cd93fe0f7c11c93878d2cb9d909de1c333978c
+Lead-in SHA-256: 0ada0cffb6fe9ff506955cb2a16a7f33a4ff9ae8360e9c352a4fcc843abf5af2
+Lead-in first sector SHA-1: 22815399cde9e12d539d64cec38f332830994614
+Lead-in last sector SHA-1: b9aee89323d1fa0071c13d736b3536251a4773d2
+XDVDFS output LBA: 198176
+XDVDFS first sector SHA-1: 827a963f0b7db9749bf127525c45f84cf18ef666
+XDVDFS magic: MICROSOFT*XBOX*MEDIA
+DAT: Microsoft - Xbox.dat
+DAT SHA-256: e7a76ba732b7b978148e93ecd92befd3b59ef8e850eca10241390d405f7f4116
+DAT entries scanned: 2689
+DAT status: NO EXACT MATCH
+Exact matches: 0
+Pregame content: unresolved zero-filled placeholder
+Postgame content: unresolved zero-filled placeholder
--- /dev/null
+{
+ "format": "xdvd-media-profile",
+ "version": 1,
+ "media_id": "TQ00501A",
+ "title_hint": "Red_Faction_II",
+ "image": {
+ "sector_size": 2048,
+ "sector_count": 3820880,
+ "byte_count": 7825162240,
+ "hashes": {
+ "crc32": "cc5f0e71",
+ "md5": "1207b2090701ba1f221d486c83c863e7",
+ "sha1": "5ea9b2262ba951afd9a5d2d44cd2b62a4d9decc7",
+ "sha256": "5995f823c6d67b9e3e3b3e1c2b54e1e036793188bec6b5be4220f559305f53b9"
+ }
+ },
+ "layout": {
+ "layout_type": "original_xbox_xgd1_redump_style_2048",
+ "layers": 2,
+ "layer_break_lba": 1913776,
+ "video_l0_start_lba": 0,
+ "video_l0_sector_count": 6832,
+ "pregame_padding_start_lba": 6832,
+ "pregame_padding_sector_count": 191312,
+ "game_output_start_lba": 198144,
+ "game_leadin_unlocked_source_start_lba": 0,
+ "game_leadin_source_sector_count": 32,
+ "game_leadin_source": "drive_read10",
+ "game_unlocked_source_start_lba": 32,
+ "game_unlocked_source_sector_count": 3431232,
+ "game_xiso_leadin_sector_count": 32,
+ "xdfs_volume_lba_within_game_region": 32,
+ "game_sector_count": 3431264,
+ "postgame_padding_start_lba": 3629408,
+ "postgame_padding_sector_count": 191312,
+ "video_l1_start_lba": 3820720,
+ "video_l1_sector_count": 160,
+ "legacy_contiguous_visible_sector_count": 198144,
+ "drive_locked_visible_sector_count": 6992,
+ "drive_reported_unlocked_sector_count": 3431264,
+ "reconstructed_output_sector_count": 3820880
+ },
+ "reconstruction": {
+ "is_reconstructed_layout": true,
+ "filler_policy": "pregame_and_postgame_zero_fill_content_placeholder",
+ "filler_geometry_verified": true,
+ "filler_verified_from_disc": false,
+ "pending_hardware_capture": false,
+ "unresolved_filler_content": true,
+ "note": "Pregame and postgame physical locations are verified; inaccessible contents remain unresolved. The 32-sector game lead-in is drive-captured."
+ },
+ "validation": {
+ "byte_count_matches_sector_count": true,
+ "lead_in_mapping_verified": true,
+ "redump_exact_match": false
+ }
+}
--- /dev/null
+# XGD1 geometry and drive-captured lead-in evidence
+
+This directory contains the compact, repository-appropriate evidence retained
+from the FriiDump 0.5.3.11 through 0.5.3.14 investigation.
+
+Retained:
+
+- the 13 cache-aligned physical-sector geometry samples;
+- the consolidated machine-readable closure record;
+- the completed 0.5.3.14 full-output and Redump comparison summary;
+- a path-sanitized media/layout evidence record.
+
+Not retained:
+
+- local build logs;
+- runner transcripts;
+- package checksum rehearsals;
+- duplicate stdout/stderr captures;
+- full logical-sector byte dumps;
+- the 7.29 GiB ISO;
+- PFI, DMI, AD C0, or other disc-specific binary payloads.
+
+Evidence conclusion:
+
+- unlocked source LBA `0..31` is real drive-readable content and maps to
+ full-output LBA `198144..198175`;
+- unlocked source LBA `32` begins XDVDFS at output LBA `198176`;
+- pregame and postgame physical geometry is resolved;
+- inaccessible pregame and postgame contents remain unresolved zero-filled
+ placeholders;
+- no exact Microsoft Xbox DAT identity was established.
-FriiDump 0.5.3.2 - Copyright (C) 2007 Arep
+FriiDump 0.5.3.15 - Copyright (C) 2007 Arep
This software comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; see COPYING for details.
3 - DVD
4 - Xbox/XGD 2048-byte-sector mode
Native profiles: GDR-8050L and GDR-3120L.
- Forced Xbox mode requires a supported Xbox
- unlock profile; no GC/Wii fallback is used.
+ Other drives keep normal FriiDump behavior
+ unless Xbox mode is explicitly forced.
-S, --size <sectors> Force disc size
-r, --raw <file> Output to file <file> in raw format (2064-byte
sectors)
- -i, --iso <file> Output to file <file> in ISO format (2048-byte
- sectors). For Xbox/XGD this reconstructs the
- redump-style XGD1 layout and writes .pfi.bin,
- .dmi.bin, and .redump.json metadata when possible
- -X, --xiso <file> Output Xbox/XGD game partition as XISO (.xiso).
- Attempts to read the 32-sector game lead-in
- from drive-readable sectors and zero-fills only
- unreadable lead-in sectors. Use -i for
- redump-style ISO + PFI/DMI/JSON; use -X only
- for game-partition XISO.
+ -i, --iso[=<file>] Output to file <file> in ISO format (2048-byte
+ sectors). For Xbox/GDR-8050L, omitting <file>
+ derives Title[MediaID].iso from the XBE/DMI;
+ providing <file> is an explicit override. For
+ Xbox/XGD this reconstructs the redump-style
+ XGD1 layout and writes .pfi.bin, .dmi.bin, and
+ .redump.json metadata when possible
+ -X, --xiso[=<file>] Output Xbox/XGD game partition as XISO (.xiso).
+ For Xbox/GDR-8050L, omitting <file> derives
+ Title[MediaID].xiso from the XBE/DMI; providing
+ <file> is an explicit override. Attempts to read
+ the 32-sector game lead-in from drive-readable
+ sectors and zero-fills only unreadable sectors.
-u, --unscramble <file> Convert (unscramble) raw image contained in
<file> to ISO format
-H, --nohash Do not compute CRC32/MD5/SHA-1/SHA-256 hashes
-9, --method9 Use dumping method 9 (Read and dump 5 blocks
at a time, using streaming read, using DMA and
some speed tricks)
+ --hlds-e7-scan Probe HLDS HIT 0xE7 cache/memdump bases only;
+ writes JSON and does not crack seeds or dump data
+ --hlds-e7-subcmd-sweep Probe HIT 0xE7 subcommands/address candidates only;
+ writes JSON and does not crack seeds or dump data
+ --hlds-e7-memrange-sweep Sweep wider HIT 0xE7 subcmd 0x01 address ranges;
+ writes JSON and does not crack seeds or dump data
+ --scan-log <file> JSON output path for --hlds-e7-scan
+ --scan-dump-prefix <prefix> Optional raw 0xE7 window dump prefix for --hlds-e7-scan
+ --hlds-profile-report <file> Write selected HLDS profile/evidence JSON
+ --redump-dat-dir <dir> Directory containing canonical Redump DAT files
+ (default: executable-relative redump_dat, then current directory)
+ --redump-report <file> Write atomic Redump evidence JSON
+ --no-redump-verify Disable automatic post-dump DAT verification
+ --xgd1-layout-probe <file> Read-only locked/unlocked XGD1 boundary probe;
+ writes atomic JSON and does not create an ISO
+ --xgd1-raw-id-probe <file> Modified-firmware cache-flushed, block-aligned raw-ID probe;
+ maps logical LBAs to decoded physical sector IDs
-A, --allmethods Try all known command/method combinations until
one works. Reopens the drive for each command so
command-specific vendor handlers are rebound.
-
-Xbox dump notes:
- - GDR-8050L redump-style -i follows the original dumper order:
- primary handshake, media-cycle, re-handshake, metadata/auth probe,
- media-cycle back to visible DVD-video view, capture video L0/L1, final
- handshake, zero-fill the 32-sector game lead-in, read game data, then write
- .pfi.bin, .dmi.bin, and .redump.json.
- - FriiDump sends STOP UNIT / spin-down after dump attempts, success or failure.
--- /dev/null
+# FriiDump 0.5.3.13 XGD1 cache-aligned raw-ID geometry probe
+
+## Objective
+
+Correct the v68 raw-ID acquisition contract and test the current XGD1
+logical-to-physical output mapping with the same cache discipline used by the
+proven modified-GDR-8050L Method 8 path.
+
+## Why v68 was not promotable
+
+The v68 hardware run succeeded at issuing commands, but its raw-ID table mixed
+valid physical IDs with stale or block-start cache entries. It issued streaming
+READ(12) at arbitrary LBAs without the zero-transfer cache flush and without
+aligning the request to the 16-sector block represented at cache base
+`0x80000000`. It also treated raw-cache user-data equality as meaningful even
+though Method 8 reconstructs the raw sector from cache header/EDC plus READ(12)
+user data before unscrambling.
+
+## Corrected capture sequence
+
+For every selected logical LBA:
+
+1. Align the cache-fill LBA downward to a 16-sector boundary.
+2. Issue READ(12) with byte 1 set to `0x08` and zero transfer length to reset the
+ cache mapping.
+3. Issue a 16-sector streaming READ(12) at the aligned block start.
+4. Retain the requested logical sector from the returned 2048-byte data.
+5. Dump only the selected cache slot's 12-byte ID/IED/CPR_MAI header and
+ four-byte EDC using `E7 48 49 54 01`.
+6. Decode layer number from sector-information bit 0 and decode the 24-bit raw
+ physical-sector number.
+7. Normalize layer-1 PSNs by 24-bit complement and compare each sample to the
+ full reconstructed-output geometry.
+
+## Geometry model under test
+
+- DVD start PSN: `0x030000`.
+- Full output layer break: LBA `1,913,776`.
+- Layer-0 last normalized PSN: `0x2033AF`.
+- Unlocked/game view maps to full output beginning at LBA `198,144`.
+- Locked LBAs `0..6831` map to output video L0.
+- Locked LBAs `6832..6991` map to output LBAs `3,820,720..3,820,879`.
+- Layer-1 raw PSNs are compared in their 24-bit complemented representation.
+
+The probe reports `physical_geometry_resolved=true` only when both states are
+verified and all 13 selected samples match the expected layer and PSN exactly.
+A mismatch is preserved in the atomic JSON report and causes a nonzero exit.
+
+## Interpretation boundary
+
+A full geometry match validates placement of readable video and game sectors in
+the current reconstructed output. It does not reveal inaccessible pregame or
+postgame filler sectors and does not establish their byte values. Filler-content
+claims remain false.
+
+## Safety
+
+The probe uses readiness, tray, authentication, READ CAPACITY, cache-flush
+READ(12), streaming READ(12), and HIT 0xE7 data-in reads. It issues no firmware
+write, flash, erase, 3B06, 3B07, or FLASHUP command and creates no ISO.
--- /dev/null
+# FriiDump 0.5.3.14 drive-captured XGD1 game lead-in
+
+## Evidence basis
+
+The 0.5.3.13 live cache-aligned raw-ID probe completed successfully on the
+modified `HL-DT-ST DVD-ROM GDR8050L` revision `0012`. Both required drive states
+were established, all 13 selected samples matched the expected layer and raw
+physical-sector number, independent validation reported zero failures, and the
+report set `physical_geometry_resolved` to true.
+
+The decisive adjacent samples were:
+
+| Unlocked source LBA | Output LBA | Raw PSN | Meaning |
+|---:|---:|---:|---|
+| 0 | 198144 | `0x060600` | first game-region lead-in sector |
+| 31 | 198175 | `0x06061F` | last game-region lead-in sector |
+| 32 | 198176 | `0x060620` | XDVDFS descriptor sector |
+| 33 | 198177 | `0x060621` | next XDVDFS sector |
+
+This closes the earlier ambiguity around the 32-sector region. It is not
+synthetic padding: it is a readable physical range exposed by the unlocked
+GDR-8050L view.
+
+## 0.5.3.14 behavior
+
+The redump-style full-output path now writes:
+
+- unlocked source LBA `0..31` to output LBA `198144..198175`;
+- unlocked source LBA `32..3431263` to output LBA `198176..3629407`.
+
+The Windows reference path treats failure to capture LBA `0..31` as a dump
+failure. The native libfriidump path uses the existing per-sector read/fallback
+helper and records `readable_sectors` and `zero_filled_sectors` in
+`.redump.json`.
+
+The metadata sidecars now distinguish:
+
+- drive-captured game lead-in;
+- geometry-verified pregame/postgame spans;
+- unresolved pregame/postgame content that remains zero-filled.
+
+## Evidence boundary
+
+This update does not claim the pregame or postgame zero-filled bytes are exact.
+The 0.5.3.13 proof establishes where those omitted physical spans lie, not their
+inaccessible contents.
+
+## Live closure
+
+The required full-disc 0.5.3.14 run completed on the modified GDR-8050L
+revision `0012`.
+
+Observed output:
+
+- sectors: `3,820,880`;
+- bytes: `7,825,162,240`;
+- CRC32: `cc5f0e71`;
+- MD5: `1207b2090701ba1f221d486c83c863e7`;
+- SHA-1: `5ea9b2262ba951afd9a5d2d44cd2b62a4d9decc7`;
+- SHA-256:
+ `5995f823c6d67b9e3e3b3e1c2b54e1e036793188bec6b5be4220f559305f53b9`.
+
+The lead-in at output LBA `198144..198175` contained `65,266` nonzero bytes.
+Its first and last sector SHA-1 values matched the independent 0.5.3.13 source
+LBA `0` and source LBA `31` evidence. The XDVDFS descriptor at output LBA
+`198176` also matched source LBA `32` and contained
+`MICROSOFT*XBOX*MEDIA`.
+
+The Microsoft Xbox DAT comparison scanned `2,689` entries and returned
+`NO EXACT MATCH`: `2,626` size matches and zero CRC32, MD5, or SHA-1 matches.
+No archive identity is established. The unresolved pregame and postgame
+placeholder contents remain the documented reason that a clean acquisition can
+differ from a canonical full-disc image.
--- /dev/null
+# FriiDump 0.5.3.15 XGD1 live closure and release cleanup
+
+## Scope
+
+This release candidate consolidates the accepted 0.5.3.11 through 0.5.3.14
+XGD1 investigation into a repository-ready state. It does not change the
+0.5.3.14 full-output mapping.
+
+## Proven behavior
+
+The modified GDR-8050L revision `0012` established:
+
+- locked/video visible capacity: `6,992` sectors;
+- unlocked/game visible capacity: `3,431,264` sectors;
+- all 17 successful paired READ(10)/READ(12) samples were byte-identical;
+- all 13 cache-aligned raw-sector-ID samples matched the expected physical
+ geometry;
+- unlocked source LBA `0..31` maps to output LBA `198144..198175`;
+- unlocked source LBA `32` begins XDVDFS at output LBA `198176`.
+
+The completed 0.5.3.14 full-output run independently verified the first and last
+lead-in sectors, the XDVDFS boundary, all sidecar mappings, and finalized output
+hashes.
+
+## Evidence boundary
+
+The pregame and postgame physical locations are resolved. Their inaccessible
+contents are not. FriiDump continues to write zero-filled placeholders for
+those two spans and records that limitation in sidecars and Redump evidence.
+
+The Microsoft Xbox DAT comparison returned `NO EXACT MATCH`. Acquisition
+success and archive identity remain separate claims.
+
+## Repository cleanup
+
+The development packages contained local CMake logs, package rehearsals,
+stdout/stderr captures, runner transcripts, and duplicate raw probe reports.
+Those files are not source authority.
+
+The cleaned tree retains:
+
+- the implementation and diagnostic probe commands;
+- four engineering reports for versions 0.5.3.11 through 0.5.3.14;
+- a compact machine-readable XGD1 closure record;
+- the 13 geometry samples;
+- path-sanitized 0.5.3.14 media-layout evidence;
+- the completed live summary;
+- a C test for DAT path resolution.
+
+## DAT discovery correction
+
+Without `--redump-dat-dir`, FriiDump now checks:
+
+1. `redump_dat` beside the running executable;
+2. `redump_dat` under the current working directory.
+
+An explicit `--redump-dat-dir` remains the first and authoritative choice.
+This prevents a valid packaged DAT set from being missed merely because a test
+runner launches FriiDump from another directory.
--- /dev/null
+# FriiDump 0.5.3.11 XGD1 logical-boundary probe
+
+## Objective
+
+Collect direct, read-only evidence about the XGD1 ranges that FriiDump
+0.5.3.10 still represents with documented synthetic zero-fill. This update
+does not change the reconstructed ISO layout.
+
+## Command
+
+```powershell
+friidump -d f: --xgd1-layout-probe "docs\debug\xgd1-layout-probe.json"
+```
+
+The option is exclusive with image output, conversion, `--allmethods`, and the
+HLDS 0xE7 probe modes.
+
+## State sequence
+
+1. Record entry READ CAPACITY(10).
+2. Cycle the tray to establish the locked/video view.
+3. Probe locked/video boundary LBAs using one-sector READ(10) and READ(12).
+4. Apply the existing GDR-8050L full authentication handshake.
+5. Verify the unlocked/game READ CAPACITY(10) state.
+6. Probe unlocked-source and capacity-edge LBAs using READ(10) and READ(12).
+7. Restore the entry locked/video state when required.
+8. Atomically publish the JSON report.
+9. Return to FriiDump for one STOP UNIT cleanup.
+
+## Evidence captured per command
+
+- command success;
+- Win32 error;
+- SCSI status;
+- sense key, ASC, and ASCQ;
+- count of nonzero bytes for a successful read, otherwise `null`;
+- all-zero classification for a successful read, otherwise `null`;
+- SHA-1 of a successful 2048-byte sector;
+- complete successful sector bytes as hexadecimal;
+- explicit READ(10)/READ(12) comparability and equality for the same LBA;
+- exact 6,992-sector locked/video and 3,431,264-sector unlocked/game state
+ verification.
+
+## Probe points
+
+The locked/video state includes the logical start, LBA 31/32, VIDEO-L0 end,
+locked VIDEO-L1 start/end, locked-capacity edge, synthetic pregame boundary,
+game-output start, output layer break, synthetic postgame boundary, VIDEO-L1
+output start, and full-output end.
+
+The unlocked/game state includes source LBA 0/31/32/33, the synthetic-gap
+length, output-boundary values interpreted as unlocked source LBAs, the mapped
+output-layer-break source, the final reported game-view sector, and
+out-of-range capacity/output candidates.
+
+## Interpretation boundary
+
+A successful logical read proves only that the active firmware state exposes
+that LBA through ordinary READ(10) or READ(12). A failed read is also useful
+sense evidence. Neither result alone proves the physical mastering contents of
+an inaccessible XGD1 filler range.
+
+The report therefore fixes these fields to false until later analysis:
+
+- `pregame_padding_resolved`
+- `postgame_padding_resolved`
+- `game_leadin_resolved`
+
+## Safety
+
+No firmware update, write, flash, erase, vendor-memory write, 3B06, 3B07, or
+FLASHUP command is used. The probe uses readiness checks, tray transitions,
+the already-established authentication handshake, READ CAPACITY, logical
+READ(10)/READ(12), a Windows volume guard, atomic report publication, and STOP
+UNIT cleanup.
+
+## Live validation result (2026-07-18)
+
+The cross-flashed GDR-8163B running modified GDR-8050L 0012 firmware passed
+the probe. Entry state was the 3,431,264-sector game view. A tray cycle
+established the exact 6,992-sector locked/video view, the full challenge
+handshake restored the exact game view, and the run ended in its original
+game-view state.
+
+Seventeen sampled LBAs were readable in-range. READ(10) and READ(12) returned
+identical 2048-byte sectors for every successful pair. Eleven sampled
+out-of-range LBAs failed under both commands with sense 05/21/00. Locked LBA
+6992 was rejected, and unlocked LBA 3431264 was rejected.
+
+This closes the ordinary logical-read branch: there is no second READ(12)
+window and no numeric output-LBA path to the synthetic pregame or postgame
+regions. Unlocked LBA 0..31 is directly readable and nonzero, while LBA 32
+contains `MICROSOFT*XBOX*MEDIA`; exact physical placement still requires raw
+sector-ID geometry evidence.
--- /dev/null
+# FriiDump 0.5.3.12 XGD1 raw-sector ID geometry probe
+
+## Objective
+
+Use the modified GDR-8050L HIT 0xE7 memory-dump surface to recover the raw DVD
+ID fields associated with selected logical LBAs. The 24-bit physical-sector
+number in each raw ID can test the logical-to-physical geometry that ordinary
+READ(10)/READ(12) could not resolve.
+
+## Command
+
+```powershell
+friidump -d f: --xgd1-raw-id-probe "docs\debug\xgd1-raw-id-probe.json"
+```
+
+This mode is Windows-only, exclusive with all image-output and other probe
+modes, limited to the GDR-8050L challenge-handshake path, and additionally
+requires the modified HIT 0xE7 profile.
+
+## Capture sequence
+
+1. Record entry capacity.
+2. Tray-cycle to the 6,992-sector locked/video state.
+3. For selected locked LBAs, issue a 16-sector streaming READ(12), retain the
+ target 2048-byte logical sector, and dump the corresponding 2064-byte raw
+ cache slot from base 0x80000000.
+4. Apply the existing challenge handshake and verify the 3,431,264-sector game
+ state.
+5. Repeat at selected unlocked LBAs, including source start, XDFS start, mapped
+ layer-break neighbors, and source end.
+6. Restore the entry state when needed, atomically publish JSON, and issue one
+ STOP UNIT.
+
+## Evidence per sample
+
+- logical LBA, cache-fill LBA, cache slot, and absolute memory address;
+- READ(12) and HIT 0xE7 command status plus sense;
+- full logical 2048-byte sector and full raw 2064-byte cache sector;
+- SHA-1 for logical, raw, and raw-user-data regions;
+- raw-user/logical equality;
+- raw 12-byte ID/IED/CPR_MAI header and four-byte EDC;
+- sector-information byte;
+- decoded 24-bit physical-sector number.
+
+## Interpretation boundary
+
+A valid raw ID can prove geometry or a discontinuity between logical views. It
+does not make omitted filler sectors readable and does not establish their
+bytes. `physical_geometry_resolved`, `pregame_filler_content_resolved`, and
+`postgame_filler_content_resolved` remain false in the generated report until
+the captured values are analyzed.
+
+## Safety
+
+The probe issues readiness, tray, authentication, READ CAPACITY, READ(12), and
+HIT 0xE7 data-in memory reads only. It does not issue firmware updates, flash,
+erase, vendor-memory writes, 3B06, 3B07, or FLASHUP commands and does not create
+an ISO.
win32compat.c
xbox_ref_bridge.h
xbox_ref_bridge.c
+ xbox_xgd1_raw_id_probe.c
xbox_ref/xbox_ref_log.c
)
fprintf (json, " \"pregame_padding_sector_count\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS);
fprintf (json, " \"game_output_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA);
fprintf (json, " \"game_leadin_sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS);
+ fprintf (json, " \"game_leadin_unlocked_source_start_lba\": %u,\n",
+ (meta -> game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ?
+ meta -> game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0);
+ fprintf (json, " \"game_leadin_source_policy\": \"drive_read10_with_per_sector_zero_fallback\",\n");
fprintf (json, " \"game_unlocked_source_start_lba\": %u,\n", meta -> game_source_lba);
fprintf (json, " \"game_unlocked_source_sector_count\": %u,\n", meta -> game_source_sectors);
fprintf (json, " \"postgame_padding_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS);
fprintf (json, " },\n");
fprintf (json, " \"lead_in_capture\": {\n");
fprintf (json, " \"game_leadin_attempted\": true,\n");
+ fprintf (json, " \"unlocked_source_start_lba\": %u,\n",
+ (meta -> game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ?
+ meta -> game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0);
+ fprintf (json, " \"sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS);
fprintf (json, " \"readable_sectors\": %u,\n", meta -> game_leadin_read_sectors);
- fprintf (json, " \"zero_filled_sectors\": %u\n", meta -> game_leadin_zero_sectors);
+ fprintf (json, " \"zero_filled_sectors\": %u,\n", meta -> game_leadin_zero_sectors);
+ fprintf (json, " \"physical_geometry_evidence\": \"friidump_0.5.3.13_cache_aligned_raw_id_probe\"\n");
fprintf (json, " },\n");
fprintf (json, " \"dvd_structures\": {\n");
fprintf (json, " \"pfi\": "); if (pfi_leaf) xbox_json_string (json, pfi_leaf); else fprintf (json, "null"); fprintf (json, ",\n");
fprintf (json, " },\n");
fprintf (json, " \"notes\": [\n");
fprintf (json, " \"PFI and DMI are stored as separate binary READ DVD STRUCTURE captures because they are not READ(10) user-data sectors.\",\n");
- fprintf (json, " \"Pregame and postgame padding are zero-filled unless a future workflow proves readable source sectors for those ranges.\"\n");
+ fprintf (json, " \"The 32-sector game lead-in is captured from unlocked source LBA 0..31; only unreadable lead-in sectors use zero-fill fallback.\",\n");
+ fprintf (json, " \"Pregame and postgame padding retain zero-filled placeholder content; their physical locations are resolved but their inaccessible bytes are not.\"\n");
fprintf (json, " ]\n");
fprintf (json, "}\n");
fclose (json);
if (game_source_lba != XBOX_XISO_STANDARD_GAME_LBA && disc_is_xbox_challenge_drive (dmp -> dsk))
warning ("Xbox XDVDFS was detected at LBA %u, not the expected GDR-8050L game-view LBA 32", game_source_lba);
- if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
- /* Match the original GDR-8050L dumper's option-1 redump path: the
- * 32-sector XISO/game lead-in area is synthetic zero-fill, and the real
- * XDVDFS data begins at unlocked source LBA 32. */
- xbox_ref_log_fprintf (stderr, "[XBOX] Writing 32-sector game lead-in as zero-fill for GDR-8050L redump flow.\n");
- meta.game_leadin_zero_sectors += XBOX_XISO_LEADIN_SECTORS;
- if (current_sector) *current_sector = XBOX_XGD1_GAME_OUTPUT_START_LBA;
- if (!xbox_write_zero_iso_range (dmp, XBOX_XISO_LEADIN_SECTORS, XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, "GAME-XISO-LEADIN"))
- goto cleanup;
- } else {
- if (!xbox_write_leadin_iso_range (dmp,
- (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
- XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, &meta, "redump game", true, current_sector))
- goto cleanup;
- }
+ /* FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that the
+ * unlocked GDR-8050L view exposes source LBA 0..31 as the physical
+ * game-region lead-in immediately preceding the XDVDFS header at LBA 32.
+ * Read those sectors from the drive; retain the existing per-sector
+ * zero-fill fallback only for genuinely unreadable sectors. */
+ xbox_ref_log_fprintf (stderr,
+ "[XBOX] Capturing 32-sector game lead-in from unlocked source LBA %u..%u.\n",
+ (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
+ (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - 1 : XBOX_XISO_LEADIN_SECTORS - 1);
+ if (!xbox_write_leadin_iso_range (dmp,
+ (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
+ XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, &meta, "redump game", true, current_sector))
+ goto cleanup;
if (!xbox_dump_read10_iso_range (dmp, game_source_lba, XBOX_XGD1_GAME_SOURCE_SECTORS,
XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XISO_LEADIN_SECTORS, meta.output_sectors, "GAME-XDVDFS", true, current_sector))
if (!payloadFilesPresent)
WRITE_WARNING("payload_files_not_fully_present");
if (redumpStyleZeroFilledPadding)
- WRITE_WARNING("redump_style_padding_zero_filled_pending_hardware_capture");
+ WRITE_WARNING("redump_style_padding_zero_filled_unresolved_content");
#undef WRITE_WARNING
fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
+ fprintf(json, " \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
+ fprintf(json, " \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
+ fprintf(json, " \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
fprintf(json, " \"reconstruction\": {\n");
fprintf(json, " \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false");
- fprintf(json, " \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "zero_fill_until_drive_can_read_filler" : "not_applicable"); fprintf(json, ",\n");
+ fprintf(json, " \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "pregame_and_postgame_zero_fill_content_placeholder" : "not_applicable"); fprintf(json, ",\n");
+ fprintf(json, " \"filler_geometry_verified\": %s,\n", redumpStyle ? "true" : "false");
fprintf(json, " \"filler_verified_from_disc\": false,\n");
fprintf(json, " \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null");
- fprintf(json, " \"pending_hardware_capture\": %s,\n", redumpStyle ? "true" : "false");
+ fprintf(json, " \"pending_hardware_capture\": false,\n");
+ fprintf(json, " \"unresolved_filler_content\": %s,\n", redumpStyle ? "true" : "false");
fprintf(json, " \"filler_ranges\": [\n");
if (redumpStyle)
{
fprintf(json, " }\n");
}
fprintf(json, " ],\n");
- fprintf(json, " \"note\": \"Padding/filler ranges are intentionally zero-filled placeholders until a drive/workflow capable of reading those regions is available.\"\n");
+ fprintf(json, " \"note\": \"Pregame and postgame physical locations are verified by cache-aligned raw sector IDs, but their inaccessible contents remain zero-filled placeholders. The 32-sector game lead-in is drive-captured from unlocked LBA 0..31.\"\n");
fprintf(json, " },\n");
fprintf(json, " \"dvd_structures\": {\n");
fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
+ fprintf(json, " \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
+ fprintf(json, " \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
+ fprintf(json, " \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba);
}
- // Build the Redump-style XISO/game region using the same proven
- // convention as option 2: the first 32 sectors are synthetic XISO
- // lead-in/padding, and the real XDFS volume begins at source LBA 32.
- // Do not read unlocked source LBA 0..31 here; on the 8050L path those
- // LBAs are not the XDFS header sectors we want in the rebuilt image.
+ // FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that
+ // unlocked source LBA 0..31 is the physical 32-sector game-region
+ // lead-in and that XDVDFS begins at source LBA 32. Preserve the
+ // drive-captured lead-in instead of synthesizing zero sectors.
gameSourceLba = XGD1_GAME_SOURCE_START_LBA;
gameSectors = REDUMP_SECTORS;
rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA;
rawGameSourceLba = gameSourceLba;
rawGameSectors = gameSectors;
- printf("[RAW] Writing %u-sector XISO lead-in/padding at output LBA %u.\n",
- XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA);
- if (!WriteZeroSectorsOutput(outFile, hHash, XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA, "GAME-XISO-LEADIN"))
+ printf("[RAW] Capturing %u-sector game lead-in from unlocked source LBA 0..%u at output LBA %u.\n",
+ XGD1_XISO_LEADIN_SECTORS,
+ XGD1_XISO_LEADIN_SECTORS - 1,
+ XGD1_GAME_OUTPUT_START_LBA);
+ if (!DumpSectorRangeWithRetry(hDevice,
+ outFile,
+ hHash,
+ 0,
+ XGD1_XISO_LEADIN_SECTORS,
+ XGD1_GAME_OUTPUT_START_LBA,
+ "GAME-XISO-LEADIN",
+ TRUE))
{
VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
goto cleanup;
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <winioctl.h>
+#include <ntddscsi.h>
#include <stdio.h>
+#include <stddef.h>
+#include <io.h>
#include "xbox_ref/utils.h"
+#include "xbox_ref/sha1.h"
#include "xbox_ref/unlock.h"
#include "xbox_ref/xbox_ref_log.h"
return sectors > XBOX_REF_LOCKED_VIDEO_VIEW_MAX_SECTORS;
}
+static BOOL xbox_ref_capacity_is_xgd1_locked_view(uint32_t sectors)
+{
+ return sectors == XGD1_VIDEO_TOTAL_SECTORS;
+}
+
+static BOOL xbox_ref_capacity_is_xgd1_game_view(uint32_t sectors)
+{
+ return sectors == REDUMP_SECTORS;
+}
+
static uint32_t xbox_ref_refresh_ready_capacity(HANDLE hDevice)
{
RefreshVolume(hDevice);
return device[0];
}
+#define XGD1_LAYOUT_PROBE_SCHEMA_VERSION 1
+#define XGD1_LAYOUT_PROBE_SECTOR_BYTES 2048U
+#define XGD1_LAYOUT_PROBE_MAX_SAMPLES 20U
+
+typedef struct xbox_ref_probe_read_s
+{
+ int read_ok;
+ DWORD win32_error;
+ UCHAR scsi_status;
+ UCHAR sense_key;
+ UCHAR asc;
+ UCHAR ascq;
+ uint32_t nonzero_bytes;
+ int all_zero;
+ char sha1[41];
+ unsigned char data[XGD1_LAYOUT_PROBE_SECTOR_BYTES];
+} xbox_ref_probe_read;
+
+typedef struct xbox_ref_probe_sample_s
+{
+ uint32_t lba;
+ const char *label;
+ xbox_ref_probe_read read10;
+ xbox_ref_probe_read read12;
+} xbox_ref_probe_sample;
+
+typedef struct xbox_ref_probe_state_s
+{
+ const char *name;
+ uint32_t capacity;
+ int capacity_valid;
+ int state_verified;
+ uint32_t sample_count;
+ xbox_ref_probe_sample samples[XGD1_LAYOUT_PROBE_MAX_SAMPLES];
+} xbox_ref_probe_state;
+
+typedef struct xbox_ref_probe_point_s
+{
+ uint32_t lba;
+ const char *label;
+} xbox_ref_probe_point;
+
+typedef struct xbox_ref_sptd_with_sense_s
+{
+ SCSI_PASS_THROUGH_DIRECT sptd;
+ ULONG filler;
+ UCHAR sense[32];
+} xbox_ref_sptd_with_sense;
+
+static const xbox_ref_probe_point xbox_ref_locked_probe_points[] = {
+ {0U, "locked_logical_start"},
+ {31U, "locked_early_lba_31"},
+ {32U, "locked_early_lba_32"},
+ {XGD1_VIDEO_L0_SECTORS - 1U, "video_l0_last"},
+ {XGD1_VIDEO_L0_SECTORS, "locked_video_l1_first"},
+ {XGD1_VIDEO_TOTAL_SECTORS - 1U, "locked_video_last"},
+ {XGD1_VIDEO_TOTAL_SECTORS, "locked_capacity_plus_one"},
+ {XGD1_GAME_OUTPUT_START_LBA - 1U, "pregame_output_last"},
+ {XGD1_GAME_OUTPUT_START_LBA, "game_output_start"},
+ {XGD1_REDUMP_LAYER_BREAK_LBA - 1U, "output_layer_break_minus_one"},
+ {XGD1_REDUMP_LAYER_BREAK_LBA, "output_layer_break"},
+ {XGD1_VIDEO_L1_OUTPUT_START_LBA - 1U, "postgame_output_last"},
+ {XGD1_VIDEO_L1_OUTPUT_START_LBA, "video_l1_output_start"},
+ {XGD1_FULL_REDUMP_SECTORS - 1U, "full_output_last"}
+};
+
+static const xbox_ref_probe_point xbox_ref_unlocked_probe_points[] = {
+ {0U, "unlocked_source_start"},
+ {XGD1_XISO_LEADIN_SECTORS - 1U, "unlocked_source_leadin_last"},
+ {XGD1_GAME_SOURCE_START_LBA, "xdfs_source_start"},
+ {XGD1_GAME_SOURCE_START_LBA + 1U, "xdfs_source_next"},
+ {(XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) - 1U, "synthetic_gap_length_minus_one"},
+ {XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS, "synthetic_gap_length"},
+ {XGD1_GAME_OUTPUT_START_LBA - 1U, "pregame_output_last_as_unlocked_source"},
+ {XGD1_GAME_OUTPUT_START_LBA, "game_output_start_as_unlocked_source"},
+ {(XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA) - 1U, "mapped_layer_break_source_minus_one"},
+ {XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA, "mapped_layer_break_source"},
+ {XGD1_GAME_SOURCE_START_LBA + XGD1_GAME_SOURCE_SECTORS - 1U, "unlocked_source_last"},
+ {REDUMP_SECTORS, "unlocked_capacity_plus_one"},
+ {XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "postgame_output_start_as_unlocked_source"},
+ {XGD1_VIDEO_L1_OUTPUT_START_LBA, "video_l1_output_start_as_unlocked_source"}
+};
+
+static void xbox_ref_probe_parse_sense(const UCHAR *sense,
+ UCHAR *sense_key,
+ UCHAR *asc,
+ UCHAR *ascq)
+{
+ UCHAR response;
+
+ if (sense_key) *sense_key = 0;
+ if (asc) *asc = 0;
+ if (ascq) *ascq = 0;
+ if (!sense)
+ return;
+
+ response = (UCHAR)(sense[0] & 0x7f);
+ if (response == 0x70 || response == 0x71)
+ {
+ if (sense_key) *sense_key = (UCHAR)(sense[2] & 0x0f);
+ if (asc) *asc = sense[12];
+ if (ascq) *ascq = sense[13];
+ }
+ else if (response == 0x72 || response == 0x73)
+ {
+ if (sense_key) *sense_key = (UCHAR)(sense[1] & 0x0f);
+ if (asc) *asc = sense[2];
+ if (ascq) *ascq = sense[3];
+ }
+}
+
+static void xbox_ref_probe_sha1(const unsigned char *data,
+ uint32_t data_size,
+ char out_sha1[41])
+{
+ SHA1_CTX ctx;
+ unsigned char digest[20];
+ uint32_t i;
+
+ if (!out_sha1)
+ return;
+ out_sha1[0] = '\0';
+ if (!data || data_size == 0)
+ return;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, data, data_size);
+ SHA1_Final(digest, &ctx);
+
+ for (i = 0; i < 20U; ++i)
+ sprintf(out_sha1 + (i * 2U), "%02x", digest[i]);
+ out_sha1[40] = '\0';
+}
+
+static void xbox_ref_probe_read_one(HANDLE hDevice,
+ UCHAR opcode,
+ uint32_t lba,
+ xbox_ref_probe_read *result)
+{
+ xbox_ref_sptd_with_sense packet;
+ unsigned char *buffer;
+ DWORD bytes_returned;
+ BOOL ioctl_ok;
+ uint32_t i;
+
+ if (!result)
+ return;
+ memset(result, 0, sizeof(*result));
+
+ buffer = (unsigned char *)VirtualAlloc(NULL,
+ XGD1_LAYOUT_PROBE_SECTOR_BYTES,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE);
+ if (!buffer)
+ {
+ result->win32_error = ERROR_NOT_ENOUGH_MEMORY;
+ return;
+ }
+
+ memset(buffer, 0, XGD1_LAYOUT_PROBE_SECTOR_BYTES);
+ memset(&packet, 0, sizeof(packet));
+
+ packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ packet.sptd.CdbLength = (opcode == 0xA8) ? 12 : 10;
+ packet.sptd.SenseInfoLength = sizeof(packet.sense);
+ packet.sptd.DataIn = SCSI_IOCTL_DATA_IN;
+ packet.sptd.DataTransferLength = XGD1_LAYOUT_PROBE_SECTOR_BYTES;
+ packet.sptd.TimeOutValue = 10;
+ packet.sptd.DataBuffer = buffer;
+ packet.sptd.SenseInfoOffset = offsetof(xbox_ref_sptd_with_sense, sense);
+ packet.sptd.Cdb[0] = opcode;
+ packet.sptd.Cdb[2] = (UCHAR)((lba >> 24) & 0xff);
+ packet.sptd.Cdb[3] = (UCHAR)((lba >> 16) & 0xff);
+ packet.sptd.Cdb[4] = (UCHAR)((lba >> 8) & 0xff);
+ packet.sptd.Cdb[5] = (UCHAR)(lba & 0xff);
+
+ if (opcode == 0xA8)
+ {
+ packet.sptd.Cdb[9] = 1;
+ }
+ else
+ {
+ packet.sptd.Cdb[8] = 1;
+ }
+
+ bytes_returned = 0;
+ ioctl_ok = DeviceIoControl(hDevice,
+ IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &packet,
+ sizeof(packet),
+ &packet,
+ sizeof(packet),
+ &bytes_returned,
+ NULL);
+
+ result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
+ result->scsi_status = packet.sptd.ScsiStatus;
+ xbox_ref_probe_parse_sense(packet.sense,
+ &result->sense_key,
+ &result->asc,
+ &result->ascq);
+ result->read_ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
+
+ if (result->read_ok)
+ {
+ memcpy(result->data, buffer, XGD1_LAYOUT_PROBE_SECTOR_BYTES);
+ result->nonzero_bytes = 0;
+ for (i = 0; i < XGD1_LAYOUT_PROBE_SECTOR_BYTES; ++i)
+ {
+ if (buffer[i] != 0)
+ result->nonzero_bytes++;
+ }
+ result->all_zero = (result->nonzero_bytes == 0U) ? 1 : 0;
+ xbox_ref_probe_sha1(buffer,
+ XGD1_LAYOUT_PROBE_SECTOR_BYTES,
+ result->sha1);
+ }
+
+ VirtualFree(buffer, 0, MEM_RELEASE);
+}
+
+static void xbox_ref_probe_capture_state(HANDLE hDevice,
+ xbox_ref_probe_state *state,
+ const xbox_ref_probe_point *points,
+ uint32_t point_count)
+{
+ uint32_t i;
+
+ if (!state || !points)
+ return;
+
+ if (point_count > XGD1_LAYOUT_PROBE_MAX_SAMPLES)
+ point_count = XGD1_LAYOUT_PROBE_MAX_SAMPLES;
+
+ state->sample_count = point_count;
+ for (i = 0; i < point_count; ++i)
+ {
+ state->samples[i].lba = points[i].lba;
+ state->samples[i].label = points[i].label;
+
+ printf("[XGD1-PROBE] %s: probing LBA %u (%s) with READ(10) and READ(12).\n",
+ state->name ? state->name : "state",
+ points[i].lba,
+ points[i].label ? points[i].label : "unlabeled");
+
+ xbox_ref_probe_read_one(hDevice,
+ 0x28,
+ points[i].lba,
+ &state->samples[i].read10);
+ xbox_ref_probe_read_one(hDevice,
+ 0xA8,
+ points[i].lba,
+ &state->samples[i].read12);
+ }
+}
+
+static void xbox_ref_probe_json_string(FILE *json, const char *value)
+{
+ const unsigned char *p;
+
+ if (!json)
+ return;
+ if (!value)
+ value = "";
+
+ fputc('"', json);
+ p = (const unsigned char *)value;
+ while (*p)
+ {
+ switch (*p)
+ {
+ case '\\': fputs("\\\\", json); break;
+ case '"': fputs("\\\"", json); break;
+ case '\b': fputs("\\b", json); break;
+ case '\f': fputs("\\f", json); break;
+ case '\n': fputs("\\n", json); break;
+ case '\r': fputs("\\r", json); break;
+ case '\t': fputs("\\t", json); break;
+ default:
+ if (*p < 0x20)
+ fprintf(json, "\\u%04x", (unsigned int)*p);
+ else
+ fputc(*p, json);
+ break;
+ }
+ ++p;
+ }
+ fputc('"', json);
+}
+
+static void xbox_ref_probe_json_hex(FILE *json,
+ const unsigned char *data,
+ uint32_t data_size)
+{
+ uint32_t i;
+
+ if (!json)
+ return;
+ fputc('"', json);
+ if (data)
+ {
+ for (i = 0; i < data_size; ++i)
+ fprintf(json, "%02x", data[i]);
+ }
+ fputc('"', json);
+}
+
+static void xbox_ref_probe_write_cdb(FILE *json,
+ UCHAR opcode,
+ uint32_t lba)
+{
+ if (!json)
+ return;
+
+ if (opcode == 0xA8)
+ {
+ fprintf(json,
+ "\"a800%08x000000010000\"",
+ lba);
+ }
+ else
+ {
+ fprintf(json,
+ "\"2800%08x00000100\"",
+ lba);
+ }
+}
+
+static void xbox_ref_probe_write_read(FILE *json,
+ const char *indent,
+ const char *name,
+ const xbox_ref_probe_read *read)
+{
+ if (!json || !read)
+ return;
+
+ fprintf(json, "%s", indent);
+ xbox_ref_probe_json_string(json, name);
+ fprintf(json, ": {\n");
+ fprintf(json, "%s \"read_ok\": %s,\n", indent, read->read_ok ? "true" : "false");
+ fprintf(json, "%s \"win32_error\": %lu,\n", indent, (unsigned long)read->win32_error);
+ fprintf(json, "%s \"scsi_status\": %u,\n", indent, (unsigned int)read->scsi_status);
+ fprintf(json, "%s \"sense_key\": %u,\n", indent, (unsigned int)read->sense_key);
+ fprintf(json, "%s \"asc\": %u,\n", indent, (unsigned int)read->asc);
+ fprintf(json, "%s \"ascq\": %u,\n", indent, (unsigned int)read->ascq);
+ if (read->read_ok)
+ {
+ fprintf(json, "%s \"nonzero_bytes\": %u,\n", indent, read->nonzero_bytes);
+ fprintf(json, "%s \"all_zero\": %s,\n", indent, read->all_zero ? "true" : "false");
+ }
+ else
+ {
+ fprintf(json, "%s \"nonzero_bytes\": null,\n", indent);
+ fprintf(json, "%s \"all_zero\": null,\n", indent);
+ }
+ fprintf(json, "%s \"sha1\": ", indent);
+ if (read->read_ok)
+ xbox_ref_probe_json_string(json, read->sha1);
+ else
+ fprintf(json, "null");
+ fprintf(json, ",\n");
+ fprintf(json, "%s \"data_hex\": ", indent);
+ if (read->read_ok)
+ xbox_ref_probe_json_hex(json, read->data, XGD1_LAYOUT_PROBE_SECTOR_BYTES);
+ else
+ xbox_ref_probe_json_string(json, "");
+ fprintf(json, "\n%s}", indent);
+}
+
+static void xbox_ref_probe_write_state(FILE *json,
+ const xbox_ref_probe_state *state,
+ int trailing_comma)
+{
+ uint32_t i;
+
+ fprintf(json, " {\n");
+ fprintf(json, " \"name\": ");
+ xbox_ref_probe_json_string(json, state->name);
+ fprintf(json, ",\n");
+ fprintf(json, " \"capacity_valid\": %s,\n", state->capacity_valid ? "true" : "false");
+ fprintf(json, " \"capacity_sectors\": %u,\n", state->capacity);
+ fprintf(json, " \"state_verified\": %s,\n", state->state_verified ? "true" : "false");
+ fprintf(json, " \"samples\": [\n");
+
+ for (i = 0; i < state->sample_count; ++i)
+ {
+ const xbox_ref_probe_sample *sample = &state->samples[i];
+ int identical = 0;
+
+ if (sample->read10.read_ok && sample->read12.read_ok)
+ {
+ identical = (memcmp(sample->read10.data,
+ sample->read12.data,
+ XGD1_LAYOUT_PROBE_SECTOR_BYTES) == 0) ? 1 : 0;
+ }
+
+ fprintf(json, " {\n");
+ fprintf(json, " \"lba\": %u,\n", sample->lba);
+ fprintf(json, " \"label\": ");
+ xbox_ref_probe_json_string(json, sample->label);
+ fprintf(json, ",\n");
+ fprintf(json, " \"read10_cdb_hex\": ");
+ xbox_ref_probe_write_cdb(json, 0x28, sample->lba);
+ fprintf(json, ",\n");
+ fprintf(json, " \"read12_cdb_hex\": ");
+ xbox_ref_probe_write_cdb(json, 0xA8, sample->lba);
+ fprintf(json, ",\n");
+ xbox_ref_probe_write_read(json, " ", "read10", &sample->read10);
+ fprintf(json, ",\n");
+ xbox_ref_probe_write_read(json, " ", "read12", &sample->read12);
+ fprintf(json, ",\n");
+ fprintf(json,
+ " \"read10_read12_comparable\": %s,\n",
+ (sample->read10.read_ok && sample->read12.read_ok) ? "true" : "false");
+ fprintf(json, " \"read10_read12_identical\": ");
+ if (sample->read10.read_ok && sample->read12.read_ok)
+ fprintf(json, "%s\n", identical ? "true" : "false");
+ else
+ fprintf(json, "null\n");
+ fprintf(json, " }%s\n", (i + 1U < state->sample_count) ? "," : "");
+ }
+
+ fprintf(json, " ]\n");
+ fprintf(json, " }%s\n", trailing_comma ? "," : "");
+}
+
+static BOOL xbox_ref_probe_write_report(const char *report_path,
+ const char *device,
+ uint32_t entry_capacity,
+ int entry_game_view,
+ int volume_lock_acquired,
+ const xbox_ref_probe_state *locked_state,
+ const xbox_ref_probe_state *unlocked_state,
+ int restore_attempted,
+ uint32_t restored_capacity,
+ int restore_verified)
+{
+ char tmp_path[XBOX_REF_RESULT_PATH_MAX];
+ const char *final_path;
+ FILE *json;
+ int fd;
+ BOOL moved;
+
+ final_path = (report_path && report_path[0]) ? report_path : "xgd1_layout_probe.json";
+ {
+ int path_length = snprintf(tmp_path,
+ sizeof(tmp_path),
+ "%s.tmp.%lu",
+ final_path,
+ (unsigned long)GetCurrentProcessId());
+ if (path_length < 0 || (size_t)path_length >= sizeof(tmp_path))
+ {
+ printf("[XGD1-PROBE] Fatal: report path is too long.\n");
+ return FALSE;
+ }
+ }
+
+ json = fopen(tmp_path, "wb");
+ if (!json)
+ {
+ printf("[XGD1-PROBE] Fatal: could not create temporary report %s.\n", tmp_path);
+ return FALSE;
+ }
+
+ fprintf(json, "{\n");
+ fprintf(json, " \"schema_version\": %d,\n", XGD1_LAYOUT_PROBE_SCHEMA_VERSION);
+ fprintf(json, " \"producer\": \"friidump-0.5.3.11\",\n");
+ fprintf(json, " \"probe\": \"original_xbox_xgd1_logical_layout_boundary\",\n");
+ fprintf(json, " \"device\": ");
+ xbox_ref_probe_json_string(json, device ? device : "");
+ fprintf(json, ",\n");
+ fprintf(json, " \"safety\": {\n");
+ fprintf(json, " \"read_only_sector_commands\": true,\n");
+ fprintf(json, " \"authentication_handshake_used\": true,\n");
+ fprintf(json, " \"tray_cycle_used\": true,\n");
+ fprintf(json, " \"firmware_write_used\": false,\n");
+ fprintf(json, " \"flash_command_used\": false\n");
+ fprintf(json, " },\n");
+ fprintf(json, " \"constants\": {\n");
+ fprintf(json, " \"full_output_sectors\": %u,\n", XGD1_FULL_REDUMP_SECTORS);
+ fprintf(json, " \"game_output_start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA);
+ fprintf(json, " \"game_view_sectors\": %u,\n", REDUMP_SECTORS);
+ fprintf(json, " \"video_total_sectors\": %u,\n", XGD1_VIDEO_TOTAL_SECTORS);
+ fprintf(json, " \"video_l0_sectors\": %u,\n", XGD1_VIDEO_L0_SECTORS);
+ fprintf(json, " \"video_l1_sectors\": %u,\n", XGD1_VIDEO_L1_SECTORS);
+ fprintf(json, " \"video_l1_output_start_lba\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA);
+ fprintf(json, " \"output_layer_break_lba\": %u\n", XGD1_REDUMP_LAYER_BREAK_LBA);
+ fprintf(json, " },\n");
+ fprintf(json, " \"entry\": {\n");
+ fprintf(json, " \"capacity_sectors\": %u,\n", entry_capacity);
+ fprintf(json, " \"game_view\": %s,\n", entry_game_view ? "true" : "false");
+ fprintf(json, " \"classification\": ");
+ if (xbox_ref_capacity_is_xgd1_game_view(entry_capacity))
+ xbox_ref_probe_json_string(json, "unlocked_game");
+ else if (xbox_ref_capacity_is_xgd1_locked_view(entry_capacity))
+ xbox_ref_probe_json_string(json, "locked_video");
+ else
+ xbox_ref_probe_json_string(json, "other_or_unknown");
+ fprintf(json, "\n");
+ fprintf(json, " },\n");
+ fprintf(json, " \"volume_lock_acquired_at_least_once\": %s,\n", volume_lock_acquired ? "true" : "false");
+ fprintf(json,
+ " \"required_states_verified\": %s,\n",
+ (locked_state->state_verified && unlocked_state->state_verified) ? "true" : "false");
+ fprintf(json, " \"states\": [\n");
+ xbox_ref_probe_write_state(json, locked_state, 1);
+ xbox_ref_probe_write_state(json, unlocked_state, 0);
+ fprintf(json, " ],\n");
+ fprintf(json, " \"restoration\": {\n");
+ fprintf(json, " \"attempted\": %s,\n", restore_attempted ? "true" : "false");
+ fprintf(json, " \"capacity_sectors\": %u,\n", restored_capacity);
+ fprintf(json, " \"verified\": %s\n", restore_verified ? "true" : "false");
+ fprintf(json, " },\n");
+ fprintf(json, " \"interpretation_boundary\": {\n");
+ fprintf(json, " \"pregame_padding_resolved\": false,\n");
+ fprintf(json, " \"postgame_padding_resolved\": false,\n");
+ fprintf(json, " \"game_leadin_resolved\": false,\n");
+ fprintf(json, " \"note\": \"This report records ordinary logical READ(10)/READ(12) behavior in locked/video and unlocked/game states. It does not claim that inaccessible physical filler sectors are zero or that the current reconstructed image is Redump-exact.\"\n");
+ fprintf(json, " }\n");
+ fprintf(json, "}\n");
+
+ if (fflush(json) != 0)
+ {
+ fclose(json);
+ DeleteFileA(tmp_path);
+ return FALSE;
+ }
+
+ fd = _fileno(json);
+ if (fd < 0 || _commit(fd) != 0)
+ {
+ fclose(json);
+ DeleteFileA(tmp_path);
+ return FALSE;
+ }
+
+ if (fclose(json) != 0)
+ {
+ DeleteFileA(tmp_path);
+ return FALSE;
+ }
+
+ moved = MoveFileExA(tmp_path,
+ final_path,
+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
+ if (!moved)
+ {
+ printf("[XGD1-PROBE] Fatal: could not atomically publish report %s (error %lu).\n",
+ final_path,
+ (unsigned long)GetLastError());
+ DeleteFileA(tmp_path);
+ return FALSE;
+ }
+
+ printf("[XGD1-PROBE] Atomic report written: %s\n", final_path);
+ return TRUE;
+}
+
+static int xbox_ref_xgd1_layout_probe_core(HANDLE hDevice,
+ const char *device,
+ const char *report_path,
+ BOOL owns_handle)
+{
+ xbox_ref_probe_state locked_state;
+ xbox_ref_probe_state unlocked_state;
+ uint32_t entry_capacity;
+ uint32_t restored_capacity;
+ int entry_game_view;
+ int volume_lock_acquired;
+ int volume_lock_ever;
+ int restore_attempted;
+ int restore_verified;
+ int states_verified;
+ DWORD bytes_returned;
+ BOOL report_ok;
+
+ memset(&locked_state, 0, sizeof(locked_state));
+ memset(&unlocked_state, 0, sizeof(unlocked_state));
+ locked_state.name = "locked_video";
+ unlocked_state.name = "unlocked_game";
+ entry_capacity = 0;
+ restored_capacity = 0;
+ entry_game_view = 0;
+ volume_lock_acquired = 0;
+ volume_lock_ever = 0;
+ restore_attempted = 0;
+ restore_verified = 0;
+ states_verified = 0;
+ bytes_returned = 0;
+
+ xbox_ref_log_open_for_target(report_path, xbox_ref_drive_letter_from_device(device));
+ printf("[XGD1-PROBE] Starting read-only logical layout boundary probe.\n");
+ printf("[XGD1-PROBE] The existing XGD1 output layout will not be modified.\n");
+
+ if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
+ {
+ printf("[XGD1-PROBE] Fatal: invalid drive handle.\n");
+ if (owns_handle)
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ if (!EnsureDriveReady(hDevice, 30000))
+ {
+ printf("[XGD1-PROBE] Fatal: media did not become ready.\n");
+ if (owns_handle)
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ entry_capacity = GetTotalSectors(hDevice);
+ entry_game_view = xbox_ref_capacity_is_xgd1_game_view(entry_capacity) ? 1 : 0;
+ printf("[XGD1-PROBE] Entry READ CAPACITY: %u sectors (%s).\n",
+ entry_capacity,
+ entry_game_view ? "game view" : "locked/video or unknown view");
+
+ DeviceIoControl(hDevice,
+ FSCTL_UNLOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL);
+
+ printf("[XGD1-PROBE] Cycling tray to establish locked/video view.\n");
+ AutomateTrayCycle(hDevice);
+ locked_state.capacity = xbox_ref_refresh_ready_capacity(hDevice);
+ locked_state.capacity_valid = locked_state.capacity != 0U;
+ locked_state.state_verified = (locked_state.capacity_valid &&
+ xbox_ref_capacity_is_xgd1_locked_view(locked_state.capacity)) ? 1 : 0;
+ printf("[XGD1-PROBE] Locked/video READ CAPACITY: %u sectors; verified=%s.\n",
+ locked_state.capacity,
+ locked_state.state_verified ? "yes" : "no");
+
+ if (DeviceIoControl(hDevice,
+ FSCTL_LOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL))
+ {
+ volume_lock_acquired = 1;
+ volume_lock_ever = 1;
+ printf("[XGD1-PROBE] Windows volume lock acquired.\n");
+ }
+ else
+ {
+ printf("[XGD1-PROBE][WARN] Windows volume lock was not acquired; close AutoPlay and Explorer dialogs if results are unstable.\n");
+ }
+
+ SetDriveSpeedMax(hDevice);
+ xbox_ref_probe_capture_state(hDevice,
+ &locked_state,
+ xbox_ref_locked_probe_points,
+ (uint32_t)(sizeof(xbox_ref_locked_probe_points) /
+ sizeof(xbox_ref_locked_probe_points[0])));
+
+ printf("[XGD1-PROBE] Applying the full Xbox handshake to establish game view.\n");
+ UnlockDrive(hDevice);
+ unlocked_state.capacity = xbox_ref_refresh_ready_capacity(hDevice);
+ unlocked_state.capacity_valid = unlocked_state.capacity != 0U;
+ unlocked_state.state_verified = (unlocked_state.capacity_valid &&
+ xbox_ref_capacity_is_xgd1_game_view(unlocked_state.capacity)) ? 1 : 0;
+ printf("[XGD1-PROBE] Unlocked/game READ CAPACITY: %u sectors; verified=%s.\n",
+ unlocked_state.capacity,
+ unlocked_state.state_verified ? "yes" : "no");
+
+ xbox_ref_probe_capture_state(hDevice,
+ &unlocked_state,
+ xbox_ref_unlocked_probe_points,
+ (uint32_t)(sizeof(xbox_ref_unlocked_probe_points) /
+ sizeof(xbox_ref_unlocked_probe_points[0])));
+
+ if (volume_lock_acquired)
+ {
+ DeviceIoControl(hDevice,
+ FSCTL_UNLOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL);
+ volume_lock_acquired = 0;
+ }
+
+ if (!entry_game_view && entry_capacity != 0U)
+ {
+ restore_attempted = 1;
+ printf("[XGD1-PROBE] Restoring the entry locked/video state with a final tray cycle.\n");
+ AutomateTrayCycle(hDevice);
+ restored_capacity = xbox_ref_refresh_ready_capacity(hDevice);
+ restore_verified = (restored_capacity != 0U &&
+ xbox_ref_capacity_is_xgd1_locked_view(restored_capacity)) ? 1 : 0;
+ }
+ else
+ {
+ restored_capacity = GetTotalSectors(hDevice);
+ restore_verified = (entry_game_view &&
+ xbox_ref_capacity_is_xgd1_game_view(restored_capacity)) ? 1 : 0;
+ }
+
+ states_verified = (locked_state.state_verified &&
+ unlocked_state.state_verified) ? 1 : 0;
+
+ report_ok = xbox_ref_probe_write_report(report_path,
+ device,
+ entry_capacity,
+ entry_game_view,
+ volume_lock_ever,
+ &locked_state,
+ &unlocked_state,
+ restore_attempted,
+ restored_capacity,
+ restore_verified);
+
+ if (owns_handle)
+ {
+ printf("[XGD1-PROBE] Issuing STOP UNIT / spin-down after probe... ");
+ printf("%s\n", StopDriveUnit(hDevice) ? "OK" : "FAILED");
+ CloseDrive(hDevice);
+ xbox_ref_log_close();
+ }
+
+ if (!report_ok)
+ return 1;
+ if (!states_verified)
+ {
+ printf("[XGD1-PROBE] Probe report is partial because one or more drive states were not verified.\n");
+ return 1;
+ }
+
+ printf("[XGD1-PROBE] Probe complete. Synthetic ranges remain unresolved until the captured evidence supports a source mapping.\n");
+ return 0;
+}
+
+
static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const char *filename, char mode, BOOL ownsHandle, xbox_ref_dump_result *result)
{
char driveLetter;
return ok ? 0 : 1;
}
+int xbox_ref_xgd1_layout_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path)
+{
+ return xbox_ref_xgd1_layout_probe_core((HANDLE)native_handle,
+ device,
+ report_path,
+ FALSE);
+}
+
+int xbox_ref_xgd1_layout_probe(const char *device,
+ const char *report_path)
+{
+ char drive_letter;
+ HANDLE hDevice;
+
+ drive_letter = xbox_ref_drive_letter_from_device(device);
+ if (!drive_letter)
+ {
+ xbox_ref_log_open_for_target(report_path, 0);
+ printf("[XGD1-PROBE] Fatal: could not parse drive letter from device '%s'.\n",
+ device ? device : "(null)");
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ hDevice = OpenDrive(drive_letter);
+ if (hDevice == INVALID_HANDLE_VALUE)
+ {
+ xbox_ref_log_open_for_target(report_path, drive_letter);
+ printf("[XGD1-PROBE] Fatal: cannot open drive %c:. Run as Administrator.\n",
+ drive_letter);
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ return xbox_ref_xgd1_layout_probe_core(hDevice,
+ device,
+ report_path,
+ TRUE);
+}
+
int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
{
return xbox_ref_gdr8050l_dump_core((HANDLE)native_handle, device, filename, mode, FALSE, result);
return xbox_ref_gdr8050l_dump_core(hDevice, device, filename, mode, TRUE, result);
}
#else
+int xbox_ref_xgd1_layout_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path)
+{
+ (void)native_handle;
+ (void)device;
+ (void)report_path;
+ return 1;
+}
+
+int xbox_ref_xgd1_layout_probe(const char *device,
+ const char *report_path)
+{
+ (void)device;
+ (void)report_path;
+ return 1;
+}
+
int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
{
(void)native_handle; (void)device; (void)filename; (void)mode; xbox_ref_dump_result_init(result); return 1;
const char *filename,
char mode,
xbox_ref_dump_result *result);
+int xbox_ref_xgd1_layout_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path);
+int xbox_ref_xgd1_layout_probe(const char *device,
+ const char *report_path);
+int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path);
+int xbox_ref_xgd1_raw_id_probe(const char *device,
+ const char *report_path);
#ifdef __cplusplus
}
--- /dev/null
+#include "xbox_ref_bridge.h"
+
+#include <string.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#include <winioctl.h>
+#include <ntddscsi.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <io.h>
+
+#ifndef SCSI_IOCTL_DATA_UNSPECIFIED
+#define SCSI_IOCTL_DATA_UNSPECIFIED 2
+#endif
+
+#include "xbox_ref/utils.h"
+#include "xbox_ref/sha1.h"
+#include "xbox_ref/unlock.h"
+#include "xbox_ref/xbox_ref_log.h"
+
+#define printf xbox_ref_printf
+
+#define XGD1_RAW_ID_PROBE_SCHEMA_VERSION 2
+#define XGD1_RAW_ID_CACHE_BASE 0x80000000U
+#define XGD1_RAW_SECTOR_BYTES 2064U
+#define XGD1_LOGICAL_SECTOR_BYTES 2048U
+#define XGD1_CACHE_FILL_SECTORS 16U
+#define XGD1_RAW_ID_MAX_SAMPLES 16U
+
+typedef struct xgd1_sptd_with_sense_s
+{
+ SCSI_PASS_THROUGH_DIRECT sptd;
+ ULONG filler;
+ UCHAR sense[32];
+} xgd1_sptd_with_sense;
+
+typedef struct xgd1_cmd_result_s
+{
+ int ok;
+ DWORD win32_error;
+ UCHAR scsi_status;
+ UCHAR sense_key;
+ UCHAR asc;
+ UCHAR ascq;
+} xgd1_cmd_result;
+
+typedef struct xgd1_raw_id_sample_s
+{
+ uint32_t lba;
+ const char *label;
+ uint32_t cache_fill_lba;
+ uint32_t cache_index;
+ uint32_t cache_address;
+ xgd1_cmd_result cache_flush;
+ xgd1_cmd_result cache_fill;
+ xgd1_cmd_result memdump_header;
+ xgd1_cmd_result memdump_edc;
+ int raw_header_valid;
+ UCHAR sector_information;
+ uint32_t layer_number;
+ uint32_t physical_sector_number;
+ uint32_t normalized_physical_sector_number;
+ uint32_t output_lba;
+ uint32_t expected_layer_number;
+ uint32_t expected_physical_sector_number;
+ uint32_t expected_normalized_physical_sector_number;
+ int layer_matches_expected;
+ int psn_matches_expected;
+ int geometry_matches_expected;
+ char logical_sha1[41];
+ unsigned char logical[XGD1_LOGICAL_SECTOR_BYTES];
+ unsigned char raw_header[12];
+ unsigned char raw_edc[4];
+} xgd1_raw_id_sample;
+
+typedef struct xgd1_raw_id_state_s
+{
+ const char *name;
+ int locked_view;
+ uint32_t capacity;
+ int capacity_valid;
+ int state_verified;
+ uint32_t sample_count;
+ uint32_t geometry_validation_failures;
+ int geometry_verified;
+ xgd1_raw_id_sample samples[XGD1_RAW_ID_MAX_SAMPLES];
+} xgd1_raw_id_state;
+
+typedef struct xgd1_probe_point_s
+{
+ uint32_t lba;
+ const char *label;
+} xgd1_probe_point;
+
+static const xgd1_probe_point xgd1_locked_points[] = {
+ {0U, "locked_logical_start"},
+ {31U, "locked_early_lba_31"},
+ {32U, "locked_early_lba_32"},
+ {XGD1_VIDEO_L0_SECTORS - 1U, "video_l0_last"},
+ {XGD1_VIDEO_L0_SECTORS, "video_l1_first"},
+ {XGD1_VIDEO_TOTAL_SECTORS - 1U, "locked_video_last"}
+};
+
+static const xgd1_probe_point xgd1_unlocked_points[] = {
+ {0U, "unlocked_source_start"},
+ {XGD1_XISO_LEADIN_SECTORS - 1U, "unlocked_leadin_last"},
+ {XGD1_GAME_SOURCE_START_LBA, "xdfs_source_start"},
+ {XGD1_GAME_SOURCE_START_LBA + 1U, "xdfs_source_next"},
+ {(XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA) - 1U, "mapped_layer_break_source_minus_one"},
+ {XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA, "mapped_layer_break_source"},
+ {REDUMP_SECTORS - 1U, "unlocked_source_last"}
+};
+
+static char xgd1_drive_letter_from_device(const char *device)
+{
+ if (!device || !device[0]) return 0;
+ if (device[0] && device[1] == ':') return device[0];
+ if (device[0] == '\\' && device[1] == '\\' &&
+ device[2] == '.' && device[3] == '\\' &&
+ device[4] && device[5] == ':')
+ return device[4];
+ return device[0];
+}
+
+static int xgd1_capacity_is_locked(uint32_t sectors)
+{
+ return sectors == XGD1_VIDEO_TOTAL_SECTORS;
+}
+
+static int xgd1_capacity_is_game(uint32_t sectors)
+{
+ return sectors == REDUMP_SECTORS;
+}
+
+static uint32_t xgd1_refresh_ready_capacity(HANDLE hDevice)
+{
+ RefreshVolume(hDevice);
+ Sleep(2000);
+ EnsureDriveReady(hDevice, 30000);
+ return GetTotalSectors(hDevice);
+}
+
+static void xgd1_parse_sense(const UCHAR *sense,
+ UCHAR *sense_key,
+ UCHAR *asc,
+ UCHAR *ascq)
+{
+ UCHAR response;
+
+ if (sense_key) *sense_key = 0;
+ if (asc) *asc = 0;
+ if (ascq) *ascq = 0;
+ if (!sense)
+ return;
+
+ response = (UCHAR)(sense[0] & 0x7f);
+ if (response == 0x70 || response == 0x71)
+ {
+ if (sense_key) *sense_key = (UCHAR)(sense[2] & 0x0f);
+ if (asc) *asc = sense[12];
+ if (ascq) *ascq = sense[13];
+ }
+ else if (response == 0x72 || response == 0x73)
+ {
+ if (sense_key) *sense_key = (UCHAR)(sense[1] & 0x0f);
+ if (asc) *asc = sense[2];
+ if (ascq) *ascq = sense[3];
+ }
+}
+
+static void xgd1_sha1(const unsigned char *data,
+ uint32_t data_size,
+ char out_sha1[41])
+{
+ SHA1_CTX ctx;
+ unsigned char digest[20];
+ uint32_t i;
+
+ if (!out_sha1)
+ return;
+ out_sha1[0] = '\0';
+ if (!data || data_size == 0)
+ return;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, data, data_size);
+ SHA1_Final(digest, &ctx);
+
+ for (i = 0; i < 20U; ++i)
+ sprintf(out_sha1 + (i * 2U), "%02x", digest[i]);
+ out_sha1[40] = '\0';
+}
+
+static void xgd1_cmd_result_init(xgd1_cmd_result *result)
+{
+ if (result)
+ memset(result, 0, sizeof(*result));
+}
+
+static void xgd1_execute_data_in(HANDLE hDevice,
+ const UCHAR *cdb,
+ UCHAR cdb_length,
+ unsigned char *buffer,
+ DWORD buffer_length,
+ DWORD timeout_seconds,
+ xgd1_cmd_result *result)
+{
+ xgd1_sptd_with_sense packet;
+ DWORD bytes_returned;
+ BOOL ioctl_ok;
+
+ xgd1_cmd_result_init(result);
+ if (!result || !cdb || !buffer || buffer_length == 0U)
+ return;
+
+ memset(buffer, 0, buffer_length);
+ memset(&packet, 0, sizeof(packet));
+
+ packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ packet.sptd.CdbLength = cdb_length;
+ packet.sptd.SenseInfoLength = sizeof(packet.sense);
+ packet.sptd.DataIn = SCSI_IOCTL_DATA_IN;
+ packet.sptd.DataTransferLength = buffer_length;
+ packet.sptd.TimeOutValue = timeout_seconds;
+ packet.sptd.DataBuffer = buffer;
+ packet.sptd.SenseInfoOffset = offsetof(xgd1_sptd_with_sense, sense);
+ memcpy(packet.sptd.Cdb, cdb, cdb_length);
+
+ bytes_returned = 0;
+ ioctl_ok = DeviceIoControl(hDevice,
+ IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &packet,
+ sizeof(packet),
+ &packet,
+ sizeof(packet),
+ &bytes_returned,
+ NULL);
+
+ result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
+ result->scsi_status = packet.sptd.ScsiStatus;
+ xgd1_parse_sense(packet.sense,
+ &result->sense_key,
+ &result->asc,
+ &result->ascq);
+ result->ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
+}
+
+static void xgd1_execute_no_data(HANDLE hDevice,
+ const UCHAR *cdb,
+ UCHAR cdb_length,
+ DWORD timeout_seconds,
+ xgd1_cmd_result *result)
+{
+ xgd1_sptd_with_sense packet;
+ DWORD bytes_returned;
+ BOOL ioctl_ok;
+
+ xgd1_cmd_result_init(result);
+ if (!result || !cdb)
+ return;
+
+ memset(&packet, 0, sizeof(packet));
+ packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ packet.sptd.CdbLength = cdb_length;
+ packet.sptd.SenseInfoLength = sizeof(packet.sense);
+ packet.sptd.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ packet.sptd.DataTransferLength = 0;
+ packet.sptd.TimeOutValue = timeout_seconds;
+ packet.sptd.DataBuffer = NULL;
+ packet.sptd.SenseInfoOffset = offsetof(xgd1_sptd_with_sense, sense);
+ memcpy(packet.sptd.Cdb, cdb, cdb_length);
+
+ bytes_returned = 0;
+ ioctl_ok = DeviceIoControl(hDevice,
+ IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &packet,
+ sizeof(packet),
+ &packet,
+ sizeof(packet),
+ &bytes_returned,
+ NULL);
+
+ result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
+ result->scsi_status = packet.sptd.ScsiStatus;
+ xgd1_parse_sense(packet.sense,
+ &result->sense_key,
+ &result->asc,
+ &result->ascq);
+ result->ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
+}
+
+static void xgd1_set_lba(UCHAR *cdb, uint32_t lba)
+{
+ cdb[2] = (UCHAR)((lba >> 24) & 0xff);
+ cdb[3] = (UCHAR)((lba >> 16) & 0xff);
+ cdb[4] = (UCHAR)((lba >> 8) & 0xff);
+ cdb[5] = (UCHAR)(lba & 0xff);
+}
+
+static void xgd1_build_memdump_cdb(UCHAR cdb[12],
+ uint32_t address,
+ uint32_t length)
+{
+ memset(cdb, 0, 12);
+ cdb[0] = 0xE7;
+ cdb[1] = 0x48;
+ cdb[2] = 0x49;
+ cdb[3] = 0x54;
+ cdb[4] = 0x01;
+ cdb[6] = (UCHAR)((address >> 24) & 0xff);
+ cdb[7] = (UCHAR)((address >> 16) & 0xff);
+ cdb[8] = (UCHAR)((address >> 8) & 0xff);
+ cdb[9] = (UCHAR)(address & 0xff);
+ cdb[10] = (UCHAR)((length >> 8) & 0xff);
+ cdb[11] = (UCHAR)(length & 0xff);
+}
+
+static void xgd1_set_expected_geometry(int locked_view,
+ xgd1_raw_id_sample *sample)
+{
+ uint32_t output_lba;
+ uint32_t layer_zero_last_psn;
+ uint32_t normalized_psn;
+ uint32_t expected_normalized_psn;
+ uint32_t expected_layer;
+ uint32_t expected_raw_psn;
+
+ if (!sample)
+ return;
+
+ if (locked_view)
+ {
+ if (sample->lba < XGD1_VIDEO_L0_SECTORS)
+ output_lba = sample->lba;
+ else
+ output_lba = XGD1_VIDEO_L1_OUTPUT_START_LBA +
+ (sample->lba - XGD1_VIDEO_L0_SECTORS);
+ }
+ else
+ {
+ output_lba = XGD1_GAME_OUTPUT_START_LBA + sample->lba;
+ }
+
+ layer_zero_last_psn = 0x30000U + XGD1_REDUMP_LAYER_BREAK_LBA - 1U;
+ if (output_lba < XGD1_REDUMP_LAYER_BREAK_LBA)
+ {
+ expected_layer = 0U;
+ expected_normalized_psn = 0x30000U + output_lba;
+ expected_raw_psn = expected_normalized_psn;
+ }
+ else
+ {
+ expected_layer = 1U;
+ expected_normalized_psn = layer_zero_last_psn -
+ (output_lba - XGD1_REDUMP_LAYER_BREAK_LBA);
+ expected_raw_psn = (~expected_normalized_psn) & 0x00ffffffU;
+ }
+
+ normalized_psn = (sample->layer_number != 0U)
+ ? ((~sample->physical_sector_number) & 0x00ffffffU)
+ : sample->physical_sector_number;
+
+ sample->output_lba = output_lba;
+ sample->normalized_physical_sector_number = normalized_psn;
+ sample->expected_layer_number = expected_layer;
+ sample->expected_physical_sector_number = expected_raw_psn;
+ sample->expected_normalized_physical_sector_number = expected_normalized_psn;
+ sample->layer_matches_expected =
+ (sample->layer_number == expected_layer) ? 1 : 0;
+ sample->psn_matches_expected =
+ (sample->physical_sector_number == expected_raw_psn) ? 1 : 0;
+ sample->geometry_matches_expected =
+ (sample->layer_matches_expected && sample->psn_matches_expected) ? 1 : 0;
+}
+
+
+static void xgd1_capture_sample(HANDLE hDevice,
+ uint32_t capacity,
+ int locked_view,
+ const xgd1_probe_point *point,
+ xgd1_raw_id_sample *sample)
+{
+ unsigned char *read_buffer;
+ UCHAR flush_cdb[12];
+ UCHAR read_cdb[12];
+ UCHAR e7_cdb[12];
+ uint32_t fill_lba;
+ uint32_t index;
+ uint32_t address;
+
+ if (!sample || !point)
+ return;
+
+ memset(sample, 0, sizeof(*sample));
+ sample->lba = point->lba;
+ sample->label = point->label;
+
+ if (capacity < XGD1_CACHE_FILL_SECTORS || point->lba >= capacity)
+ {
+ sample->cache_flush.win32_error = ERROR_INVALID_PARAMETER;
+ return;
+ }
+
+ fill_lba = point->lba & ~(XGD1_CACHE_FILL_SECTORS - 1U);
+ if (fill_lba > capacity - XGD1_CACHE_FILL_SECTORS)
+ fill_lba = capacity - XGD1_CACHE_FILL_SECTORS;
+ index = point->lba - fill_lba;
+ address = XGD1_RAW_ID_CACHE_BASE + index * XGD1_RAW_SECTOR_BYTES;
+
+ sample->cache_fill_lba = fill_lba;
+ sample->cache_index = index;
+ sample->cache_address = address;
+
+ memset(flush_cdb, 0, sizeof(flush_cdb));
+ flush_cdb[0] = 0xA8;
+ flush_cdb[1] = 0x08;
+ xgd1_set_lba(flush_cdb, fill_lba);
+ xgd1_execute_no_data(hDevice,
+ flush_cdb,
+ 12,
+ 15,
+ &sample->cache_flush);
+ if (!sample->cache_flush.ok)
+ return;
+
+ read_buffer = (unsigned char *)VirtualAlloc(
+ NULL,
+ XGD1_CACHE_FILL_SECTORS * XGD1_LOGICAL_SECTOR_BYTES,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE);
+ if (!read_buffer)
+ {
+ sample->cache_fill.win32_error = ERROR_NOT_ENOUGH_MEMORY;
+ return;
+ }
+
+ memset(read_cdb, 0, sizeof(read_cdb));
+ read_cdb[0] = 0xA8;
+ xgd1_set_lba(read_cdb, fill_lba);
+ read_cdb[9] = (UCHAR)XGD1_CACHE_FILL_SECTORS;
+ read_cdb[10] = 0x80;
+
+ xgd1_execute_data_in(hDevice,
+ read_cdb,
+ 12,
+ read_buffer,
+ XGD1_CACHE_FILL_SECTORS * XGD1_LOGICAL_SECTOR_BYTES,
+ 15,
+ &sample->cache_fill);
+
+ if (sample->cache_fill.ok)
+ {
+ memcpy(sample->logical,
+ read_buffer + index * XGD1_LOGICAL_SECTOR_BYTES,
+ XGD1_LOGICAL_SECTOR_BYTES);
+ xgd1_sha1(sample->logical,
+ XGD1_LOGICAL_SECTOR_BYTES,
+ sample->logical_sha1);
+
+ xgd1_build_memdump_cdb(e7_cdb, address, 12U);
+ xgd1_execute_data_in(hDevice,
+ e7_cdb,
+ 12,
+ sample->raw_header,
+ 12U,
+ 15,
+ &sample->memdump_header);
+
+ xgd1_build_memdump_cdb(e7_cdb, address + 2060U, 4U);
+ xgd1_execute_data_in(hDevice,
+ e7_cdb,
+ 12,
+ sample->raw_edc,
+ 4U,
+ 15,
+ &sample->memdump_edc);
+
+ if (sample->memdump_header.ok && sample->memdump_edc.ok)
+ {
+ sample->raw_header_valid = 1;
+ sample->sector_information = sample->raw_header[0];
+ sample->layer_number = (uint32_t)(sample->sector_information & 1U);
+ sample->physical_sector_number =
+ ((uint32_t)sample->raw_header[1] << 16) |
+ ((uint32_t)sample->raw_header[2] << 8) |
+ (uint32_t)sample->raw_header[3];
+ xgd1_set_expected_geometry(locked_view, sample);
+ }
+ }
+
+ VirtualFree(read_buffer, 0, MEM_RELEASE);
+}
+
+static void xgd1_capture_state(HANDLE hDevice,
+ xgd1_raw_id_state *state,
+ const xgd1_probe_point *points,
+ uint32_t point_count)
+{
+ uint32_t i;
+
+ if (!state || !points || !state->state_verified)
+ return;
+ if (point_count > XGD1_RAW_ID_MAX_SAMPLES)
+ point_count = XGD1_RAW_ID_MAX_SAMPLES;
+
+ state->sample_count = point_count;
+ state->geometry_validation_failures = 0U;
+ for (i = 0; i < point_count; ++i)
+ {
+ printf("[XGD1-RAW-ID] %s: cache-flush, aligned READ(12), and HIT 0xE7 header/EDC capture for LBA %u (%s).\n",
+ state->name ? state->name : "state",
+ points[i].lba,
+ points[i].label ? points[i].label : "unlabeled");
+ xgd1_capture_sample(hDevice,
+ state->capacity,
+ state->locked_view,
+ &points[i],
+ &state->samples[i]);
+ if (!state->samples[i].cache_flush.ok ||
+ !state->samples[i].cache_fill.ok ||
+ !state->samples[i].raw_header_valid ||
+ !state->samples[i].geometry_matches_expected)
+ state->geometry_validation_failures++;
+ }
+
+ state->geometry_verified =
+ (state->state_verified &&
+ state->sample_count > 0U &&
+ state->geometry_validation_failures == 0U) ? 1 : 0;
+}
+
+static void xgd1_json_string(FILE *f, const char *s)
+{
+ const unsigned char *p;
+
+ fputc('"', f);
+ if (s)
+ {
+ for (p = (const unsigned char *)s; *p; ++p)
+ {
+ switch (*p)
+ {
+ case '\\': fputs("\\\\", f); break;
+ case '"': fputs("\\\"", f); break;
+ case '\b': fputs("\\b", f); break;
+ case '\f': fputs("\\f", f); break;
+ case '\n': fputs("\\n", f); break;
+ case '\r': fputs("\\r", f); break;
+ case '\t': fputs("\\t", f); break;
+ default:
+ if (*p < 0x20)
+ fprintf(f, "\\u%04x", (unsigned int)*p);
+ else
+ fputc(*p, f);
+ break;
+ }
+ }
+ }
+ fputc('"', f);
+}
+
+static void xgd1_json_hex(FILE *f,
+ const unsigned char *data,
+ uint32_t size)
+{
+ uint32_t i;
+
+ fputc('"', f);
+ for (i = 0; i < size; ++i)
+ fprintf(f, "%02x", data[i]);
+ fputc('"', f);
+}
+
+static void xgd1_write_cmd_result(FILE *f,
+ const xgd1_cmd_result *r)
+{
+ fprintf(f, "{\"ok\":%s,\"win32_error\":%lu,\"scsi_status\":%u,"
+ "\"sense_key\":%u,\"asc\":%u,\"ascq\":%u}",
+ (r && r->ok) ? "true" : "false",
+ (unsigned long)(r ? r->win32_error : 0),
+ (unsigned int)(r ? r->scsi_status : 0),
+ (unsigned int)(r ? r->sense_key : 0),
+ (unsigned int)(r ? r->asc : 0),
+ (unsigned int)(r ? r->ascq : 0));
+}
+
+static void xgd1_write_sample(FILE *f,
+ const xgd1_raw_id_sample *s,
+ int comma)
+{
+ fprintf(f, " {\n");
+ fprintf(f, " \"lba\": %u,\n", s->lba);
+ fprintf(f, " \"label\": ");
+ xgd1_json_string(f, s->label ? s->label : "");
+ fprintf(f, ",\n");
+ fprintf(f, " \"cache_fill_lba\": %u,\n", s->cache_fill_lba);
+ fprintf(f, " \"cache_index\": %u,\n", s->cache_index);
+ fprintf(f, " \"cache_address\": \"0x%08x\",\n", s->cache_address);
+ fprintf(f, " \"cache_flush\": ");
+ xgd1_write_cmd_result(f, &s->cache_flush);
+ fprintf(f, ",\n");
+ fprintf(f, " \"cache_fill\": ");
+ xgd1_write_cmd_result(f, &s->cache_fill);
+ fprintf(f, ",\n");
+ fprintf(f, " \"memdump_header\": ");
+ xgd1_write_cmd_result(f, &s->memdump_header);
+ fprintf(f, ",\n");
+ fprintf(f, " \"memdump_edc\": ");
+ xgd1_write_cmd_result(f, &s->memdump_edc);
+ fprintf(f, ",\n");
+ fprintf(f, " \"raw_header_valid\": %s,\n",
+ s->raw_header_valid ? "true" : "false");
+ if (s->raw_header_valid)
+ {
+ fprintf(f, " \"sector_information\": %u,\n",
+ (unsigned int)s->sector_information);
+ fprintf(f, " \"layer_number\": %u,\n", s->layer_number);
+ fprintf(f, " \"physical_sector_number\": %u,\n",
+ s->physical_sector_number);
+ fprintf(f, " \"normalized_physical_sector_number\": %u,\n",
+ s->normalized_physical_sector_number);
+ fprintf(f, " \"output_lba\": %u,\n", s->output_lba);
+ fprintf(f, " \"expected_layer_number\": %u,\n",
+ s->expected_layer_number);
+ fprintf(f, " \"expected_physical_sector_number\": %u,\n",
+ s->expected_physical_sector_number);
+ fprintf(f, " \"expected_normalized_physical_sector_number\": %u,\n",
+ s->expected_normalized_physical_sector_number);
+ fprintf(f, " \"layer_matches_expected\": %s,\n",
+ s->layer_matches_expected ? "true" : "false");
+ fprintf(f, " \"psn_matches_expected\": %s,\n",
+ s->psn_matches_expected ? "true" : "false");
+ fprintf(f, " \"geometry_matches_expected\": %s,\n",
+ s->geometry_matches_expected ? "true" : "false");
+ fprintf(f, " \"logical_sha1\": \"%s\",\n", s->logical_sha1);
+ fprintf(f, " \"raw_header_hex\": ");
+ xgd1_json_hex(f, s->raw_header, 12U);
+ fprintf(f, ",\n");
+ fprintf(f, " \"raw_edc_hex\": ");
+ xgd1_json_hex(f, s->raw_edc, 4U);
+ fprintf(f, ",\n");
+ fprintf(f, " \"logical_data_hex\": ");
+ xgd1_json_hex(f, s->logical, XGD1_LOGICAL_SECTOR_BYTES);
+ fprintf(f, "\n");
+ }
+ else
+ {
+ fprintf(f, " \"sector_information\": null,\n");
+ fprintf(f, " \"layer_number\": null,\n");
+ fprintf(f, " \"physical_sector_number\": null,\n");
+ fprintf(f, " \"normalized_physical_sector_number\": null,\n");
+ fprintf(f, " \"output_lba\": null,\n");
+ fprintf(f, " \"expected_layer_number\": null,\n");
+ fprintf(f, " \"expected_physical_sector_number\": null,\n");
+ fprintf(f, " \"expected_normalized_physical_sector_number\": null,\n");
+ fprintf(f, " \"layer_matches_expected\": false,\n");
+ fprintf(f, " \"psn_matches_expected\": false,\n");
+ fprintf(f, " \"geometry_matches_expected\": false,\n");
+ fprintf(f, " \"logical_sha1\": null,\n");
+ fprintf(f, " \"raw_header_hex\": \"\",\n");
+ fprintf(f, " \"raw_edc_hex\": \"\",\n");
+ fprintf(f, " \"logical_data_hex\": \"\"\n");
+ }
+ fprintf(f, " }%s\n", comma ? "," : "");
+}
+
+static void xgd1_write_state(FILE *f,
+ const xgd1_raw_id_state *state,
+ int comma)
+{
+ uint32_t i;
+
+ fprintf(f, " {\n");
+ fprintf(f, " \"name\": ");
+ xgd1_json_string(f, state->name ? state->name : "");
+ fprintf(f, ",\n");
+ fprintf(f, " \"capacity_valid\": %s,\n",
+ state->capacity_valid ? "true" : "false");
+ fprintf(f, " \"capacity_sectors\": %u,\n", state->capacity);
+ fprintf(f, " \"state_verified\": %s,\n",
+ state->state_verified ? "true" : "false");
+ fprintf(f, " \"geometry_validation_failures\": %u,\n",
+ state->geometry_validation_failures);
+ fprintf(f, " \"geometry_verified\": %s,\n",
+ state->geometry_verified ? "true" : "false");
+ fprintf(f, " \"samples\": [\n");
+ for (i = 0; i < state->sample_count; ++i)
+ xgd1_write_sample(f,
+ &state->samples[i],
+ (i + 1U < state->sample_count));
+ fprintf(f, " ]\n");
+ fprintf(f, " }%s\n", comma ? "," : "");
+}
+
+static int xgd1_write_report(const char *report_path,
+ const char *device,
+ uint32_t entry_capacity,
+ int entry_game_view,
+ int volume_lock_ever,
+ const xgd1_raw_id_state *locked_state,
+ const xgd1_raw_id_state *unlocked_state,
+ int restore_attempted,
+ uint32_t restored_capacity,
+ int restore_verified)
+{
+ const char *final_path;
+ char tmp_path[MAX_PATH * 4];
+ FILE *f;
+ int fd;
+ BOOL moved;
+ uint32_t validation_failures;
+ int geometry_resolved;
+
+ final_path = (report_path && report_path[0])
+ ? report_path
+ : "xgd1_raw_id_probe.json";
+
+ validation_failures =
+ (locked_state ? locked_state->geometry_validation_failures : 0U) +
+ (unlocked_state ? unlocked_state->geometry_validation_failures : 0U);
+ geometry_resolved =
+ (locked_state && locked_state->geometry_verified &&
+ unlocked_state && unlocked_state->geometry_verified) ? 1 : 0;
+
+ if (strlen(final_path) + 32U >= sizeof(tmp_path))
+ {
+ printf("[XGD1-RAW-ID] Fatal: report path is too long.\n");
+ return 0;
+ }
+
+ sprintf(tmp_path,
+ "%s.tmp.%lu.%lu",
+ final_path,
+ (unsigned long)GetCurrentProcessId(),
+ (unsigned long)GetTickCount());
+
+ f = fopen(tmp_path, "wb");
+ if (!f)
+ {
+ printf("[XGD1-RAW-ID] Fatal: could not create temporary report %s.\n",
+ tmp_path);
+ return 0;
+ }
+
+ fprintf(f, "{\n");
+ fprintf(f, " \"schema_version\": %u,\n",
+ XGD1_RAW_ID_PROBE_SCHEMA_VERSION);
+ fprintf(f, " \"producer\": \"friidump-0.5.3.13\",\n");
+ fprintf(f, " \"probe\": \"original_xbox_xgd1_cache_aligned_raw_id_geometry\",\n");
+ fprintf(f, " \"device\": ");
+ xgd1_json_string(f, device ? device : "");
+ fprintf(f, ",\n");
+ fprintf(f, " \"cache\": {\n");
+ fprintf(f, " \"memdump_command\": \"E7 48 49 54 01\",\n");
+ fprintf(f, " \"base_address\": \"0x%08x\",\n",
+ XGD1_RAW_ID_CACHE_BASE);
+ fprintf(f, " \"raw_sector_stride\": %u,\n",
+ XGD1_RAW_SECTOR_BYTES);
+ fprintf(f, " \"cache_flush_command\": \"READ(12), byte1=0x08, zero transfer length\",\n");
+ fprintf(f, " \"cache_fill_command\": \"READ(12) streaming, 16 sectors, block-aligned LBA\",\n");
+ fprintf(f, " \"memdump_regions\": \"12-byte ID/IED/CPR_MAI header plus 4-byte EDC only\",\n");
+ fprintf(f, " \"raw_user_data_compared_to_logical\": false,\n");
+ fprintf(f, " \"raw_user_data_note\": \"The proven Method 8 path replaces the drive-cache user field with READ(12) data before unscrambling; equality is not expected and is not a geometry test.\"\n");
+ fprintf(f, " },\n");
+ fprintf(f, " \"geometry_model\": {\n");
+ fprintf(f, " \"dvd_start_psn\": 196608,\n");
+ fprintf(f, " \"game_output_start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA);
+ fprintf(f, " \"layer_break_lba\": %u,\n", XGD1_REDUMP_LAYER_BREAK_LBA);
+ fprintf(f, " \"layer_zero_last_normalized_psn\": %u,\n",
+ 0x30000U + XGD1_REDUMP_LAYER_BREAK_LBA - 1U);
+ fprintf(f, " \"layer_one_raw_psn_is_24bit_complement\": true\n");
+ fprintf(f, " },\n");
+ fprintf(f, " \"safety\": {\n");
+ fprintf(f, " \"read_only_sector_commands\": true,\n");
+ fprintf(f, " \"authentication_handshake_used\": true,\n");
+ fprintf(f, " \"tray_cycle_used\": true,\n");
+ fprintf(f, " \"firmware_write_used\": false,\n");
+ fprintf(f, " \"flash_command_used\": false\n");
+ fprintf(f, " },\n");
+ fprintf(f, " \"entry\": {\n");
+ fprintf(f, " \"capacity_sectors\": %u,\n", entry_capacity);
+ fprintf(f, " \"game_view\": %s,\n",
+ entry_game_view ? "true" : "false");
+ fprintf(f, " \"classification\": \"%s\"\n",
+ entry_game_view ? "unlocked_game" : "locked_video_or_unknown");
+ fprintf(f, " },\n");
+ fprintf(f, " \"volume_lock_acquired_at_least_once\": %s,\n",
+ volume_lock_ever ? "true" : "false");
+ fprintf(f, " \"required_states_verified\": %s,\n",
+ (locked_state && locked_state->state_verified &&
+ unlocked_state && unlocked_state->state_verified)
+ ? "true" : "false");
+ fprintf(f, " \"geometry_validation_failures\": %u,\n",
+ validation_failures);
+ fprintf(f, " \"physical_geometry_resolved\": %s,\n",
+ geometry_resolved ? "true" : "false");
+ fprintf(f, " \"states\": [\n");
+ xgd1_write_state(f, locked_state, 1);
+ xgd1_write_state(f, unlocked_state, 0);
+ fprintf(f, " ],\n");
+ fprintf(f, " \"restoration\": {\n");
+ fprintf(f, " \"attempted\": %s,\n",
+ restore_attempted ? "true" : "false");
+ fprintf(f, " \"capacity_sectors\": %u,\n", restored_capacity);
+ fprintf(f, " \"verified\": %s\n",
+ restore_verified ? "true" : "false");
+ fprintf(f, " },\n");
+ fprintf(f, " \"interpretation_boundary\": {\n");
+ fprintf(f, " \"physical_geometry_resolved\": %s,\n",
+ geometry_resolved ? "true" : "false");
+ fprintf(f, " \"pregame_filler_content_resolved\": false,\n");
+ fprintf(f, " \"postgame_filler_content_resolved\": false,\n");
+ fprintf(f, " \"note\": \"A complete cache-aligned raw-ID match can validate the logical-to-physical placement used by the current reconstruction. It does not make inaccessible filler sectors readable and does not establish exact filler bytes.\"\n");
+ fprintf(f, " }\n");
+ fprintf(f, "}\n");
+
+ if (fflush(f) != 0)
+ {
+ fclose(f);
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ fd = _fileno(f);
+ if (fd < 0 || _commit(fd) != 0)
+ {
+ fclose(f);
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ if (fclose(f) != 0)
+ {
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ moved = MoveFileExA(tmp_path,
+ final_path,
+ MOVEFILE_REPLACE_EXISTING |
+ MOVEFILE_WRITE_THROUGH);
+ if (!moved)
+ {
+ printf("[XGD1-RAW-ID] Fatal: could not atomically publish report %s (error %lu).\n",
+ final_path,
+ (unsigned long)GetLastError());
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ printf("[XGD1-RAW-ID] Atomic report written: %s\n", final_path);
+ return 1;
+}
+
+static int xgd1_raw_id_probe_core(HANDLE hDevice,
+ const char *device,
+ const char *report_path,
+ BOOL owns_handle)
+{
+ xgd1_raw_id_state locked_state;
+ xgd1_raw_id_state unlocked_state;
+ uint32_t entry_capacity;
+ uint32_t restored_capacity;
+ int entry_game_view;
+ int volume_lock_acquired;
+ int volume_lock_ever;
+ int restore_attempted;
+ int restore_verified;
+ int states_verified;
+ DWORD bytes_returned;
+ int report_ok;
+
+ memset(&locked_state, 0, sizeof(locked_state));
+ memset(&unlocked_state, 0, sizeof(unlocked_state));
+ locked_state.name = "locked_video";
+ locked_state.locked_view = 1;
+ unlocked_state.name = "unlocked_game";
+ unlocked_state.locked_view = 0;
+ entry_capacity = 0;
+ restored_capacity = 0;
+ entry_game_view = 0;
+ volume_lock_acquired = 0;
+ volume_lock_ever = 0;
+ restore_attempted = 0;
+ restore_verified = 0;
+ states_verified = 0;
+ bytes_returned = 0;
+
+ xbox_ref_log_open_for_target(report_path,
+ xgd1_drive_letter_from_device(device));
+ printf("[XGD1-RAW-ID] Starting cache-aligned raw-sector ID geometry probe.\n");
+ printf("[XGD1-RAW-ID] Requires modified GDR-8050L firmware with HIT 0xE7 memdump support.\n");
+ printf("[XGD1-RAW-ID] No ISO or firmware-write command will be issued.\n");
+
+ if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
+ {
+ printf("[XGD1-RAW-ID] Fatal: invalid drive handle.\n");
+ if (owns_handle)
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ if (!EnsureDriveReady(hDevice, 30000))
+ {
+ printf("[XGD1-RAW-ID] Fatal: media did not become ready.\n");
+ if (owns_handle)
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ entry_capacity = GetTotalSectors(hDevice);
+ entry_game_view = xgd1_capacity_is_game(entry_capacity) ? 1 : 0;
+ printf("[XGD1-RAW-ID] Entry READ CAPACITY: %u sectors (%s).\n",
+ entry_capacity,
+ entry_game_view ? "game view" : "locked/video or unknown view");
+
+ DeviceIoControl(hDevice,
+ FSCTL_UNLOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL);
+
+ printf("[XGD1-RAW-ID] Cycling tray to establish locked/video view.\n");
+ AutomateTrayCycle(hDevice);
+ locked_state.capacity = xgd1_refresh_ready_capacity(hDevice);
+ locked_state.capacity_valid = locked_state.capacity != 0U;
+ locked_state.state_verified =
+ (locked_state.capacity_valid &&
+ xgd1_capacity_is_locked(locked_state.capacity)) ? 1 : 0;
+ printf("[XGD1-RAW-ID] Locked/video READ CAPACITY: %u sectors; verified=%s.\n",
+ locked_state.capacity,
+ locked_state.state_verified ? "yes" : "no");
+
+ if (DeviceIoControl(hDevice,
+ FSCTL_LOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL))
+ {
+ volume_lock_acquired = 1;
+ volume_lock_ever = 1;
+ printf("[XGD1-RAW-ID] Windows volume lock acquired.\n");
+ }
+ else
+ {
+ printf("[XGD1-RAW-ID][WARN] Windows volume lock was not acquired.\n");
+ }
+
+ SetDriveSpeedMax(hDevice);
+ xgd1_capture_state(hDevice,
+ &locked_state,
+ xgd1_locked_points,
+ (uint32_t)(sizeof(xgd1_locked_points) /
+ sizeof(xgd1_locked_points[0])));
+
+ printf("[XGD1-RAW-ID] Applying the full Xbox handshake to establish game view.\n");
+ UnlockDrive(hDevice);
+ unlocked_state.capacity = xgd1_refresh_ready_capacity(hDevice);
+ unlocked_state.capacity_valid = unlocked_state.capacity != 0U;
+ unlocked_state.state_verified =
+ (unlocked_state.capacity_valid &&
+ xgd1_capacity_is_game(unlocked_state.capacity)) ? 1 : 0;
+ printf("[XGD1-RAW-ID] Unlocked/game READ CAPACITY: %u sectors; verified=%s.\n",
+ unlocked_state.capacity,
+ unlocked_state.state_verified ? "yes" : "no");
+
+ xgd1_capture_state(hDevice,
+ &unlocked_state,
+ xgd1_unlocked_points,
+ (uint32_t)(sizeof(xgd1_unlocked_points) /
+ sizeof(xgd1_unlocked_points[0])));
+
+ if (volume_lock_acquired)
+ {
+ DeviceIoControl(hDevice,
+ FSCTL_UNLOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL);
+ volume_lock_acquired = 0;
+ }
+
+ if (!entry_game_view && entry_capacity != 0U)
+ {
+ restore_attempted = 1;
+ printf("[XGD1-RAW-ID] Restoring entry locked/video state with a final tray cycle.\n");
+ AutomateTrayCycle(hDevice);
+ restored_capacity = xgd1_refresh_ready_capacity(hDevice);
+ restore_verified =
+ (restored_capacity != 0U &&
+ xgd1_capacity_is_locked(restored_capacity)) ? 1 : 0;
+ }
+ else
+ {
+ restored_capacity = GetTotalSectors(hDevice);
+ restore_verified =
+ (entry_game_view &&
+ xgd1_capacity_is_game(restored_capacity)) ? 1 : 0;
+ }
+
+ states_verified =
+ (locked_state.state_verified &&
+ unlocked_state.state_verified) ? 1 : 0;
+
+ report_ok = xgd1_write_report(report_path,
+ device,
+ entry_capacity,
+ entry_game_view,
+ volume_lock_ever,
+ &locked_state,
+ &unlocked_state,
+ restore_attempted,
+ restored_capacity,
+ restore_verified);
+
+ if (owns_handle)
+ {
+ printf("[XGD1-RAW-ID] Issuing STOP UNIT / spin-down after probe... ");
+ printf("%s\n", StopDriveUnit(hDevice) ? "OK" : "FAILED");
+ CloseDrive(hDevice);
+ xbox_ref_log_close();
+ }
+
+ if (!report_ok)
+ return 1;
+ if (!states_verified)
+ {
+ printf("[XGD1-RAW-ID] Probe report is partial because one or more states were not verified.\n");
+ return 1;
+ }
+ if (!locked_state.geometry_verified || !unlocked_state.geometry_verified)
+ {
+ printf("[XGD1-RAW-ID] Geometry validation failed: locked failures=%u, unlocked failures=%u.\n",
+ locked_state.geometry_validation_failures,
+ unlocked_state.geometry_validation_failures);
+ return 1;
+ }
+
+ printf("[XGD1-RAW-ID] Probe complete. All selected raw IDs match the current XGD1 output geometry.\n");
+ return 0;
+}
+
+int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path)
+{
+ return xgd1_raw_id_probe_core((HANDLE)native_handle,
+ device,
+ report_path,
+ FALSE);
+}
+
+int xbox_ref_xgd1_raw_id_probe(const char *device,
+ const char *report_path)
+{
+ char drive_letter;
+ HANDLE hDevice;
+
+ drive_letter = xgd1_drive_letter_from_device(device);
+ if (!drive_letter)
+ {
+ printf("[XGD1-RAW-ID] Fatal: could not parse drive letter from device.\n");
+ return 1;
+ }
+
+ hDevice = OpenDrive(drive_letter);
+ if (hDevice == INVALID_HANDLE_VALUE)
+ {
+ printf("[XGD1-RAW-ID] Fatal: cannot open drive %c:. Run as Administrator.\n",
+ drive_letter);
+ return 1;
+ }
+
+ return xgd1_raw_id_probe_core(hDevice,
+ device,
+ report_path,
+ TRUE);
+}
+
+#else
+
+int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path)
+{
+ (void)native_handle;
+ (void)device;
+ (void)report_path;
+ return 1;
+}
+
+int xbox_ref_xgd1_raw_id_probe(const char *device,
+ const char *report_path)
+{
+ (void)device;
+ (void)report_path;
+ return 1;
+}
+
+#endif
libfriidump\vanilla_2384.c
libfriidump\win32compat.c
libfriidump/xbox_ref_bridge.c
+libfriidump/xbox_xgd1_raw_id_probe.c
libfriidump/xbox_ref/utils.c
libfriidump/xbox_ref/unlock.c
libfriidump/xbox_ref/rc4.c
#include "dumper.h"
#include "unscrambler.h"
#include "xbox_ref/xbox_ref_log.h"
+#include "xbox_ref_bridge.h"
#include "redump_dat.h"
#ifdef WIN32
static bool g_redump_attempted = false;
static bool g_operation_duration_override_valid = false;
static double g_operation_duration_override = 0.0;
+static char g_executable_dir[1024];
static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
if (!dst || dst_size == 0)
#define PACKAGE_NAME "FriiDump"
/* Define to the version of this package. */
-#define PACKAGE_VERSION "0.5.3.10"
+#define PACKAGE_VERSION "0.5.3.15"
#ifdef WIN32
char *redump_dat_dir;
char *redump_report;
bool no_redump_verify;
+ bool xgd1_layout_probe;
+ char *xgd1_layout_probe_report;
+ bool xgd1_raw_id_probe;
+ char *xgd1_raw_id_probe_report;
} options;
static const char *friidump_requested_output_target(void) {
+ if (options.xgd1_raw_id_probe_report && options.xgd1_raw_id_probe_report[0]) return options.xgd1_raw_id_probe_report;
+ if (options.xgd1_layout_probe_report && options.xgd1_layout_probe_report[0]) return options.xgd1_layout_probe_report;
if (options.iso_out && options.iso_out[0]) return options.iso_out;
if (options.xiso_out && options.xiso_out[0]) return options.xiso_out;
if (options.raw_out && options.raw_out[0]) return options.raw_out;
+static void friidump_init_executable_dir(const char *argv0) {
+ char path[1024];
+ size_t length = 0;
+ char *slash;
+ char *backslash;
+ char *separator;
+
+ g_executable_dir[0] = '\0';
+ path[0] = '\0';
+
+#ifdef WIN32
+ {
+ DWORD result = GetModuleFileNameA(NULL, path, (DWORD)sizeof(path));
+ if (result > 0 && result < sizeof(path)) {
+ path[result] = '\0';
+ length = (size_t)result;
+ }
+ }
+#else
+#if defined(__linux__)
+ {
+ ssize_t result = readlink("/proc/self/exe", path, sizeof(path) - 1);
+ if (result > 0 && (size_t)result < sizeof(path)) {
+ path[result] = '\0';
+ length = (size_t)result;
+ }
+ }
+#endif
+ if (length == 0 && argv0 && argv0[0] &&
+ (strchr(argv0, '/') || strchr(argv0, '\\'))) {
+ char *resolved = realpath(argv0, path);
+ if (resolved)
+ length = strlen(path);
+ }
+#endif
+
+ if (length == 0)
+ return;
+
+ slash = strrchr(path, '/');
+ backslash = strrchr(path, '\\');
+ separator = slash;
+ if (backslash && (!separator || backslash > separator))
+ separator = backslash;
+
+ if (!separator)
+ return;
+
+ *separator = '\0';
+ if (path[0])
+ snprintf(g_executable_dir, sizeof(g_executable_dir), "%s", path);
+}
+
static const char *friidump_redump_dat_basename(disc_type type_id) {
switch (type_id) {
case DISC_TYPE_GAMECUBE:
redump_verify_result_init(&g_redump_result);
g_redump_attempted = false;
representation_note = (type_id == DISC_TYPE_XBOX)
- ? "XGD1 acquisition success and exact Redump hash identity are separate claims; documented synthetic reconstruction ranges may prevent an exact match."
+ ? "XGD1 acquisition success and exact Redump hash identity are separate claims; unresolved zero-filled pregame/postgame content may prevent an exact match."
: "";
if (options.no_redump_verify) {
return;
}
- dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0]) ? options.redump_dat_dir : "redump_dat";
- snprintf(dat_path, sizeof(dat_path), "%s/%s", dat_dir, basename);
+ dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0])
+ ? options.redump_dat_dir
+ : NULL;
+ redump_resolve_dat_path(dat_dir, g_executable_dir, basename,
+ dat_path, sizeof(dat_path));
if (output_size == 0)
output_size = friidump_file_size(output_path);
" --scan-dump-prefix <prefix> Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n"
" --hlds-profile-report <file> Write selected HLDS profile/evidence JSON\n"
" --redump-dat-dir <dir> Directory containing canonical Redump DAT files\n"
- " (default: redump_dat)\n"
+ " (default: executable-relative redump_dat, then current directory)\n"
" --redump-report <file> Write atomic Redump evidence JSON\n"
" --no-redump-verify Disable automatic post-dump DAT verification\n"
+ " --xgd1-layout-probe <file> Read-only locked/unlocked XGD1 boundary probe;\n"
+ " writes atomic JSON and does not create an ISO\n"
+ " --xgd1-raw-id-probe <file> Modified-firmware cache-flushed, block-aligned raw-ID probe;\n"
+ " maps logical LBAs to decoded physical sector IDs\n"
" -A, --allmethods Try all known command/method combinations until\n"
" one works. Reopens the drive for each command so\n"
" command-specific vendor handlers are rebound.\n"
{"redump-dat-dir", 1, 0, 1006},
{"redump-report", 1, 0, 1007},
{"no-redump-verify", 0, 0, 1008},
+ {"xgd1-layout-probe", 1, 0, 1009},
+ {"xgd1-raw-id-probe", 1, 0, 1010},
#ifdef DEBUG
/* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */
{"donottunscramble", 0, 0, 'n'},
options.redump_dat_dir = NULL;
options.redump_report = NULL;
options.no_redump_verify = false;
+ options.xgd1_layout_probe = false;
+ options.xgd1_layout_probe_report = NULL;
+ options.xgd1_raw_id_probe = false;
+ options.xgd1_raw_id_probe_report = NULL;
do {
#ifdef DEBUG
case 1008:
options.no_redump_verify = true;
break;
+ case 1009:
+ options.xgd1_layout_probe = true;
+ my_strdup (options.xgd1_layout_probe_report, optarg);
+ options.disctype = DISC_TYPE_XBOX;
+ break;
+ case 1010:
+ options.xgd1_raw_id_probe = true;
+ my_strdup (options.xgd1_raw_id_probe_report, optarg);
+ options.disctype = DISC_TYPE_XBOX;
+ break;
case -1:
break;
default:
fprintf (stderr, "WARNING: Extra parameters ignored\n");
}
+ if (options.xgd1_layout_probe || options.xgd1_raw_id_probe)
+ options.disctype = DISC_TYPE_XBOX;
+
/* Sanity checks... */
out = false;
if (!options.device && !options.raw_in) {
fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n");
} else if (options.xiso_requested && (options.raw_out || options.iso_requested)) {
fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n");
+ } else if ((options.xgd1_layout_probe || options.xgd1_raw_id_probe) &&
+ (options.autodump || options.raw_in || options.raw_out ||
+ options.iso_requested || options.xiso_requested ||
+ options.allmethods || options.hlds_e7_scan ||
+ options.hlds_e7_subcmd_sweep || options.hlds_e7_memrange_sweep ||
+ (options.xgd1_layout_probe && options.xgd1_raw_id_probe))) {
+ fprintf (stderr, "XGD1 probe modes are mutually exclusive read-only diagnostics and cannot be combined with dump, conversion, all-methods, or HLDS 0xE7 probe options.\n");
} else {
/* Specified options seem to make sense */
out = true;
u_int32_t current_sector = 0;
xbox_forced = (options.disctype == DISC_TYPE_XBOX);
- xbox_output_requested = xbox_forced || options.xiso_requested;
+ xbox_output_requested = xbox_forced || options.xiso_requested || options.xgd1_layout_probe || options.xgd1_raw_id_probe;
dump_attempted = false;
fprintf (stderr, "OK\n");
}
+ if (options.xgd1_layout_probe || options.xgd1_raw_id_probe) {
+#ifdef WIN32
+ int media_rc;
+ int media_sense_key;
+ int media_asc;
+ int media_ascq;
+ int probe_status;
+ bool stop_ok;
+ const char *probe_name;
+
+ probe_name = options.xgd1_raw_id_probe ? "XGD1 raw-sector ID" : "XGD1 logical-boundary";
+
+ if (!disc_is_xbox_challenge_drive (d)) {
+ fprintf (stderr,
+ "%s probe currently supports only the GDR-8050L challenge-handshake profile.\n",
+ probe_name);
+ return false;
+ }
+
+ if (options.xgd1_raw_id_probe &&
+ !(disc_get_hlds_e7_type (d) == 44 ||
+ disc_get_hlds_e7_type (d) == 45 ||
+ disc_get_hlds_e7_type (d) == 442 ||
+ disc_get_hlds_e7_type (d) == 443 ||
+ disc_get_hlds_e7_type (d) == 445)) {
+ fprintf (stderr,
+ "--xgd1-raw-id-probe requires the modified GDR-8050L HIT 0xE7 memdump profile.\n");
+ return false;
+ }
+
+ fprintf (stderr, "\nChecking for ready Xbox media before %s probing... ", probe_name);
+ media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
+ if (media_rc <= 0) {
+ fprintf (stderr,
+ "Failed (sense %02X/%02X/%02X). Insert the disc, wait for spin-up, close AutoPlay/File Explorer, and retry.\n",
+ media_sense_key, media_asc, media_ascq);
+ return false;
+ }
+ fprintf (stderr, "OK\n");
+
+ gettimeofday (&(stats -> start_time), NULL);
+ if (options.xgd1_raw_id_probe)
+ probe_status = xbox_ref_xgd1_raw_id_probe_with_handle (
+ disc_get_native_handle (d), disc_get_device (d), options.xgd1_raw_id_probe_report);
+ else
+ probe_status = xbox_ref_xgd1_layout_probe_with_handle (
+ disc_get_native_handle (d), disc_get_device (d), options.xgd1_layout_probe_report);
+
+ fprintf (stderr, "Issuing STOP UNIT / spin-down after %s probe... ", probe_name);
+ stop_ok = disc_stop_unit (d, false);
+ fprintf (stderr, "%s\n", stop_ok ? "OK" : "Failed");
+ gettimeofday (&(stats -> end_time), NULL);
+ fprintf (stderr, "%s probe status: %s\n", probe_name, probe_status == 0 ? "OK" : "FAILED");
+ friidump_summary_reset();
+ return probe_status == 0;
+#else
+ fprintf (stderr, "XGD1 probe modes are available only in the Windows build.\n");
+ return false;
+#endif
+ }
+
if (options.hlds_e7_scan) {
out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... ");
/* First of all... */
drop_euid ();
+ friidump_init_executable_dir((argc > 0) ? argv[0] : NULL);
xbox_ref_log_open_for_target(NULL, 0);
welcome ();
my_free (options.raw_out);
my_free (options.raw_in);
my_free (options.hlds_profile_report);
+ my_free (options.xgd1_layout_probe_report);
+ my_free (options.xgd1_raw_id_probe_report);
}
if (xbox_ref_log_path())
#include <stdlib.h>
#include <string.h>
+static int redump_path_exists(const char *path) {
+ FILE *f;
+ if (!path || !path[0])
+ return 0;
+ f = fopen(path, "rb");
+ if (!f)
+ return 0;
+ fclose(f);
+ return 1;
+}
+
+static int redump_join_path(
+ const char *dir,
+ const char *child_dir,
+ const char *basename,
+ char *out_path,
+ size_t out_path_size
+) {
+ int written;
+
+ if (!dir || !dir[0] || !basename || !basename[0] ||
+ !out_path || out_path_size == 0)
+ return 0;
+
+ if (child_dir && child_dir[0])
+ written = snprintf(out_path, out_path_size, "%s/%s/%s",
+ dir, child_dir, basename);
+ else
+ written = snprintf(out_path, out_path_size, "%s/%s",
+ dir, basename);
+
+ if (written < 0 || (size_t)written >= out_path_size) {
+ out_path[0] = '\0';
+ return 0;
+ }
+
+ return 1;
+}
+
+int redump_resolve_dat_path(
+ const char *explicit_dir,
+ const char *executable_dir,
+ const char *basename,
+ char *out_path,
+ size_t out_path_size
+) {
+ char candidate[1024];
+
+ if (!basename || !basename[0] || !out_path || out_path_size == 0)
+ return 0;
+
+ out_path[0] = '\0';
+
+ if (explicit_dir && explicit_dir[0]) {
+ if (!redump_join_path(explicit_dir, NULL, basename,
+ out_path, out_path_size))
+ return 0;
+ return redump_path_exists(out_path);
+ }
+
+ if (executable_dir && executable_dir[0] &&
+ redump_join_path(executable_dir, "redump_dat", basename,
+ candidate, sizeof(candidate))) {
+ if (redump_path_exists(candidate)) {
+ snprintf(out_path, out_path_size, "%s", candidate);
+ return 1;
+ }
+
+ /* Retain the executable-relative path as the preferred diagnostic
+ * candidate if neither automatic location exists. */
+ snprintf(out_path, out_path_size, "%s", candidate);
+ }
+
+ if (redump_join_path(".", "redump_dat", basename,
+ candidate, sizeof(candidate))) {
+ if (redump_path_exists(candidate)) {
+ snprintf(out_path, out_path_size, "%s", candidate);
+ return 1;
+ }
+
+ if (!out_path[0])
+ snprintf(out_path, out_path_size, "%s", candidate);
+ }
+
+ return 0;
+}
+
static int redump_ascii_equal(const char *a, const char *b) {
unsigned char ca, cb;
if (!a || !b)
const char *redump_verify_overall_string(const redump_verify_result *result);
const char *redump_verify_confidence_string(const redump_verify_result *result);
const char *redump_verify_match_kind_string(const redump_verify_result *result);
+
+/*
+ * Resolve a DAT path predictably.
+ *
+ * Resolution order:
+ * 1. explicit_dir, when non-empty;
+ * 2. executable_dir/redump_dat;
+ * 3. current-working-directory redump_dat.
+ *
+ * The selected candidate is always written to out_path when arguments are
+ * valid. The return value is nonzero only when that candidate exists.
+ */
+int redump_resolve_dat_path(
+ const char *explicit_dir,
+ const char *executable_dir,
+ const char *basename,
+ char *out_path,
+ size_t out_path_size
+);
+
int redump_verify_dat_file(
const char *dat_path,
uint64_t image_size,
--- /dev/null
+#include "redump_dat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const char *optional_arg(const char *arg) {
+ if (!arg || strcmp(arg, "-") == 0)
+ return NULL;
+ return arg;
+}
+
+int main(int argc, char **argv) {
+ const char *explicit_dir;
+ const char *executable_dir;
+ const char *basename;
+ const char *expected_path;
+ int expected_found;
+ int found;
+ char resolved[1024];
+
+ if (argc != 6) {
+ fprintf(stderr,
+ "Usage: %s <explicit-dir|-> <executable-dir|-> "
+ "<basename> <expected-path> <expected-found-0-or-1>\n",
+ argc > 0 ? argv[0] : "test_redump_dat_resolver");
+ return 64;
+ }
+
+ explicit_dir = optional_arg(argv[1]);
+ executable_dir = optional_arg(argv[2]);
+ basename = argv[3];
+ expected_path = argv[4];
+ expected_found = atoi(argv[5]) ? 1 : 0;
+
+ memset(resolved, 0, sizeof(resolved));
+ found = redump_resolve_dat_path(
+ explicit_dir,
+ executable_dir,
+ basename,
+ resolved,
+ sizeof(resolved)
+ ) ? 1 : 0;
+
+ printf("resolved=%s\n", resolved);
+ printf("found=%d\n", found);
+
+ if (found != expected_found) {
+ fprintf(stderr, "Found-state mismatch: expected %d, observed %d\n",
+ expected_found, found);
+ return 1;
+ }
+
+ if (strcmp(resolved, expected_path) != 0) {
+ fprintf(stderr, "Path mismatch:\n expected: %s\n observed: %s\n",
+ expected_path, resolved);
+ return 2;
+ }
+
+ return 0;
+}