Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package redumper for openSUSE:Factory checked in at 2026-09-04 12:38:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/redumper (Old) and /work/SRC/openSUSE:Factory/.redumper.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "redumper" Fri Sep 4 12:38:31 2026 rev:9 rq:1375430 version:746 Changes: -------- --- /work/SRC/openSUSE:Factory/redumper/redumper.changes 2026-07-20 10:01:25.276332466 +0200 +++ /work/SRC/openSUSE:Factory/.redumper.new.1265/redumper.changes 2026-09-04 12:39:00.696637379 +0200 @@ -1,0 +2,14 @@ +Wed Sep 2 11:53:18 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 746 + * Support BU50N in flash_mt1959 (#436). + * Add LG-Based ASUS SDRW-8D2S-U (LG GP75N inside) (#434). + * MSF fix tools, some cleanup. + * clang release modules compiler bug workaround. + * Fix missing include (#427). + * Add initial UDF support (#403). + * dat and dat_extra split (#425). + * Extract Content IDs from PS4/PS5 PKGs (#390). + * toc fixes. + +------------------------------------------------------------------- Old: ---- redumper-b734.tar.gz New: ---- redumper-b746.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ redumper.spec ++++++ --- /var/tmp/diff_new_pack.TLeHHd/_old 2026-09-04 12:39:01.303658703 +0200 +++ /var/tmp/diff_new_pack.TLeHHd/_new 2026-09-04 12:39:01.305658773 +0200 @@ -17,7 +17,7 @@ Name: redumper -Version: 734 +Version: 746 Release: 0 Summary: Low level CD dumper utility License: GPL-3.0-only ++++++ redumper-b734.tar.gz -> redumper-b746.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/.github/copilot-instructions.md new/redumper-b746/.github/copilot-instructions.md --- old/redumper-b734/.github/copilot-instructions.md 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/.github/copilot-instructions.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +0,0 @@ -Use C-style casts. -Use latest C++ standard when generating code. -Use snake case for stack variables. -Do not put a space between keyword and (. -Always add a new line before {. -Do not add {} to one line if(). -Do not uppercase first letter of a comment except where it's abbrevation. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/CMakeLists.txt new/redumper-b746/CMakeLists.txt --- old/redumper-b734/CMakeLists.txt 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/CMakeLists.txt 2026-08-25 03:51:25.000000000 +0200 @@ -151,6 +151,7 @@ "filesystem/iso9660/iso9660_entry.ixx" "filesystem/iso9660/iso9660_map.ixx" "filesystem/udf/udf.ixx" + "filesystem/udf/udf_browser.ixx" "filesystem/udf/udf_defs.ixx" "hash/block_hasher.ixx" "hash/md5.ixx" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/README.md new/redumper-b746/README.md --- old/redumper-b734/README.md 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/README.md 2026-08-25 03:51:25.000000000 +0200 @@ -1,186 +1,228 @@ -[](https://stand-with-ukraine.pp.ua) -# redumper -Copyright 2021-2025 Hennadiy Brych +# redumper — Low-Level Optical Disc Dumper -## Intro -redumper is a low-level byte-perfect CD disc dumper. It supports incremental dumps, advanced SCSI/C2 repair, intelligent audio CD offset detection and a lot of other features. +redumper is a cross-platform command-line tool for creating accurate optical disc dumps. It provides a low-level, byte-perfect workflow for CDs, including incremental refinement, SCSI/C2 error recovery, raw subchannel preservation, and automatic audio-offset detection. It also supports dumping of DVD, HD DVD, and Blu-ray media. -redumper is also a general purpose DVD/HD-DVD/Blu-ray disc dumper. DVD and later media format dumping is implemented using high-level mode thus most of the complexities described in this document are related to the CD technology. If you intend to dump DVD media and later, you can usually use any drive and it just works. +redumper was originally created for the [Redump](https://redump.info/) disc-preservation initiative and is now the default dumping software used for Redump optical-disc submissions. -Everything is written from scratch in C++. +redumper is written from scratch in C++ and runs on Windows, Linux, and macOS. -## Operating Systems Support -Available for Windows, Linux and macOS. +## Features -## General -redumper operates using commands. +- Low-level CD acquisition with compatible PLEXTOR and MediaTek-based drives +- Sample-granular dump state for incremental refinement across multiple drives +- SCSI and C2 error detection, retries, and repair +- Automatic drive read-offset correction and disc write-offset detection +- Raw, uncorrected subchannel storage with TOC- and Q-based track splitting +- Lead-in, pre-gap, and lead-out extraction on supported drives +- Protection analysis, image information, hashing, and skeleton generation +- High-level DVD, HD DVD, and Blu-ray dumping +- Raw DVD and Blu-ray sector dumping with [OmniDrive](https://github.com/RibShark/OmniDrive) firmware +- Nintendo GameCube, Wii, and Wii U disc dumping with OmniDrive firmware +- DVD CSS key extraction and title-key recovery +- Xbox and Xbox 360 DVD dumping with compatible Kreon or OmniDrive firmware +- Drive capability testing and selected firmware flashing tools -The preferred way is to run it without arguments (equivalent to running `redumper disc`). This is the most common use case that will dump the disc and generate the dump files. Everything that can be automated, is automated. If the `--drive` option is not specified, the first available drive with an inserted disc will be used. If the `--image-name` option is not specified, the image name will be generated based on the current date and drive name. +## Quick start -The full command list is available by running `redumper --help`. +Download and extract the archive for your platform from the [latest GitHub release](https://github.com/superg/redumper/releases/latest). The release workflow publishes x86, x64, and ARM64 builds where supported. -## Windows -The drive is specified using its drive letter: `--drive=E:` or `--drive=E`. +Insert a disc and run redumper without arguments: -## Linux -The drive is specified using the full path of its device file: `--drive=/dev/sg1`. +```console +redumper +``` -The disc has to be unmounted before running redumper. I suggest disabling removable drives automounting. +This is equivalent to `redumper disc`, the aggregate workflow that dumps the disc and produces the applicable output files. Unless explicitly configured, redumper selects the first available drive containing a disc and generates an image name from the date and drive identity. -At the time of writing, drive autodetection doesn't work for some Linux distributions. Autodetection is based on kernel sysfs guidelines but some distributions deviate from that (or I didn't implement it right). This will be fixed at some point. If autodetection doesn't work for you, the current suggested workaround is to specify the drive directly. +By default, redumper records errors but performs no sector retries. For a dump that retries SCSI/C2 errors, specify an appropriate retry count: -Note that the device file has to represent a generic SCSI device as write commands will be executed (not in a sense of disc burning, but to alter drive state such as setting drive speed). This will mean using a device file like `/dev/sg0` rather than `/dev/sr0`. +```console +redumper disc --retries=100 +``` -## macOS -The drive is specified using its BSD name: `--drive=disk2` +Existing output files are protected unless `--overwrite` is supplied. -Run `diskutil list` to see a list of all disks in the system. +For the complete command and option reference, run: -Currently, the macOS version is distributed as an archive with binary executable and its C++ runtime library dependencies - there is no installation package available. You should download and extract the archive while preserving the directory structure (the binary needs to locate its library dependencies). The binary requires SCSI drive access and won't run from Desktop / Downloads / Documents home directories due to macOS security restrictions. If you attempt to run redumper from these restricted directories, you will see an error: `error: failed to create service plugin interface, MACH ((iokit/common) resource shortage)`. It's recommended to copy the entire extracted directory structure to a different location and run the binary from there. +```console +redumper --help +``` -## Supported Drives -Known good PLEXTOR/LG/ASUS/LITE-ON drive models are fully supported and recommended for the perfect CD dump. The full list is [HERE](drive.ixx#L130). Drives listed under "//OTHER" are bad drives that I own, they are listed purely for experimentation - please do not buy these drives! +## Platform notes -DVD/HD-DVD/Blu-ray discs do not require specific drive models with the exception of Xbox and Xbox 360 DVD discs (XGD). Only "Kreon" custom firmware DVD drives are supported for XGD dumping. +### Windows -While there is some GENERIC drive support implemented, GENERIC drives often will not provide a perfect CD dump due to drive firmware limitations. Feel free to use this functionality at your leisure but at the moment I don't have the bandwidth for supporting such cases, so you're on your own. +Specify a drive by its drive letter: -## Good Drives Technical -D8 and BE CDDA read opcodes are supported using compatible drives (PLEXTOR and LG/ASUS/LITE-ON respectively). Everything, including data tracks, is read as audio. On an initial dump pass, each sector is dumped from start to end in a linear fashion. Known slow sectors such as multisession gaps (session Lead-in area) are skipped. redumper never seeks back and a great care is exercised not to put an excessive wear on the drive. +```console +redumper --drive=E: +``` -If drive is a good PLEXTOR, the session lead-in will be read using a negative LBA range. Several lead-in read attempts are made to ensure data integrity. Sometimes it's possible to capture other session lead-in but there is no direct control of it. +The trailing colon is optional. -A good LG/ASUS/LITE-ON can natively read ~135 out of 150 pre-gap sectors, which is usually enough for 99% of discs. Very rarely, there is some non zero data in early lead-in sectors (TOC area). Such drives are unable to extract this data and a PLEXTOR is needed. +### Linux -A good PLEXTOR can read ~100 lead-out sectors. Good LG/ASUS/LITE-ONs have lead-out sectors locked but these drives usually have a cache read opcode that is used to extract available lead-out sectors. Finally, an ASUS BW-16D1HT flashed with RibShark custom 3.10 firmware has fully unlocked lead-out. All these cases are supported. +The disc must be unmounted before redumper accesses it. Specify the generic SCSI device, not the block device: -In order to accomodate for first session lead-in sectors, primary scrambled dump output (.scram file) prepends an extra space of 45150 sectors (10 minutes, a maximum addressable negative MSF range that can be used to access first session lead-in). +```console +redumper --drive=/dev/sg1 +``` -The resulting dump is drive read offset corrected but not combined offset corrected (the disc write offset is determined at a later track split stage). Sector dump state (SUCCESS / SUCCESS_SCSI_OFF / SUCCESS_C2_OFF / ERROR_C2 / ERROR_SKIP) is stored for each sample. 1 sample is 4 bytes (2 16-bit signed samples of stereo audio data), e.g. for 1 sector there are 588 state values. All this allows an incremental dump improvement with sample granularity using different drives with different read offsets. +Use `/dev/sgN`, rather than `/dev/srN`, because redumper sends SCSI commands that change drive state, such as the requested read speed. If automatic drive detection does not work on a distribution whose sysfs layout differs from the kernel convention, pass the device path explicitly. Disabling removable-media automounting is recommended on dedicated dumping systems. -Subchannel data is stored uncorrected RAW (multiplexed). Both TOC-based and Subchannel Q-based splits are supported; subchannel Q is corrected in memory and never stored on disk. This allows to keep subchannel-based protection schemes (libcrypt, SecuROM etc.) for a later analysis, as well as future R-W packs extraction (CD+G, CD+MIDI). Disc write offset detection is calculated based on a variety of methods: data track as an addressing difference between data sector MSF and subchannel Q MSF, data / audio track intersection of BE read method was used, silence based Perfect Audio Offset detection, CDi-Ready data in index 0 offset detection. The split will fail if the track sector range contains SCSI/C2 errors or is inaccessible, e.g.: a track split of an LG/ASUS dump without cache lead-out data if the combined offset is positive. +### macOS -## GENERIC Drives Feature Evaluation Guide -### General -This is a guide that will help you to evaluate your GENERIC drive features for CD dumping and make it work with redumper manually. -I do not provide support and I don't need feedback. Please do not create issues related to adding GENERIC drives to redumper source code - I don't have bandwidth for that. +Specify the drive by its BSD name, which can be found with `diskutil list`: -### Requirements -Any not too scratched mixed-mode disc where the first track is a data track and following tracks are audio tracks will work (e.g. no multisession). +```console +redumper --drive=disk2 +``` -### Automatic Evaluation -Redumper supports automated GENERIC drive feature detection using `redumper drive::test` after inserting the mixed-mode disc. +The macOS archive contains `bin/redumper` together with private C++ runtime libraries under `lib`. The executable resolves those libraries relative to its own location through `@executable_path/../lib`, so extract and move the complete directory tree rather than copying the executable by itself. -Some GENERIC drives may have unexpected behaviour or hang when redumper attempts to detect custom firmware features. If you encounter this issue, power cycle your drive and use `--drive-test-skip-plextor-leadin` to skip the "PLEXTOR lead-in" step and/or `--drive-test-skip-cache-read` to skip the "MEDIATEK cache read" step. +macOS 10.15 and later place Desktop, Documents, and Downloads behind its [privacy-controlled Files and Folders policy](https://support.apple.com/guide/security/controlling-app-access-to-files-in-macos-secddd1d86a6/web). That policy applies to command-line programs through the terminal application that launched them. redumper also requires a lower-level operation than normal file access: after unmounting the disc, it asks IOKit to create an MMC device user client, obtains a `SCSITaskDeviceInterface`, and requests exclusive access so it can send raw SCSI commands directly to the drive. -### Manual Evaluation Steps -**1. Identify drive sector order** +When redumper is launched from a privacy-controlled directory and macOS blocks creation of that IOKit user-client plug-in, `IOCreatePlugInInterfaceForService` returns `kIOReturnNoResources`. IOKit formats that generic status as `(iokit/common) resource shortage`; in this situation it does not indicate that the Mac is low on memory or disk space. redumper reports the failure as: -According to SCSI MMC specifications, RAW CD sector order is data-c2-subcode (DATA_C2_SUB). In practice, this is not always the case. For some drives it's data-subcode-c2 (DATA_SUB_C2). This is complicated by the fact that there are drives that don't support C2 error vectors and there are drives that don't support subcode (subchannel) reading. Some, but not all, drive features can be queried using SCSI GET_CONFIGURATION and MODE_SENSE commands but again, sometimes these declarations don't match what the drive firmware actually does. For these reasons, redumper doesn't use any feature information from the drive, instead, the logic is based on executing the actual commands and checking returned status codes. It's recommended to always specify the drive speed as some drives don't have good defaults (too slow or too fast) and in particular for this test it's advised to keep drive speed on the lower end so 8 is a good default. Setting drive type to GENERIC is absolutely required as by de fault redumper accepts only known good drives. The default drive sector order is DATA_C2_SUB but it's specified here to make it obvious. The other arguments are making redumper verbose, allowing overwrite of output files and specifying the default dump name. Dump files will be overwritten by the subsequent redumper invocations and after the test, dump files can be safely deleted - they don't serve any purpose. redumper can be killed any moment from the console by sending it SIGINT signal (Ctrl+C); there is no need to wait for the full dump if it's obvious that something is not right. +```text +error: failed to create service plugin interface, MACH ((iokit/common) resource shortage) +``` -Run `redumper dump --speed=8 --drive-type=GENERIC --drive-sector-order=DATA_C2_SUB --verbose --overwrite --image-name=drive_test` and analyze the output. 3 outcomes are possible: +Move the entire extracted redumper directory to a normal local location outside Desktop, Documents, and Downloads—for example `~/Applications/redumper`—and run `bin/redumper` from there. This both preserves the executable-to-library layout and avoids the privacy boundary while redumper establishes its IOKit SCSI connection. - - normal dump operation, sector counter steadily grows, "Q" counter is low and grows slowly (some subcode errors are likely), ocassional SCSI/C2 errors are possible: This is a likely outcome and an indication that drive sector order is successfully identified. Wait until the dump process finishes to make sure that the same sector order works for audio sectors and you can proceed to the next step - - mass SCSI errors are an indication of unsupported C2 or subcode: Restart the test with --drive-sector-order=DATA_SUB (no C2 support is more likely). If that didn't help, restart again with --drive-sector-order=DATA_C2 (no subcode support, less likely) - - mass C2 errors, sector, "SCSI/C2" and "Q" counters are growing equally fast: This hints at a swapped C2/subcode order. Restart the test with --drive-sector-order=DATA_SUB_C2 to confirm the assumption +## Drive support -**2. Identify if drive can read lead-in** +For accurate CD dumping, use a drive recognized by redumper. Compatible PLEXTOR drives use vendor-specific negative-range D8 CDDA access, which allows redumper to recover the complete disc lead-in; supported LG, ASUS, LITE-ON, and other MediaTek-based drives use the BE CDDA path and, where available, vendor-specific cache access. -Lead-in consists of the Table of Contents (TOC) and 2 seconds of pre-gap. In general, TOC is not interesting as data from there is accessible using READ_TOC SCSI command. On the other hand, being able to dump pre-gap is very important for audio discs for two reasons: shifted first track data due to a negative combined offset (left) or HTOA (Hidden Track One Audio) which can use whole 2 seconds of pre-gap. It's less important for data discs as the negative combined offset is compensated by the drive firmware if the BE read command is used (more about that later) and according to the specifications, the pre-gap of a data track should be zeroed (but scrambled) and it really is in almost all cases. +The authoritative drive database is maintained in [`drive.ixx`](drive.ixx). The installed executable can display it directly: -If redumper doesn't know the GENERIC drive, by default it reads starting from LBA 0 which usually is the first sector of the first track. When the previous step was executed to identify the drive sector order, we omitted setting it not to affect sector order detection. Now that the sector order is known, we can safely test it. As was mentioned earlier, the total pre-gap size is 2 seconds, which is 150 sectors total (1 second is 75 sectors). By using a negative LBA range [-150..-1] we can access the disc pre-gap. In theory it's all good, but in practice most drives are not able to access early pre-gap sectors and there is a pretty reasonable explanation for that. As the drive reads discs from left to right (from center outward), in order to seek the requested sector LBA, the drive has to position "somewhere earlier" in that area (left) and using subcode Q positional information, and read (from left to right) until it finds the requested LBA. The problem is, that the pre-gap is precee ded by the TOC and TOC information is stored in subcode Q using a different format. For example, there is no positional information there so by seeking "before" the pre-gap the drive doesn't know where is it located and gets confused, will try to reposition, read again and so on. That said, usually skipping the first 15 pre-gap sectors is a good default for the next test. +```console +redumper --list-recommended-drives +redumper --list-all-drives +``` -Run `redumper dump --speed=8 --drive-type=GENERIC --drive-sector-order=<sector_order_from_previous_step> --drive-pregap-start=-135 --verbose --overwrite --image-name=drive_test` and analyze the output. A number of outcomes are possible: +Generic MMC drives can be used experimentally, but firmware limitations may prevent a complete or byte-perfect CD dump. DVD, HD DVD, and Blu-ray dumping generally does not require a specific model. Xbox and Xbox 360 DVD media are the exception and require a compatible drive with Kreon or OmniDrive firmware. - - no SCSI errors, drive dumps specified negative LBA pre-gap range and continues dumping from LBA 0: All set, the specified value is the earliest pre-gap LBA the drive can dump, proceed to the next step - - SCSI errors, error number is equal to the specified pre-gap start (for -135 it will be 135 errors): Drive firmware blocks negative LBA range and no pre-gap sectors are accessible at all, proceed to the next step - - SCSI errors, error number is less than the specified pre-gap start: Reduce pre-gap start value and try again - - sector counter froze and drive makes weird noises: Reduce pre-gap start value and restart the test - - some drives can read pre-gap but give a single SCSI error for a single LBA such as -1: Just use your best judgement, redumper can handle anything and it will mark such a sector as inaccessible and if this particular sector is required later (depending on a track type / combined offset / split options) it will fail on a track split, and even that can be overriden, for science of course. +Compatible drives running [OmniDrive](https://github.com/RibShark/OmniDrive) firmware can use redumper's raw DVD and Blu-ray acquisition paths. Enable them explicitly with `--dvd-raw` or `--bd-raw`: -**3. Identify if drive can read lead-out** +```console +redumper disc --drive=<drive> --dvd-raw +redumper disc --drive=<drive> --bd-raw +``` -Being able to dump a few lead-out sectors is sometimes important for discs with a positive combined offset (shifted right). There can be non-zero sample values if the last track is an audio track or some non-zero data bytes in case of a data track. Depending on a combination of drive read offset and disc write offset, the missing amount can be quite significant, more than 1 sector. The exact conditions where lead-out sectors are needed depend on the combined offset, the drive read method and the nature of data stored at the end of the last track. It often can be audio silence or empty late data sectors which can be easily generated but there is no guarantee. +OmniDrive also enables redumper to dump Xbox, Xbox 360, Nintendo GameCube, Wii, and Wii U discs. redumper detects these proprietary formats and automatically uses the appropriate raw acquisition path; Nintendo discs are also descrambled as part of the dump. No additional raw-mode option is required. -redumper is greedy, it tries to overread lead-out sectors while it can. It stops reading either on a SCSI error (usually blocked by firmware) or on a slow sector (a session boundary or a disc edge). +### Test an unknown drive -This test is quite simple, run `redumper dump --speed=8 --drive-type=GENERIC --drive-sector-order=<sector_order_from_previous_step> --drive-pregap-start=<pregap_start_from_previous_step> --verbose --overwrite --image-name=drive_test` +`drive::test` replaces the former manual generic-drive evaluation procedure. Insert a known-good mixed-mode CD containing at least one data track and one audio track, then run: -Note the total sectors count value at start, the number after sector counter "/" symbol and let the dump process complete. After redumper exits, compare the same total sectors count value - if it grew, the difference would be the number of lead-out sectors the drive successfully read. An unlikely outcome will be that drive never stops, the total sectors count value continuously grows and the "Q" counter is stale. This means that the drive firmware is buggy and it just returns the last valid sector value from the cache, so kill the process. +```console +redumper drive::test --drive=<drive> +``` -**4. Identify better drive read method** +The test probes supported READ CD variants and sector layouts, D8 support, PLEXTOR lead-in access, readable pre-gap and lead-out ranges, and the MediaTek F1 cache-read command. Add `--verbose` to include SCSI failures. If a drive hangs while a vendor-specific probe is attempted, power-cycle it and skip that probe on the next run: -The previous steps were executed with the default drive read method BE. BE is guaranteed to be supported by all drives but has a number of disadvantages such as sync aligned and possibly corrected data sectors by the drive firmware and inability to read sectors on a data/audio track boundary. Aligned data sectors mean that it won't be possible to detect a true disc write offset based on a data track. This is very important for a mixed-mode disc as disc write offset is used to shift audio tracks appropriately and that is an absolute requirement for a perfect dump. +```console +redumper drive::test --drive=<drive> --drive-test-skip-plextor-leadin +redumper drive::test --drive=<drive> --drive-test-skip-cache-read +``` -There are two alternative read methods that might be supported by the drive, D8 and BE CDDA. Both rely on a concept where data sectors are read as audio sectors thus preventing drive data track offset correction and possible data sector altering. D8 used to be a legacy command to read audio sectors from early SCSI MMC specifications. Some drives like good PLEXTORs preserved such a command and it's unlocked for any sector type, that's why a good PLEXTOR is awesome! The next best way is to use BE read method but specify CDDA expected sector type for all sectors. According to the SCSI MMC specifications, BE CDDA expected sector type setting should return a SCSI error if a data sector is encountered, but some drives like good LG/ASUS do not enforce that requirement and that's why a good LG/ASUS is nice too! +The normal `disc` workflow can also attempt to detect the sector order of an unrecognized generic drive: -The BE CDDA read method will always be attempted during a dump, and if it fails, redumper will fallback to BE. +```console +redumper disc --drive=<drive> --drive-type=GENERIC --auto-detect +``` -To test for the D8 read method, run `redumper dump --speed=8 --drive-type=GENERIC --drive-sector-order=<sector_order_from_previous_step> --drive-pregap-start=<pregap_start_from_previous_step> --drive-read-method=D8 --verbose --overwrite --image-name=drive_test` +## Common workflows -Your chances of success with the D8 read method grow if it's an early drive (or a PLEXTOR in disguise). If you get no SCSI errors, congratulations, this is the best read method available, go get yourself a beer! If you get mass SCSI errors, the last thing to try is to exclude C2 from the drive sector order and restart the test, as some drives are unable to use C2 in that mode. +### Choose the output location -**5. Is it a good PLEXTOR or a good LG/ASUS rebadge?** +```console +redumper disc --drive=F: --retries=100 --image-name=my_dump --image-path=my_dump_directory +``` -If the D8 read method is available, it might be a good hint that the drive is a PLEXTOR in disguise. There are good PLEXTORs in QPS external enclosures, HP and Creative rebadges, and some other rebadge rumors. If the BE_CDDA read method is available and the drive can read at least 135 pre-gap sectors, there is a chance that the drive might be a good LG/ASUS in disguise, for instance a recently found LITE-ON. There are a few cache partition configurations possible, all based on 8 MB and 3 MB cache sizes. +This dumps the disc in drive `F:`, retries erroneous sectors up to 100 times, and writes files using `my_dump` as the basename under `my_dump_directory`. Add `--verbose` for detailed diagnostics. -By setting the --drive-type value, you can instruct redumper to use good drive specific features, such as reading lead-in using PLEXTOR negative range or reading lead-out from LG/ASUS cache. +Options accept either `--option=value` or `--option value` syntax. -To check for a good PLEXTOR, run `redumper disc --speed=8 --drive-type=PLEXTOR --drive-sector-order=<sector_order_from_previous_step> --drive-pregap-start=<pregap_start_from_previous_step> --drive-read-method=D8 --verbose --overwrite --image-name=drive_test` +### Refine an existing dump -Look for a "PLEXTOR: reading lead-in" message. If the process ends with a "PLEXTOR: lead-in found" message, this is a good PLEXTOR. If the LBA counter decreases all the way and it's at least a minute long, you can kill the process, as it's not a good PLEXTOR. +```console +redumper refine --drive=G: --speed=8 --retries=500 --image-name=my_dump --image-path=my_dump_directory +``` -To check for a good LG/ASUS, run `redumper disc --speed=8 --drive-type=LG_ASU8A --drive-sector-order=<sector_order_from_previous_step> --drive-pregap-start=<pregap_start_from_previous_step> --verbose --overwrite --image-name=drive_test` +This re-reads unresolved areas of an existing dump, possibly with a different drive or speed. Keep the same image path and name so redumper can find the dump state created by the initial pass. -Look for a "LG/ASUS: searching lead-out in cache" message which will appear right after reading the last LBA. If the next message is "LG/ASUS: lead-out found", it's a good LG/ASUS. If you get "error: read cache failed", there is no read cache command and it's not a good LG/ASUS. +### Force a track split -A proper distinction between different cache partition configurations is very important but it requires manual cache dump analysis. In a nutshell, first you have to establish the true cache size. The above command hardcodes an 8 MB cache size as it's the maximum possible size. If the cache size is smaller, for example 3 MB, and it's being read as 8 MB, it wraps around by the firmware. Basically you have to open the .cache file in a hex editor and determine where it starts repeating data. For example, search for the next appearance of the first 8 bytes you see at the start of the file. If you found a match, it's address will basically be the cache size. If it's not found, it's likely a 8 MB cache size. Even if the cache size is the same, multiple partition configurations are possible. Some are already enumerated [HERE](drive.ixx#L256), where the first value is the cache size in MB, and the second value is a number of sectors that are stored in cache. Determining the second value is o ut of scope here - it requires parsing cache subchannel to see where the boundary is. I have some tools for that but it's not user friendly at the moment. When you determine the cache size, predefined cache configurations most likely will work but that requires extensive testing using discs with various write offsets and comparing to a known good dump. +```console +redumper split --force-split --image-name=my_dump --image-path=my_dump_directory +``` -## Flashing drive firmware +Normally, track splitting fails if required sectors contain errors or are inaccessible. `--force-split` generates the BIN/CUE output anyway for analysis or experimentation; it should not be treated as a verified dump. -redumper can be used to flash the firmware on certain drive models. At the moment, this is limited to drives that use the MT1339 chipset, such as the TS-H353C. Other drive models may be supported in the future. +## How CD dumping works -Although this function has been used successfully by many users, flashing is inherently a dangerous process and is done at your own risk. redumper does not do any checks that the firmware file you provide is correct for the drive model, or that the drive model is of the correct chipset. Providing an incorrect firmware file for your drive, or trying to flash an unsupported drive, may permanently brick your drive. Losing the data connection to the drive during flashing, or losing power during flashing, may also permanently brick your drive. Flashing takes a few seconds in most cases. +Compatible drives read every track as audio, including data tracks, using D8 or BE CDDA commands. The initial pass proceeds linearly from beginning to end and skips known slow regions such as multisession gaps. Avoiding backward seeks during this pass reduces unnecessary drive wear. -Flashing can be done using `redumper flash::mt1339 --drive=<drive> --firmware=<filename>` where the drive is specified using the syntax described above specific to the operating system you are using. The firmware file should be in `.bin` format. +Supported PLEXTOR drives can read session lead-in through a negative LBA range. Many supported MediaTek-based drives read about 135 of the 150 pre-gap sectors and may recover otherwise locked lead-out sectors through a cache-read command. Some rare discs contain non-zero data earlier in the lead-in and therefore require a capable PLEXTOR drive. -## Examples -**1.** +The primary scrambled CD dump (`.scram`) reserves 45,150 sectors before LBA 0: ten minutes, the largest negative MSF range addressable for first-session lead-in access. -`redumper` +redumper corrects the drive read offset during acquisition. The combined offset remains uncorrected until splitting, when the disc write offset is derived. Dump state is recorded for every stereo sample—588 state values per CD sector—using states such as `SUCCESS`, `SUCCESS_SCSI_OFF`, `SUCCESS_C2_OFF`, `ERROR_C2`, and `ERROR_SKIP`. This allows later refinement at sample granularity with drives that have different read offsets. -If run without arguments, redumper will use the first available supported drive with a disc inside and "disc" aggregate mode. The image name will be autogenerated based on a date/time and drive path and will be put in the current process working directory. If you have two drives and the first drive is already busy dumping, running redumper again will dump the disc in the next available drive and so on, for easy daisy chaining. Please take into account that no SCSI/C2 rereads will be performed on errors unless you specify --retries=100, but don't worry as it won't let you split to tracks if tracks have errors. +Subchannel data is stored raw, multiplexed, and uncorrected. redumper can split from either the drive TOC or corrected Subchannel Q information without rewriting the stored subchannel data. This preserves protection-related data, including LibCrypt and SecuROM patterns, for later analysis and leaves room for future extraction of R-W content such as CD+G and CD+MIDI. -**2.** +Write-offset detection combines several available signals, including the difference between data-sector MSF and Subchannel Q MSF, data/audio boundaries, silence-based Perfect Audio Offset detection, and CD-i Ready data in index 0. Unless forced, splitting stops when its required sector range contains SCSI/C2 errors or cannot be read. -`redumper disc --verbose --drive=F: --retries=100 --image-name=my_dump_name --image-path=my_dump_directory` +## Firmware flashing -or (you can use spaces and = interchangeably) +redumper contains flashing commands for selected MT1339, MT1959, Samsung SD-616F/T, and PLEXTOR drives: -`redumper disc --verbose --drive F: --retries 100 --image-name my_dump_name --image-path my_dump_directory` +```console +redumper flash::mt1339 --drive=<drive> --firmware=<firmware.bin> +redumper flash::mt1959 --drive=<drive> --firmware=<firmware.bin> +redumper flash::sd616 --drive=<drive> --firmware=<firmware.bin> +redumper flash::plextor --drive=<drive> --firmware=<firmware.bin> +``` -This will dump a disc in drive F: with 100 retries count in case of errors (refine). The dump files will be stored in the my_dump_directory directory, dump files will have a my_dump_name base name, and you will get verbose messages. +> [!WARNING] +> Firmware flashing is inherently dangerous and can permanently brick a drive. Use only firmware intended for the exact drive and chipset, keep the drive connected throughout the operation, and ensure power cannot be interrupted. `--force-flash` bypasses vendor/model verification and increases the risk; use it only when you fully understand the target hardware. -**3.** +## Building from source -`redumper refine --verbose --drive=G: --speed=8 --retries=500 --image-name=my_dump_name --image-path=my_dump_directory` +redumper uses C++20 modules. The CI-supported build environments use: -This will refine a previous dump from the my_dump_directory with a base name of my_dump_name on a different drive using lowest speed with 500 retries count. +- CMake 3.28 or newer +- LLVM/Clang 18 and Ninja on Linux and macOS +- Microsoft Visual Studio 2022 on Windows -**4.** +The checked-in toolchain files under [`cmake/toolchains`](cmake/toolchains) define the supported platform and architecture combinations. For example, an x64 Linux release build is configured and tested with: -`redumper split --verbose --force --image-name=my_dump_name --image-path=my_dump_directory` +```console +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/linux-x64.cmake +cmake --build build +ctest --test-dir build --output-on-failure +``` -This will force generation of track split with track errors, just because you really want to see and experiment with unscrambled tracks. +On x64 Windows with Visual Studio 2022: -## Building from Source +```console +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/windows-x64.cmake +cmake --build build --config Release +ctest --test-dir build -C Release --output-on-failure +``` -redumper uses C++ modules, which requires a C++20 compiler and build tools: -- LLVM/Clang 18 (Linux & macOS) or Microsoft Visual Studio 2022 (Windows) -- Ninja 1.11 (Linux & macOS only) -- CMake 3.28 +If a suitable GoogleTest installation is not available, the test configuration downloads it during configuration and requires network access at that stage. The complete release matrix is in [`.github/workflows/cmake.yml`](.github/workflows/cmake.yml). -For detailed build instructions, please check my GitHub Actions workflow [HERE](.github/workflows/cmake.yml). I do not provide a Docker container and I will not be able to troubleshoot your build issues. If my GitHub Actions CI/CD can build it, so can you. +## Author -## Contact -Discord: **supermegag** +redumper is created and maintained by [Hennadiy Brych](https://github.com/superg). Discord: **supermegag**. + +## Need help? + +Start with `redumper --help` and, for drive questions, include the output of `redumper --list-all-drives` or `redumper drive::test --verbose` as applicable. For bugs and project questions, open an issue in the [redumper issue tracker](https://github.com/superg/redumper/issues) and include the exact command plus the complete error or warning output. Do not upload or attach copyrighted disc contents. + +## License + +redumper is free software licensed under the [GNU General Public License, version 3](LICENSE). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/cd/cd_split.ixx new/redumper-b746/cd/cd_split.ixx --- old/redumper-b734/cd/cd_split.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/cd/cd_split.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -287,11 +287,9 @@ } -std::vector<std::string> write_tracks(Context &ctx, const TOC &toc, std::fstream &scm_fs, std::fstream &state_fs, std::shared_ptr<const OffsetManager> offset_manager, - const std::vector<Range<int32_t>> &protection, bool dreamcast, const Options &options) +void write_tracks(Context &ctx, const TOC &toc, std::fstream &scm_fs, std::fstream &state_fs, std::shared_ptr<const OffsetManager> offset_manager, const std::vector<Range<int32_t>> &protection, + bool dreamcast, const Options &options) { - std::vector<std::string> xml_lines; - Scrambler scrambler; std::vector<uint8_t> sector(CD_DATA_SIZE); std::vector<State> state(CD_DATA_SIZE_SAMPLES); @@ -416,11 +414,12 @@ // LOG("debug: scram offset: {:08X}", debug_get_scram_offset(d.first, write_offset)); } - xml_lines.emplace_back(rom_entry.xmlLine()); + if(lilo) + ctx.dat_extra.emplace_back(rom_entry.xmlLine()); + else + ctx.dat.emplace_back(rom_entry.xmlLine()); } } - - return xml_lines; } @@ -1439,7 +1438,7 @@ // write tracks LOG("writing tracks"); - ctx.dat = write_tracks(ctx, toc, scm_fs, state_fs, offset_manager, protection, dreamcast, options); + write_tracks(ctx, toc, scm_fs, state_fs, offset_manager, protection, dreamcast, options); LOG("done"); LOG(""); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/cd/fix_msf.ixx new/redumper-b746/cd/fix_msf.ixx --- old/redumper-b734/cd/fix_msf.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/cd/fix_msf.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -182,4 +182,103 @@ return exit_code; } + +export int redumper_fix_msf_shift(Context &ctx, Options &options) +{ + int exit_code = 0; + + auto image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); + + if(!std::filesystem::exists(image_prefix + ".cue")) + throw_line("no files to process"); + + auto tracks = cue_get_entries(image_prefix + ".cue"); + if(tracks.size() != 1 + || (tracks.front().second != TrackType::MODE1_2352 && tracks.front().second != TrackType::MODE2_2352 && tracks.front().second != TrackType::CDI_2352 + && tracks.front().second != TrackType::MODE0_2352)) + throw_line("no files to process"); + + auto t = std::filesystem::path(options.image_path) / tracks.front().first; + uint32_t sectors_count = std::filesystem::file_size(t) / sizeof(Sector); + std::fstream fs(t, std::fstream::binary | std::fstream::in); + std::fstream fs_out(t.string() + ".fixed", std::fstream::binary | std::fstream::out); + + int32_t lba_base; + + for(uint32_t s = 0; s < sectors_count; ++s) + { + Sector sector; + fs.read((char *)§or, sizeof(sector)); + if(fs.fail()) + throw_line("read failed"); + + int32_t lba = BCDMSF_to_LBA(sector.header.address); + if(!s) + lba_base = lba; + + fs_out.seekp((lba - lba_base) * sizeof(Sector)); + if(fs_out.fail()) + throw_line("read failed"); + fs_out.write((char *)§or, sizeof(sector)); + if(fs_out.fail()) + throw_line("write failed"); + } + + return exit_code; +} + + +export int redumper_trim(Context &ctx, Options &options) +{ + int exit_code = 0; + + auto image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); + + if(!std::filesystem::exists(image_prefix + ".cue")) + throw_line("no files to process"); + + auto tracks = cue_get_entries(image_prefix + ".cue"); + if(tracks.size() != 1 + || (tracks.front().second != TrackType::MODE1_2352 && tracks.front().second != TrackType::MODE2_2352 && tracks.front().second != TrackType::CDI_2352 + && tracks.front().second != TrackType::MODE0_2352)) + throw_line("no files to process"); + + auto t = std::filesystem::path(options.image_path) / tracks.front().first; + uint32_t sectors_count = std::filesystem::file_size(t) / sizeof(Sector); + std::fstream fs(t, std::fstream::binary | std::fstream::in); + std::fstream fs_out(t.string() + ".fixed", std::fstream::binary | std::fstream::out); + + bool pvd_found = false; + + for(uint32_t s = 0; s < sectors_count; ++s) + { + Sector sector; + fs.read((char *)§or, sizeof(sector)); + if(fs.fail()) + throw_line("read failed"); + + auto [data, data_size] = data_sector_get_data(sector); + if(!pvd_found && data_size >= sizeof(iso9660::PrimaryVolumeDescriptor)) + { + auto const &pvd = *(iso9660::PrimaryVolumeDescriptor *)data; + auto identifier = to_string_view(pvd.standard_identifier); + + if(pvd.type == iso9660::VolumeDescriptorType::PRIMARY && (identifier == iso9660::DESCRIPTOR_ID_CD || identifier == iso9660::DESCRIPTOR_ID_CDI)) + { + sectors_count = pvd.volume_space_size.lsb; + pvd_found = true; + } + } + + fs_out.write((char *)§or, sizeof(sector)); + if(fs_out.fail()) + throw_line("write failed"); + } + + if(!pvd_found) + throw_line("primary volume descriptor not found"); + + return exit_code; +} + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/cd/toc.ixx new/redumper-b746/cd/toc.ixx --- old/redumper-b734/cd/toc.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/cd/toc.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -821,6 +821,9 @@ void initTOC(const std::vector<uint8_t> &toc_buffer) { + if(toc_buffer.size() < sizeof(CMD_ParameterListHeader)) + return; + // don't rely on anything and sort by session number / track number std::map<uint8_t, Session::Track> tracks; @@ -864,6 +867,9 @@ void initFullTOC(const std::vector<uint8_t> &toc_buffer) { + if(toc_buffer.size() < sizeof(CMD_ParameterListHeader)) + return; + // don't rely on anything and sort by session number / track number std::map<uint8_t, std::map<uint8_t, Session::Track>> tracks; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/common.ixx new/redumper-b746/common.ixx --- old/redumper-b734/common.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/common.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -70,7 +70,8 @@ std::optional<bool> protection_trim; RefineCache refine_cache; std::optional<bool> refine; - std::optional<std::vector<std::string>> dat; + std::vector<std::string> dat; + std::vector<std::string> dat_extra; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/drive/detect.ixx new/redumper-b746/drive/detect.ixx --- old/redumper-b734/drive/detect.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/drive/detect.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -1,107 +1,120 @@ -module; -#include <algorithm> -#include <cstdint> -#include <string> -#include <vector> - -export module drive.detect; - -import cd.cd; -import cd.common; -import cd.subcode; -import cd.toc; -import common; -import drive; -import options; -import scsi.cmd; -import scsi.mmc; -import scsi.sptd; -import utils.logger; -import utils.strings; - - -namespace gpsxre -{ - - -export bool detect_sector_order(SPTD &sptd, DriveConfig &drive_config, int32_t lba) -{ - std::vector<uint8_t> sector_buffer(CD_RAW_DATA_SIZE); - - for(auto const &order : SECTOR_ORDER_STRING) - { - auto test_layout = sector_order_layout(order.first); - - auto error_field = test_layout.c2_offset == CD_RAW_DATA_SIZE ? READ_CD_ErrorField::NONE : READ_CD_ErrorField::C2; - auto sub_channel = test_layout.subcode_offset == CD_RAW_DATA_SIZE ? READ_CD_SubChannel::NONE : READ_CD_SubChannel::RAW; - - // try read as-audio first, then fallback to as-data - auto status = cmd_read_cd(sptd, sector_buffer.data(), CD_RAW_DATA_SIZE, lba, 1, READ_CD_ExpectedSectorType::CD_DA, error_field, sub_channel); - if(status.status_code) - status = cmd_read_cd(sptd, sector_buffer.data(), CD_RAW_DATA_SIZE, lba, 1, READ_CD_ExpectedSectorType::ALL_TYPES, error_field, sub_channel); - if(status.status_code) - continue; - - // validate by checking subcode Q CRC at the expected offset - if(test_layout.subcode_offset != CD_RAW_DATA_SIZE) - { - if(subcode_extract_q(sector_buffer.data() + test_layout.subcode_offset).isValid()) - { - // C2 should be zeroed assuming a clean first sector - if(test_layout.c2_offset != CD_RAW_DATA_SIZE) - { - auto c2_start = sector_buffer.data() + test_layout.c2_offset; - if(std::any_of(c2_start, c2_start + CD_C2_SIZE, [](uint8_t v) { return v != 0; })) - continue; - } - - drive_config.sector_order = order.first; - LOG("GENERIC: auto-detected sector order: {}", order.second); - return true; - } - } - else - { - // no subcode, validate DATA_C2 order - if(test_layout.c2_offset != CD_RAW_DATA_SIZE) - { - auto c2_start = sector_buffer.data() + test_layout.c2_offset; - if(std::all_of(c2_start, c2_start + CD_C2_SIZE, [](uint8_t v) { return v == 0; })) - { - drive_config.sector_order = order.first; - LOG("GENERIC: auto-detected sector order: {} (no subcode validation)", order.second); - return true; - } - } - } - } - - return false; -} - - -export int redumper_drive_detect(Context &ctx, Options &options) -{ - int exit_code = 0; - - if(ctx.disc_type != DiscType::CD) - return exit_code; - - if(ctx.drive_config.type == Type::GENERIC && ctx.drive_config.read_method == ReadMethod::BE && !options.drive_sector_order) - { - auto toc_buffer = toc_read(*ctx.sptd); - TOC toc(toc_buffer, false); - if(toc.sessions.empty() || toc.sessions.front().tracks.empty()) - { - LOG("warning: sector order detection failed (TOC has no tracks), using default"); - return exit_code; - } - int32_t lba = toc.sessions.front().tracks.front().lba_start; - if(!detect_sector_order(*ctx.sptd, ctx.drive_config, lba)) - LOG("warning: sector order detection failed, using default"); - } - - return exit_code; -} - -} +module; +#include <algorithm> +#include <array> +#include <cstdint> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +export module drive.detect; + +import cd.cd; +import cd.common; +import cd.subcode; +import cd.toc; +import common; +import drive; +import options; +import scsi.cmd; +import scsi.mmc; +import scsi.sptd; +import utils.logger; +import utils.strings; + + +namespace gpsxre +{ + + +// keep detection candidates local and constexpr: optimized C++ module builds can miscompile iteration over the exported, +// dynamically initialized SECTOR_ORDER_STRING map. This also makes the probe order explicit. +static constexpr std::array<std::pair<SectorOrder, std::string_view>, 4> SECTOR_ORDER_CANDIDATES = { + std::pair{ SectorOrder::DATA_C2_SUB, std::string_view{ "DATA_C2_SUB" } }, + std::pair{ SectorOrder::DATA_SUB_C2, std::string_view{ "DATA_SUB_C2" } }, + std::pair{ SectorOrder::DATA_SUB, std::string_view{ "DATA_SUB" } }, + std::pair{ SectorOrder::DATA_C2, std::string_view{ "DATA_C2" } } +}; + + +export bool detect_sector_order(SPTD &sptd, DriveConfig &drive_config, int32_t lba) +{ + std::vector<uint8_t> sector_buffer(CD_RAW_DATA_SIZE); + + for(auto const &[order, name] : SECTOR_ORDER_CANDIDATES) + { + auto test_layout = sector_order_layout(order); + + auto error_field = test_layout.c2_offset == CD_RAW_DATA_SIZE ? READ_CD_ErrorField::NONE : READ_CD_ErrorField::C2; + auto sub_channel = test_layout.subcode_offset == CD_RAW_DATA_SIZE ? READ_CD_SubChannel::NONE : READ_CD_SubChannel::RAW; + + // try read as-audio first, then fallback to as-data + auto status = cmd_read_cd(sptd, sector_buffer.data(), CD_RAW_DATA_SIZE, lba, 1, READ_CD_ExpectedSectorType::CD_DA, error_field, sub_channel); + if(status.status_code) + status = cmd_read_cd(sptd, sector_buffer.data(), CD_RAW_DATA_SIZE, lba, 1, READ_CD_ExpectedSectorType::ALL_TYPES, error_field, sub_channel); + if(status.status_code) + continue; + + // validate by checking subcode Q CRC at the expected offset + if(test_layout.subcode_offset != CD_RAW_DATA_SIZE) + { + if(subcode_extract_q(sector_buffer.data() + test_layout.subcode_offset).isValid()) + { + // C2 should be zeroed assuming a clean first sector + if(test_layout.c2_offset != CD_RAW_DATA_SIZE) + { + auto c2_start = sector_buffer.data() + test_layout.c2_offset; + if(std::any_of(c2_start, c2_start + CD_C2_SIZE, [](uint8_t v) { return v != 0; })) + continue; + } + + drive_config.sector_order = order; + LOG("GENERIC: auto-detected sector order: {}", name); + return true; + } + } + else + { + // no subcode, validate DATA_C2 order + if(test_layout.c2_offset != CD_RAW_DATA_SIZE) + { + auto c2_start = sector_buffer.data() + test_layout.c2_offset; + if(std::all_of(c2_start, c2_start + CD_C2_SIZE, [](uint8_t v) { return v == 0; })) + { + drive_config.sector_order = order; + LOG("GENERIC: auto-detected sector order: {} (no subcode validation)", name); + return true; + } + } + } + } + + return false; +} + + +export int redumper_drive_detect(Context &ctx, Options &options) +{ + int exit_code = 0; + + if(ctx.disc_type != DiscType::CD) + return exit_code; + + if(ctx.drive_config.type == Type::GENERIC && ctx.drive_config.read_method == ReadMethod::BE && !options.drive_sector_order) + { + auto toc_buffer = toc_read(*ctx.sptd); + TOC toc(toc_buffer, false); + if(toc.sessions.empty() || toc.sessions.front().tracks.empty()) + { + LOG("warning: sector order detection failed (TOC has no tracks), using default"); + return exit_code; + } + int32_t lba = toc.sessions.front().tracks.front().lba_start; + if(!detect_sector_order(*ctx.sptd, ctx.drive_config, lba)) + LOG("warning: sector order detection failed, using default"); + } + + return exit_code; +} + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/drive/flash_mt1959.ixx new/redumper-b746/drive/flash_mt1959.ixx --- old/redumper-b734/drive/flash_mt1959.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/drive/flash_mt1959.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -35,6 +35,7 @@ const std::vector<DriveEntry> MT1959_SUPPORTED_DRIVES = { { "HL-DT-ST", "BD-RE BU40N", "MT1959 Boot BU5 " }, + { "HL-DT-ST", "BD-RE BU50N", "MT1959 Boot BU5 " }, // MT1959 Boot BU51 { "ASUS", "BW-16D1HT", "MT1959 Boot JB8 " }, { "ASUS", "BW-16D1H-U", "MT1959 Boot JB8 " }, { "HL-DT-ST", "BD-RE BH16NS60", "MT1959 Boot JB8 " }, // NS60 @@ -142,7 +143,7 @@ auto drive_config = read_mt1959_config(*ctx.sptd, config_offset, config_size); std::string_view drive_id((const char *)drive_config.data(), id_size); - if(drive_id != firmware_id) + if(drive_id != firmware_id && !(drive_id == "MT1959 Boot BU51" && firmware_id == "MT1959 Boot BU5 ")) throw_line("firmware id mismatch (drive: {}, firmware: {})", drive_id, firmware_id); LOG("drive patch configuration (offset: 0x{:04X}, size: 0x{:X}): ", config_offset, config_size); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/drive.ixx new/redumper-b746/drive.ixx --- old/redumper-b734/drive.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/drive.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -214,6 +214,7 @@ { "PLEXTOR" , "CD-R PX-W8220T" , "" , "", "", +355, 294, -75, ReadMethod::D8, SectorOrder::DATA_SUB , Type::PLEXTOR }, { "PLEXTOR" , "CD-R PX-W8432T" , "" , "", "", +355, 294, -75, ReadMethod::D8, SectorOrder::DATA_SUB , Type::PLEXTOR }, // OTHER + { "ASUS" , "SDRW-08D2S-U" , "A812", "", "", +6, 0, 0, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // internal model: LG GP75N { "ASUS" , "SDRW-08D2S-U" , "B901", "", "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_SUB_C2, Type::GENERIC }, // internal model: DU-8A6NH11B { "ASUS" , "SDRW-08U9M-U" , "A112", "", "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_SUB_C2, Type::GENERIC }, { "Lite-On" , "LTN483S 48x Max" , "PD03", "", "", -1164, 0, 0, ReadMethod::BE, SectorOrder::DATA_C2 , Type::GENERIC }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/dvd/dvd_dump.ixx new/redumper-b746/dvd/dvd_dump.ixx --- old/redumper-b734/dvd/dvd_dump.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/dvd/dvd_dump.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -1393,7 +1393,7 @@ signal.raiseDefault(); if(rom_update) - ctx.dat = std::vector<std::string>(1, rom_entry.xmlLine()); + ctx.dat.emplace_back(rom_entry.xmlLine()); return errors.scsi || errors.edc; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/dvd/dvd_split.ixx new/redumper-b746/dvd/dvd_split.ixx --- old/redumper-b734/dvd/dvd_split.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/dvd/dvd_split.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -59,8 +59,7 @@ ROMEntry dmi_rom_entry(dmi_path.filename().string()); dmi_rom_entry.update(manufacturer.data(), FORM1_DATA_SIZE); - if(ctx.dat.has_value()) - ctx.dat->push_back(dmi_rom_entry.xmlLine()); + ctx.dat_extra.emplace_back(dmi_rom_entry.xmlLine()); } else { @@ -88,8 +87,7 @@ ROMEntry pfi_rom_entry(pfi_path.filename().string()); pfi_rom_entry.update(physical.data(), FORM1_DATA_SIZE); - if(ctx.dat.has_value()) - ctx.dat->push_back(pfi_rom_entry.xmlLine()); + ctx.dat_extra.emplace_back(pfi_rom_entry.xmlLine()); } else { @@ -114,8 +112,7 @@ ROMEntry ss_rom_entry(ss_path.filename().string()); ss_rom_entry.update(security.data(), FORM1_DATA_SIZE); - if(ctx.dat.has_value()) - ctx.dat->push_back(ss_rom_entry.xmlLine()); + ctx.dat_extra.emplace_back(ss_rom_entry.xmlLine()); } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/filesystem/udf/udf.ixx new/redumper-b746/filesystem/udf/udf.ixx --- old/redumper-b734/filesystem/udf/udf.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/filesystem/udf/udf.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -1,3 +1,4 @@ export module filesystem.udf; +export import :browser; export import :defs; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/filesystem/udf/udf_browser.ixx new/redumper-b746/filesystem/udf/udf_browser.ixx --- old/redumper-b734/filesystem/udf/udf_browser.ixx 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b746/filesystem/udf/udf_browser.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -0,0 +1,115 @@ +module; +#include <cstdint> +#include <cstring> +#include <memory> +#include <optional> +#include <vector> + +export module filesystem.udf:browser; + +import :defs; + +import cd.cdrom; +import readers.data_reader; + +export namespace gpsxre::udf +{ + +class Browser +{ +public: + const static std::optional<udf::AnchorVolumeDescriptorPointer> findAnchorVolumeDescriptorPointer(DataReader *data_reader) + { + std::vector<uint8_t> data(FORM1_DATA_SIZE); + data_reader->read(data.data(), AVDP_PRIMARY_LBA, 1); + + auto const &avdp = (udf::AnchorVolumeDescriptorPointer &)data[0]; + + if(avdp.descriptor_tag.tag_identifier == udf::TagIdentifier::ANCHOR_POINTER) + { + return avdp; + } + else + { + return {}; + } + } + + const static std::optional<uint32_t> findDescriptorOffset(DataReader *data_reader, ExtentDescriptor main_vds, TagIdentifier type) + { + for(uint32_t lba = main_vds.location; lba <= main_vds.location + ((main_vds.length - 1) / FORM1_DATA_SIZE); ++lba) + { + std::vector<uint8_t> data(FORM1_DATA_SIZE); + data_reader->read(data.data(), lba, 1); + + auto const &tag = (udf::DescriptorTag &)data[0]; + + if(tag.tag_identifier == type) + { + return lba; + } + else if(tag.tag_identifier == udf::TagIdentifier::TERMINATING) + { + break; + } + } + + return {}; + } + + static std::shared_ptr<udf::FileEntry> rootDirectory(DataReader *data_reader) + { + // Try and find the AVDP so we can look through the volume descriptor sequence. + auto const avdp = findAnchorVolumeDescriptorPointer(data_reader); + if(avdp.has_value()) + { + // Search for a partition in the volume descriptor sequence. This is probably partition 0. + auto partition_offset = findDescriptorOffset(data_reader, avdp.value().main_vds, udf::TagIdentifier::PARTITION); + if(!partition_offset.has_value()) + { + return {}; + } + std::vector<uint8_t> partition_data(FORM1_DATA_SIZE); + + data_reader->read(partition_data.data(), partition_offset.value(), 1); + struct udf::PartitionDescriptor partition_descriptor = (udf::PartitionDescriptor &)partition_data[0]; + + // Search for the logical volume descriptor. This contains the extent of the file set descriptor. + auto lvd_offset = findDescriptorOffset(data_reader, avdp.value().main_vds, udf::TagIdentifier::LOGICAL); + if(!lvd_offset.has_value()) + { + return {}; + } + std::vector<uint8_t> lvd_data(FORM1_DATA_SIZE); + data_reader->read(lvd_data.data(), lvd_offset.value(), 1); + struct udf::LogicalVolumeDescriptor logical_volume_descriptor = (udf::LogicalVolumeDescriptor &)lvd_data[0]; + + auto const &file_set_descriptor_extent = (udf::long_ad &)logical_volume_descriptor.logical_volume_contents_use; + + // If we have the partition the file set descriptor is found in, parse it to find the root directory ICB extent. + if(partition_descriptor.partition_number == file_set_descriptor_extent.extent_location.partition_reference_number) + { + std::vector<uint8_t> fsd_data(FORM1_DATA_SIZE); + data_reader->read(fsd_data.data(), partition_descriptor.partition_starting_location + file_set_descriptor_extent.extent_location.logical_block_number, 1); + auto const &file_set_descriptor = (udf::FileSetDescriptor &)fsd_data[0]; + + auto const &root_directory_icb_extent = file_set_descriptor.root_directory_icb; + + // If we have the partition the root directory is in, read and return it. + if(partition_descriptor.partition_number == root_directory_icb_extent.extent_location.partition_reference_number) + { + std::vector<uint8_t> root_directory_data(FORM1_DATA_SIZE); + data_reader->read(root_directory_data.data(), partition_descriptor.partition_starting_location + root_directory_icb_extent.extent_location.logical_block_number, 1); + + auto root_directory_file_entry = std::shared_ptr<udf::FileEntry>(new udf::FileEntry()); + std::memcpy(root_directory_file_entry.get(), root_directory_data.data(), sizeof(udf::FileEntry)); + return root_directory_file_entry; + } + } + } + + return {}; + } +}; + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/filesystem/udf/udf_defs.ixx new/redumper-b746/filesystem/udf/udf_defs.ixx --- old/redumper-b734/filesystem/udf/udf_defs.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/filesystem/udf/udf_defs.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -64,6 +64,122 @@ uint8_t reserved[480]; }; +struct timestamp +{ + uint16_t type_and_timezone; + int16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t centiseconds; + uint8_t hundreds_of_microseconds; + uint8_t microseconds; +}; + +struct charspec +{ + uint8_t character_set_type; + uint8_t character_set_info[63]; +}; + +struct lb_addr +{ + uint32_t logical_block_number; + uint16_t partition_reference_number; +}; + +struct long_ad +{ + uint32_t extent_length; + lb_addr extent_location; + uint8_t implementation_use[6]; +}; + +struct EntityID +{ + uint8_t flags; + uint8_t identifier[23]; + uint8_t identifier_suffix[8]; +}; + +struct LogicalVolumeDescriptor +{ + DescriptorTag descriptor_tag; + uint32_t volume_descriptor_sequence_number; + charspec descriptor_character_set; + char logical_volume_identifier[128]; + uint32_t logical_block_size; + EntityID domain_identifier; + uint8_t logical_volume_contents_use[16]; + uint32_t map_table_length; + uint32_t number_of_partition_maps; + EntityID implementation_identifier; + uint8_t implementation_use[128]; + ExtentDescriptor integrity_sequence_extent; + uint8_t PartitionMaps[]; +}; + +struct FileSetDescriptor +{ + DescriptorTag descriptor_tag; + timestamp recording_date_and_time; + uint16_t interchange_level; + uint16_t maximum_interchange_level; + uint32_t character_set_list; + uint32_t maximum_character_set_list; + uint32_t file_set_number; + uint32_t file_set_descriptor_number; + charspec logical_volume_identifier_character_set; + char logical_volume_identifier[128]; + charspec file_set_character_set; + char file_set_identifier[32]; + char copyright_file_identifier[32]; + char abstract_file_identifier[32]; + long_ad root_directory_icb; + EntityID domain_identifier; + long_ad next_extent; + long_ad system_stream_directory_icb; + uint8_t reserved[32]; +}; + +struct icbtag +{ + uint32_t prior_recorded_number_of_direct_entries; + uint16_t strategy_type; + uint8_t strategy_parameter[2]; + uint16_t maximum_number_of_entries; + uint8_t reserved; + uint8_t file_type; + lb_addr parent_icb_location; + uint16_t flags; +}; + +struct FileEntry +{ + DescriptorTag descriptor_tag; + icbtag icb_tag; + uint32_t uid; + uint32_t gid; + uint32_t permissions; + uint16_t file_link_count; + uint8_t record_format; + uint8_t record_display_attributes; + uint32_t record_length; + uint64_t information_length; + uint64_t logical_blocks_recorded; + timestamp access_time; + timestamp modification_time; + timestamp attribute_time; + uint32_t checkpoint; + long_ad extended_attribute_icb; + EntityID implementation_identifier; + uint64_t unique_id; + uint32_t length_of_extended_attributes; + uint32_t length_of_allocation_descriptors; + uint8_t extended_attributes_and_allocation_descriptors[]; +}; struct PartitionDescriptor { @@ -71,12 +187,12 @@ uint32_t volume_descriptor_sequence_number; uint16_t partition_flags; uint16_t partition_number; - uint8_t partition_contents[32]; + EntityID partition_contents; uint8_t partition_contents_use[128]; uint32_t access_type; uint32_t partition_starting_location; uint32_t partition_length; - uint8_t implementation_identifier[32]; + EntityID implementation_identifier; uint8_t implementation_use[128]; uint8_t reserved[156]; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/hash.ixx new/redumper-b746/hash.ixx --- old/redumper-b734/hash.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/hash.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -1,4 +1,5 @@ module; +#include <algorithm> #include <filesystem> #include <fstream> #include <list> @@ -21,6 +22,31 @@ namespace gpsxre { +ROMEntry calculate_file_hash(const std::filesystem::path &f) +{ + ROMEntry rom_entry(f.filename().string()); + + std::fstream fs(f, std::fstream::in | std::fstream::binary); + if(!fs.is_open()) + throw_line("unable to open file ({})", f.filename().string()); + + std::vector<uint8_t> data(CHUNK_1MB); // 1Mb chunk + batch_process_range<uint64_t>(std::pair(0, std::filesystem::file_size(f)), data.size(), + [&](uint64_t offset, uint64_t size) -> bool + { + fs.read((char *)data.data(), size); + if(fs.fail()) + throw_line("read failed ({})", f.filename().string()); + + rom_entry.update(data.data(), size); + + return false; + }); + + return rom_entry; +} + + void progress_output(uint64_t byte, uint64_t bytes_count) { char animation = byte == bytes_count ? '*' : spinner_animation(); @@ -33,10 +59,10 @@ { int exit_code = 0; - if(!ctx.dat) - { - auto image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); + auto image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); + if(ctx.dat.empty()) + { std::vector<std::filesystem::path> files; if(std::filesystem::exists(image_prefix + ".cue")) { @@ -46,68 +72,84 @@ else if(std::filesystem::exists(image_prefix + ".iso")) { files.push_back(image_prefix + ".iso"); - - // hash xbox extras - if(std::filesystem::exists(image_prefix + ".dmi")) - files.push_back(image_prefix + ".dmi"); - if(std::filesystem::exists(image_prefix + ".pfi")) - files.push_back(image_prefix + ".pfi"); - if(std::filesystem::exists(image_prefix + ".ss")) - files.push_back(image_prefix + ".ss"); } else throw_line("image file not found"); if(!files.empty()) { - uint64_t byte = 0; + uint64_t bytes = 0; uint64_t bytes_count = 0; for(auto f : files) bytes_count += std::filesystem::file_size(f); - std::vector<std::string> dat; - - std::vector<uint8_t> sector(CD_DATA_SIZE); for(auto f : files) { - std::fstream fs(f, std::fstream::in | std::fstream::binary); - if(!fs.is_open()) - throw_line("unable to open file ({})", f.filename().string()); + progress_output(bytes, bytes_count); - ROMEntry rom_entry(f.filename().string()); + auto rom_entry = calculate_file_hash(f); + bytes += rom_entry.getSize(); - std::vector<uint8_t> data(CHUNK_1MB); // 1Mb chunk - batch_process_range<uint64_t>(std::pair(0, std::filesystem::file_size(f)), data.size(), - [&](uint64_t offset, uint64_t size) -> bool - { - progress_output(byte, bytes_count); - - fs.read((char *)data.data(), size); - if(fs.fail()) - throw_line("read failed ({})", f.filename().string()); + ctx.dat.push_back(rom_entry.xmlLine()); + } - rom_entry.update(data.data(), size); + progress_output(bytes_count, bytes_count); + LOG(""); + LOG(""); + } + } - byte += size; + if(ctx.dat_extra.empty()) + { + std::list<std::filesystem::path> files; - return false; - }); + // hash CD extras + if(std::filesystem::exists(image_prefix + ".cue")) + { + auto image_path = std::filesystem::path(options.image_path); + if(image_path.empty()) + image_path = "."; - dat.push_back(rom_entry.xmlLine()); - } + auto cue_entries = cue_get_entries(image_prefix + ".cue"); + auto track_prefix = std::filesystem::path(image_prefix).filename().string() + " (Track "; - ctx.dat = dat; + for(auto const &entry : std::filesystem::directory_iterator(image_path)) + { + auto filename = entry.path().filename().string(); + if(!filename.starts_with(track_prefix) || !filename.ends_with(").bin")) + continue; - progress_output(bytes_count, bytes_count); - LOG(""); - LOG(""); + if(std::none_of(cue_entries.begin(), cue_entries.end(), [&](auto const &t) { return std::filesystem::path(t.first) == filename; })) + files.push_back(entry.path()); + } + } + // hash xbox extras + else if(std::filesystem::exists(image_prefix + ".iso")) + { + if(std::filesystem::exists(image_prefix + ".dmi")) + files.push_back(image_prefix + ".dmi"); + if(std::filesystem::exists(image_prefix + ".pfi")) + files.push_back(image_prefix + ".pfi"); + if(std::filesystem::exists(image_prefix + ".ss")) + files.push_back(image_prefix + ".ss"); } + + files.sort(); + for(auto f : files) + ctx.dat_extra.push_back(calculate_file_hash(f).xmlLine()); } - if(ctx.dat) + if(!ctx.dat.empty()) { LOG("dat:"); - for(auto l : *ctx.dat) + for(auto l : ctx.dat) + LOG("{}", l); + } + + if(!ctx.dat_extra.empty()) + { + LOG("dat (extra):"); + for(auto l : ctx.dat_extra) LOG("{}", l); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/options.ixx new/redumper-b746/options.ixx --- old/redumper-b734/options.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/options.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -404,6 +404,9 @@ LOG("\tflash::mt1959 \tflashes MT1959 drive firmware"); LOG("\tflash::sd616 \tflashes SD-616F/T drive firmware"); LOG("\tflash::plextor\tflashes PLEXTOR drive firmware"); + LOG("\ttools::fixmsf \trepairs sectors with invalid sync or MSF addresses"); + LOG("\ttools::fixmsf::shift\treorders sectors using their MSF addresses"); + LOG("\ttools::trim \ttrims a raw CD image to its ISO9660 volume size"); LOG(""); LOG("OPTIONS:"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/redumper.ixx new/redumper-b746/redumper.ixx --- old/redumper-b734/redumper.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/redumper.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -125,28 +125,30 @@ const std::map<std::string, Command> COMMANDS{ // NAME DRIVE READY AUTO IMAGE GENERATE HANDLER - { "rings", { true, true, true, false, false, redumper_rings } }, - { "dump", { true, true, true, true, true, redumper_dump } }, - { "dump::extra", { true, true, true, true, false, redumper_dump_extra } }, - { "refine", { true, true, true, true, false, redumper_refine } }, - { "dvdkey", { true, true, true, false, false, redumper_dvdkey } }, - { "eject", { true, false, false, false, false, redumper_eject } }, - { "dvdisokey", { false, false, false, true, false, redumper_dvdisokey } }, - { "protection", { false, false, false, true, false, redumper_protection } }, - { "split", { false, false, false, true, false, redumper_split } }, - { "hash", { false, false, false, true, false, redumper_hash } }, - { "info", { false, false, false, true, false, redumper_info } }, - { "skeleton", { false, false, false, true, false, redumper_skeleton } }, - { "flash::mt1339", { true, false, false, false, false, redumper_flash_tsst } }, - { "flash::sd616", { true, false, false, false, false, redumper_flash_sd616 } }, - { "flash::plextor", { true, false, false, false, false, redumper_flash_plextor } }, - { "flash::mt1959", { true, false, false, false, false, redumper_flash_mt1959 } }, - { "subchannel", { false, false, false, true, false, redumper_subchannel } }, - { "debug", { false, false, false, false, false, redumper_debug } }, - { "fixmsf", { false, false, false, true, false, redumper_fix_msf } }, - { "debug::flip", { false, false, false, true, false, redumper_flip } }, - { "drive::test", { true, true, true, false, false, redumper_drive_test } }, - { "drive::detect", { true, true, true, false, false, redumper_drive_detect } }, + { "rings", { true, true, true, false, false, redumper_rings } }, + { "dump", { true, true, true, true, true, redumper_dump } }, + { "dump::extra", { true, true, true, true, false, redumper_dump_extra } }, + { "refine", { true, true, true, true, false, redumper_refine } }, + { "dvdkey", { true, true, true, false, false, redumper_dvdkey } }, + { "eject", { true, false, false, false, false, redumper_eject } }, + { "dvdisokey", { false, false, false, true, false, redumper_dvdisokey } }, + { "protection", { false, false, false, true, false, redumper_protection } }, + { "split", { false, false, false, true, false, redumper_split } }, + { "hash", { false, false, false, true, false, redumper_hash } }, + { "info", { false, false, false, true, false, redumper_info } }, + { "skeleton", { false, false, false, true, false, redumper_skeleton } }, + { "flash::mt1339", { true, false, false, false, false, redumper_flash_tsst } }, + { "flash::sd616", { true, false, false, false, false, redumper_flash_sd616 } }, + { "flash::plextor", { true, false, false, false, false, redumper_flash_plextor } }, + { "flash::mt1959", { true, false, false, false, false, redumper_flash_mt1959 } }, + { "subchannel", { false, false, false, true, false, redumper_subchannel } }, + { "debug", { false, false, false, false, false, redumper_debug } }, + { "tools::fixmsf", { false, false, false, true, false, redumper_fix_msf } }, + { "tools::fixmsf::shift", { false, false, false, true, false, redumper_fix_msf_shift } }, + { "tools::trim", { false, false, false, true, false, redumper_trim } }, + { "debug::flip", { false, false, false, true, false, redumper_flip } }, + { "drive::test", { true, true, true, false, false, redumper_drive_test } }, + { "drive::detect", { true, true, true, false, false, redumper_drive_detect } }, }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/rom_entry.ixx new/redumper-b746/rom_entry.ixx --- old/redumper-b734/rom_entry.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/rom_entry.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -40,6 +40,12 @@ } + uint64_t getSize() const + { + return _size; + } + + std::string xmlLine() { // do it only once because final() for MD5/SHA-1 modifies state diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/systems/ps4.ixx new/redumper-b746/systems/ps4.ixx --- old/redumper-b734/systems/ps4.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/systems/ps4.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -1,17 +1,22 @@ module; +#include <array> #include <filesystem> #include <format> #include <map> #include <ostream> #include <span> +#include <string_view> +#include <vector> #include "system.hh" #include "throw_line.hh" export module systems.ps4; +import cd.cdrom; import filesystem.iso9660; import readers.data_reader; import systems.ps3; +import utils.endian; @@ -54,7 +59,94 @@ serial.insert(4, "-"); os << std::format(" serial: {}", serial) << std::endl; } + + std::string content_ids = getContentIds(data_reader, root_directory, _PKG_FILE_NAMES); + + if(content_ids.empty()) + return; + + os << std::format(" content ID(s): {}", content_ids) << std::endl; } + +protected: + std::string getContentIds(DataReader *data_reader, std::shared_ptr<iso9660::Entry> root_directory, std::span<const std::string_view> pkg_file_names) const + { + auto app_directory = root_directory->subEntry("app"); + if(!app_directory) + return ""; + + auto app_directory_entries = app_directory->entries(); + std::string content_ids; + + for(auto &e : app_directory_entries) + { + if(!e->isDirectory()) + continue; + + for(const auto &pkg_file_name : pkg_file_names) + { + auto app_pkg_entry = e->subEntry(std::string(pkg_file_name)); + if(!app_pkg_entry) + continue; + + std::vector<uint8_t> app_pkg_header(FORM1_DATA_SIZE); + data_reader->read((uint8_t *)app_pkg_header.data(), app_pkg_entry->sectorsLBA(), 1); + auto pkg_header = (PkgHeader *)app_pkg_header.data(); + pkg_header->swapEndianness(); + + if(pkg_header->pkg_magic != _PKG_MAGIC) + continue; + + if(!content_ids.empty()) + content_ids += ", "; + + content_ids += std::string(reinterpret_cast<const char *>(pkg_header->pkg_content_id), sizeof(pkg_header->pkg_content_id)); + + break; + } + } + + return content_ids; + } + +private: + static constexpr uint32_t _PKG_MAGIC = 0x7F434E54; + static constexpr std::array<std::string_view, 3> _PKG_FILE_NAMES = { "app.pkg", "app_h.pkg", "app_0.pkg" }; + + struct PkgHeader + { + uint32_t pkg_magic; + uint32_t pkg_type; + uint32_t pkg_0x008; + uint32_t pkg_file_count; + uint32_t pkg_entry_count; + uint16_t pkg_sc_entry_count; + uint16_t pkg_entry_count_2; + uint32_t pkg_table_offset; + uint32_t pkg_entry_data_size; + uint64_t pkg_body_offset; + uint64_t pkg_body_size; + uint64_t pkg_content_offset; + uint64_t pkg_content_size; + unsigned char pkg_content_id[0x24]; + + void swapEndianness() + { + pkg_magic = endian_swap(pkg_magic); + pkg_type = endian_swap(pkg_type); + pkg_0x008 = endian_swap(pkg_0x008); + pkg_file_count = endian_swap(pkg_file_count); + pkg_entry_count = endian_swap(pkg_entry_count); + pkg_sc_entry_count = endian_swap(pkg_sc_entry_count); + pkg_entry_count_2 = endian_swap(pkg_entry_count_2); + pkg_table_offset = endian_swap(pkg_table_offset); + pkg_entry_data_size = endian_swap(pkg_entry_data_size); + pkg_body_offset = endian_swap(pkg_body_offset); + pkg_body_size = endian_swap(pkg_body_size); + pkg_content_offset = endian_swap(pkg_content_offset); + pkg_content_size = endian_swap(pkg_content_size); + }; + }; }; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/systems/ps5.ixx new/redumper-b746/systems/ps5.ixx --- old/redumper-b734/systems/ps5.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/systems/ps5.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -4,6 +4,7 @@ #include <map> #include <ostream> #include <span> +#include <string_view> #include "system.hh" #include "throw_line.hh" @@ -11,6 +12,7 @@ import filesystem.iso9660; import readers.data_reader; +import systems.ps4; import utils.misc; import utils.strings; @@ -19,7 +21,7 @@ namespace gpsxre { -export class SystemPS5 : public System +export class SystemPS5 : public SystemPS4 { public: std::string getName() override @@ -48,9 +50,18 @@ if(auto it = param_json.find("masterDataId"); it != param_json.end()) os << std::format(" serial: {}", it->second.insert(4, "-")) << std::endl; + + std::string content_ids = getContentIds(data_reader, root_directory, _PKG_FILE_NAMES); + + if(content_ids.empty()) + return; + + os << std::format(" content ID(s): {}", content_ids) << std::endl; } private: + static constexpr std::array<std::string_view, 1> _PKG_FILE_NAMES = { "app_sc.pkg" }; + std::map<std::string, std::string> loadJSON(std::shared_ptr<iso9660::Entry> json_entry) const { std::map<std::string, std::string> json; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b734/systems/s_cdrom.ixx new/redumper-b746/systems/s_cdrom.ixx --- old/redumper-b734/systems/s_cdrom.ixx 2026-07-18 13:19:20.000000000 +0200 +++ new/redumper-b746/systems/s_cdrom.ixx 2026-08-25 03:51:25.000000000 +0200 @@ -217,7 +217,7 @@ if(auto count = ranges_count(edc_errors); count) os << std::format(" EDC errors: {} (LBA: {})", count, ranges_to_string(edc_errors)) << std::endl; if(auto count = ranges_count(subheader_mismatches); count) - os << std::format(" CD-XA subheader mismatches: {} (LBA: {})", count, ranges_to_string(subheader_mismatches)) << std::endl; + os << std::format(" CD-XA subheader mismatches: {}", count) << std::endl; os << std::endl; os << std::format(" REDUMP.INFO errors: {}", redump_errors) << std::endl; }
