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-10-26 10:00:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/redumper (Old) and /work/SRC/openSUSE:Factory/.redumper.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "redumper" Mon Oct 26 10:00:33 2026 rev:11 rq:1392389 version:768 Changes: -------- --- /work/SRC/openSUSE:Factory/redumper/redumper.changes 2026-09-28 10:45:23.765296642 +0200 +++ /work/SRC/openSUSE:Factory/.redumper.new.2252680/redumper.changes 2026-10-26 10:01:37.872812430 +0100 @@ -1,0 +2,18 @@ +Sun Oct 25 11:08:15 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 768 + * READ CD: request the exact transfer length (#459). + * Fix READ CDDA short transfer check rejecting oversized buffers + (#468). + * Add LG GP57ES40 drive (#466). + * Add ATAPI DVD D DH16D2S GD0D drive (#463). + * Print MKB version (#462). + * Added support for flashing encrypted firmware (#456). + * Add LG SP80NB60 drive (#460). + * Fix layer break printing (#458). + * AES-128 and CMAC implementation. + * Improve MT1959 flashing support (#452). + * Update raw OmniDrive dump read size handling (#451). + * Add entry support to UDF implementation (#450). + +------------------------------------------------------------------- Old: ---- redumper-b752.tar.gz New: ---- redumper-b768.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ redumper.spec ++++++ --- /var/tmp/diff_new_pack.jfRrcR/_old 2026-10-26 10:01:38.708847506 +0100 +++ /var/tmp/diff_new_pack.jfRrcR/_new 2026-10-26 10:01:38.711847631 +0100 @@ -17,7 +17,7 @@ Name: redumper -Version: 752 +Version: 768 Release: 0 Summary: Low level CD dumper utility License: GPL-3.0-only ++++++ redumper-b752.tar.gz -> redumper-b768.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/CMakeLists.txt new/redumper-b768/CMakeLists.txt --- old/redumper-b752/CMakeLists.txt 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/CMakeLists.txt 2026-10-23 03:56:38.000000000 +0200 @@ -126,7 +126,9 @@ "drive/plextor.ixx" "drive/mediatek.ixx" "drive/test.ixx" + "dvd/aacs.ixx" "dvd/bd.ixx" + "dvd/bd_key.ixx" "dvd/bd_scrambler.ixx" "dvd/css/css.ixx" "dvd/dvd.ixx" @@ -153,8 +155,11 @@ "filesystem/udf/udf.ixx" "filesystem/udf/udf_browser.ixx" "filesystem/udf/udf_defs.ixx" + "filesystem/udf/udf_entry.ixx" "filesystem/udf/udf_size.ixx" "hash/block_hasher.ixx" + "hash/aes128.ixx" + "hash/cmac.ixx" "hash/md5.ixx" "hash/sha1.ixx" "readers/disc_read_cdda_reader.ixx" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/README.md new/redumper-b768/README.md --- old/redumper-b752/README.md 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/README.md 2026-10-23 03:56:38.000000000 +0200 @@ -110,7 +110,7 @@ redumper disc --drive=<drive> --bd-raw ``` -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. +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. If you are getting SCSI errors with OmniDrive, try lower the transfer length (`--dump-read-size`). ### Test an unknown drive diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/drive/flash_mt1959.ixx new/redumper-b768/drive/flash_mt1959.ixx --- old/redumper-b752/drive/flash_mt1959.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/drive/flash_mt1959.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -2,11 +2,13 @@ #include <algorithm> #include <chrono> #include <cstdint> +#include <cstring> #include <fstream> #include <span> #include <string> #include <string_view> #include <thread> +#include <unordered_set> #include <vector> #include "throw_line.hh" @@ -20,6 +22,7 @@ import utils.file_io; import utils.hex_bin; import utils.logger; +import hash.aes128; @@ -28,34 +31,74 @@ struct DriveEntry { - std::string vendor_id; std::string product_id; - std::string config_id; + std::string bootstring_suffix; + std::string svccode; }; +const std::vector<std::unordered_set<std::string>> MT1959_BOOTSTRING_SUFFIXES{ + { "BUP1" }, // slim (old) + { "BU5 ", "BU51", "BUP3", "BP32", "BUP5", "BP52" }, // slim + { "JB8 ", "JB9 ", "JBC6", "JBP6" }, // half-height +}; + +// OEM firmwares lack the SVC code 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 - { "HL-DT-ST", "BD-RE WH16NS60", "MT1959 Boot JB8 " }, // NS60 - { "HL-DT-ST", "BD-RE BH16NS58", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE WH16NS58", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE BH16NS55", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE BH16NS50", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE BH14NS50", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE BH16NS40", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE WH16NS40", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE BH14NS40", "MT1959 Boot JB8 " }, // NS50 - { "HL-DT-ST", "BD-RE WH14NS40", "MT1959 Boot JB8 " }, // NS50 + { "BDDVDRW CH12NS40", "JBC6", "NS40" }, + { "BDDVDRW UH12NS40", "JBC6", "NS40" }, + { "BC-12B1ST b", "JBC6", " " }, + { "BC-12D2HT", "JBC6", " " }, + { "BD-RE BE16NU50", "JBP6", "NU50" }, + { "BD-RE BH14NS40", "JB8 ", "NS50" }, + { "BD-RE WH14NS40", "JB8 ", "NS50" }, + { "BD-RE BH14NS50", "JB8 ", "NS50" }, + { "BD-RE WH14NS50", "JB8 ", "NS50" }, + { "BD-RE BH14NS58", "JB8 ", "NS50" }, + { "BD-RE WH14NS58", "JB8 ", "NS50" }, + { "BD-RE BH16NS40", "JB8 ", "NS50" }, + { "BD-RE WH16NS40", "JB8 ", "NS50" }, + { "BD-RE WH16NS46", "JB8 ", "NS50" }, + { "BD-RE BH16NS50", "JB8 ", "NS50" }, + { "BD-RE WH16NS50", "JB8 ", "NS50" }, + { "BD-RE BH16NS55", "JB8 ", "NS50" }, + { "BD-RE WH16NS55", "JB8 ", "NS50" }, + { "BD-RE BH16NS58", "JB8 ", "NS50" }, + { "BD-RE WH16NS58", "JB8 ", "NS50" }, + { "BD-RE WH16NS58", "JB8 ", " " }, // Vinpower OEM + { "BD-RE BH16NS60", "JB8 ", "NS60" }, // JB9 board + { "BD-RE WH16NS60", "JB8 ", "NS60" }, // JB9 board + { "BD-RE BH-A10AME", "JB8 ", " " }, + { "BD-RE BH40N", "JB8 ", " " }, + { "BD-RE BH50N", "JB8 ", " " }, + { "BW-16D1HT", "JB8 ", " " }, + { "BW-16D1H-U", "JB8 ", " " }, + { "BW-16D1X-U", "JB8 ", " " }, + { "BD-RE BP50NB40", "BUP3", "NB50" }, + { "BD-RE BP50NB40", "BP32", "NB52" }, + { "BD-RE WP50NB40", "BUP3", "NB50" }, + { "BD-RE WP50NB40", "BP32", "NB52" }, + { "BD-RE BP55EB40", "BUP3", "NB50" }, // SVC code not a typo here + { "BD-RE BP55EB40", "BP32", "EB52" }, + { "BD-RE BP60NB10", "BUP5", "NB10" }, + { "BD-RE BP60NB10", "BP52", "NB12" }, + { "BD-RE BU40N", "BU5 ", "BU40" }, + { "BD-RE BU40N", "BU5 ", " " }, + { "BDRE BU40N", "BU5 ", " " }, // HP version + { "BD-RE BU50N", "BU51", " " }, + { "BDRE BU50N", "BU51", " " }, // HP version + // { "BD-RE BP71N ", "", "" }, // needs checking }; void flash_mt1959(SPTD &sptd, std::span<const uint8_t> firmware_data, uint32_t block_size) { - if(auto status = cmd_write_buffer(sptd, nullptr, 0, WRITE_BUFFER_Mode::VENDOR_SPECIFIC, 0, 0); status.status_code) - throw_line("failed to flash firmware, SCSI ({})", SPTD::StatusMessage(status)); + if(auto status = cmd_write_buffer(sptd, nullptr, 0, WRITE_BUFFER_Mode::DOWNLOAD_MICROCODE_WITH_OFFSETS, 0x67, 0, 0); status.status_code) + { + // slim flash unlocked failed, try standard + status = cmd_write_buffer(sptd, nullptr, 0, WRITE_BUFFER_Mode::VENDOR_SPECIFIC, 0, 0, 0); + if(status.status_code) + throw_line("failed to flash firmware, SCSI ({})", SPTD::StatusMessage(status)); + } for(uint32_t offset = 0; offset < firmware_data.size();) { @@ -64,7 +107,7 @@ LOGC_RF("[{:3}%] flashing: [{:08X} .. {:08X})", 100 * offset / (uint32_t)firmware_data.size(), offset, offset_next); - if(auto status = cmd_write_buffer(sptd, &firmware_data[offset], size, WRITE_BUFFER_Mode::DOWNLOAD_MICROCODE_WITH_OFFSETS, offset, size); status.status_code) + if(auto status = cmd_write_buffer(sptd, &firmware_data[offset], size, WRITE_BUFFER_Mode::DOWNLOAD_MICROCODE_WITH_OFFSETS, 0, offset, size); status.status_code) throw_line("failed to flash firmware, SCSI ({})", SPTD::StatusMessage(status)); offset = offset_next; @@ -72,17 +115,25 @@ LOGC(""); LOGC(""); - constexpr uint32_t wait_seconds = 15; - uint32_t ready_count = 0; - for(uint32_t i = 0; i < wait_seconds; ++i) - { - LOGC_RF("waiting for drive to become ready... [{}/{}]", i + 1, wait_seconds); - if(!cmd_drive_ready(sptd).status_code) - ++ready_count; + constexpr uint8_t completion_value = 32; + auto status = cmd_drive_ready(sptd); - if(ready_count >= 2) - break; + if(!status.status_code) + { + // if it's ready immediately the flash failed + throw_line("failed to flash firmware, firmware file failed drive verification"); + } + constexpr uint32_t timeout = 60; + for(uint32_t i = 0; i <= timeout; i++) + { + if(i >= timeout) + throw_line("failed to flash firmware, flash timed out"); + if(status.sense_key != 0x02 || status.asc != 0x04) + throw_line("failed to flash firmware, SCSI ({})", SPTD::StatusMessage(status)); + LOGC_RF("waiting for flash to complete... [{}/{}]", status.information[1], completion_value); + if(status = cmd_drive_ready(sptd); !status.status_code) + break; std::this_thread::sleep_for(std::chrono::seconds(1)); } @@ -91,27 +142,72 @@ } -std::vector<uint8_t> extract_firmware_config(std::span<const uint8_t> firmware_data, uint32_t config_offset, uint32_t config_size) +std::string_view extract_firmware_bootstring(std::span<const uint8_t> firmware_data, uint32_t bootstring_offset, uint32_t bootstring_size) { - return std::vector<uint8_t>(&firmware_data[config_offset], &firmware_data[config_offset + config_size]); + return std::string_view((const char *)&firmware_data[bootstring_offset], bootstring_size); } -std::vector<uint8_t> read_mt1959_config(SPTD &sptd, uint32_t config_offset, uint32_t config_size) +std::string read_mt1959_bootstring(SPTD &sptd, uint32_t bootstring_offset, uint32_t bootstring_size) { - std::vector<uint8_t> config(config_size); + std::vector<uint8_t> bootstring(bootstring_size); - if(auto status = cmd_read_buffer(sptd, config.data(), config.size(), READ_BUFFER_Mode::DOWNLOAD_MICROCODE_WITH_OFFSETS, config_offset, config.size()); status.status_code) - throw_line("failed to read drive patch configuration, SCSI ({})", SPTD::StatusMessage(status)); + if(auto status = cmd_read_buffer(sptd, bootstring.data(), bootstring.size(), READ_BUFFER_Mode::DOWNLOAD_MICROCODE_WITH_OFFSETS, 0, bootstring_offset, bootstring_size); status.status_code) + throw_line("failed to read drive bootstring, SCSI ({})", SPTD::StatusMessage(status)); - return config; + return std::string((const char *)bootstring.data(), bootstring_size); } -void modify_firmware(std::span<uint8_t> firmware_data, uint32_t clear_size, uint32_t config_offset, std::span<const uint8_t> drive_config) +std::string read_mt1959_svccode(SPTD &sptd, uint32_t svccode_offset, uint32_t svccode_size) +{ + std::vector<uint8_t> svccode(svccode_size); + + if(auto status = cmd_read_buffer(sptd, svccode.data(), svccode.size(), READ_BUFFER_Mode::DOWNLOAD_MICROCODE_WITH_OFFSETS, 0, svccode_offset, svccode_size); status.status_code) + throw_line("failed to read drive SVC code, SCSI ({})", SPTD::StatusMessage(status)); + + return std::string((const char *)svccode.data(), svccode_size); +} + + +bool needs_encryption(SPTD &sptd, std::string &vendor_specific) +{ + GET_CONFIGURATION_FirmwareInformationBody firmware_information; + + if(auto status = cmd_get_configuration_firmware_information(sptd, &firmware_information); status.status_code) + throw_line("failed to read current firmware build date/time, SCSI ({})", SPTD::StatusMessage(status)); + + // if year < 2020 + if(const uint8_t decade = firmware_information.year[0] - '0'; decade < 2) + return false; + + if(vendor_specific.empty() || vendor_specific.size() < 2) + { + LOGC("invalid vendor-specific data; assuming encrypted firmware is required"); + return true; + } + + return vendor_specific[1] != 'M'; +} + + +void modify_firmware(std::span<uint8_t> firmware_data, uint32_t clear_size, uint32_t bootstring_offset, std::string &bootstring, bool encrypt_firmware) { std::fill(firmware_data.begin(), firmware_data.begin() + clear_size, (uint8_t)0xFF); - std::copy(drive_config.begin(), drive_config.end(), &firmware_data[config_offset]); + std::copy(bootstring.begin(), bootstring.end(), &firmware_data[bootstring_offset]); + + if(!encrypt_firmware) + return; + + constexpr std::array<uint8_t, AES128::BLOCK_SIZE> MT1959_ENC_KEY{ 0x5E, 0x9E, 0x4F, 0x00, 0x94, 0xEF, 0x20, 0xAB, 0x52, 0xE3, 0x5E, 0x73, 0x6A, 0xCB, 0x23, 0x24 }; + const AES128 aes128(MT1959_ENC_KEY); + for(auto i = 0; i < firmware_data.size(); i += AES128::BLOCK_SIZE) + { + std::array<uint8_t, AES128::BLOCK_SIZE> block; + memcpy(block.data(), firmware_data.data() + i, block.size()); + block = aes128.encryptBlock(block); + memcpy(firmware_data.data() + i, block.data(), block.size()); + } } @@ -120,36 +216,65 @@ int exit_code = 0; constexpr uint32_t block_size = 0x4000; - constexpr uint32_t config_offset = 0x3000; - constexpr uint32_t config_size = 0x20; - constexpr uint32_t id_size = 0x10; + constexpr uint32_t bootstring_offset = 0x3000; + constexpr uint32_t bootstring_size = 0x10; + constexpr std::string_view bootstring_prefix = "MT1959 Boot "; + constexpr uint32_t svccode_offset = 0x1EC04C; + constexpr uint32_t svccode_size = 0x4; constexpr uint32_t clear_size = 0x10000; auto firmware_data = read_vector(options.firmware); if(firmware_data.size() < clear_size) throw_line("firmware data too small (size: {:#x}, required: {:#x})", firmware_data.size(), clear_size); - auto firmware_config = extract_firmware_config(firmware_data, config_offset, config_size); - std::string_view firmware_id((const char *)firmware_config.data(), id_size); + if(firmware_data.size() % 16 != 0) + throw_line("firmware size is incorrect (not a multiple of 16) (size: {:#x})", firmware_data.size()); + + auto drive_bootstring = read_mt1959_bootstring(*ctx.sptd, bootstring_offset, bootstring_size); + auto drive_svccode = read_mt1959_svccode(*ctx.sptd, svccode_offset, svccode_size); if(!options.force_flash) { - auto it = std::find_if(MT1959_SUPPORTED_DRIVES.begin(), MT1959_SUPPORTED_DRIVES.end(), - [&](const auto &d) { return d.vendor_id == ctx.drive_config.vendor_id && d.product_id == ctx.drive_config.product_id; }); - if(it == MT1959_SUPPORTED_DRIVES.end()) - throw_line("flashing of this drive is unsupported"); - else if(firmware_id != it->config_id) - throw_line("unexpected firmware config id (current: {}, expected: {})", firmware_id, it->config_id); - } - - 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 && !(drive_id == "MT1959 Boot BU51" && firmware_id == "MT1959 Boot BU5 ")) - throw_line("firmware id mismatch (drive: {}, firmware: {})", drive_id, firmware_id); + auto firmware_bootstring = extract_firmware_bootstring(firmware_data, bootstring_offset, bootstring_size); - LOG("drive patch configuration (offset: 0x{:04X}, size: 0x{:X}): ", config_offset, config_size); - LOG("{}", hexdump(drive_config.data(), 0, (uint32_t)drive_config.size())); + // compare prefix + if(drive_bootstring.compare(0, bootstring_prefix.length(), bootstring_prefix) != 0) + throw_line("unexpected drive bootstring prefix (current: {}, expected: {})", drive_bootstring.substr(0, bootstring_prefix.length()), bootstring_prefix); + if(firmware_bootstring.compare(0, bootstring_prefix.length(), bootstring_prefix) != 0) + throw_line("unexpected firmware bootstring prefix (current: {}, expected: {})", firmware_bootstring.substr(0, bootstring_prefix.length()), bootstring_prefix); + + auto firmware_bootstring_suffix = firmware_bootstring.substr(bootstring_prefix.length()); + auto drive_bootstring_suffix = drive_bootstring.substr(bootstring_prefix.length()); + + bool is_compatible = false; + for(auto &compatible_suffixes : MT1959_BOOTSTRING_SUFFIXES) + { + if(compatible_suffixes.contains(std::string(firmware_bootstring_suffix)) && compatible_suffixes.contains(std::string(drive_bootstring_suffix))) + is_compatible = true; + } + if(!is_compatible) + throw_line("incompatible firmware bootstring for drive (firmware bootstring: {}, drive bootstring: {})", firmware_bootstring_suffix, drive_bootstring_suffix); + } + + + auto bootstring = std::string(drive_bootstring.substr(bootstring_prefix.length())); + // if drive is in boot mode we should use the detected string, otherwise we derive string based on product ID and SVC code + if(ctx.drive_config.product_revision_level != "BOOT") + { + for(auto &drive_entry : MT1959_SUPPORTED_DRIVES) + { + if(drive_entry.product_id == ctx.drive_config.product_id) + { + bootstring = drive_entry.bootstring_suffix; + // exit loop if SVC code matches, since that guarantees correct bootstring + if(drive_entry.svccode == drive_svccode) + break; + } + } + } + bootstring = std::string(bootstring_prefix) + bootstring; - modify_firmware(firmware_data, clear_size, config_offset, drive_config); + const bool encrypt_firmware = needs_encryption(*ctx.sptd, ctx.drive_config.vendor_specific); + modify_firmware(firmware_data, clear_size, bootstring_offset, bootstring, encrypt_firmware); flash_mt1959(*ctx.sptd, firmware_data, block_size); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/drive/flash_plextor.ixx new/redumper-b768/drive/flash_plextor.ixx --- old/redumper-b752/drive/flash_plextor.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/drive/flash_plextor.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -54,7 +54,7 @@ cmd_drive_ready(sptd); auto mode = offset_next < firmware_data.size() ? WRITE_BUFFER_Mode::DOWNLOAD_MICROCODE : WRITE_BUFFER_Mode::DOWNLOAD_MICROCODE_SAVE; - SPTD::Status status = cmd_write_buffer(sptd, &firmware_data[offset], size, mode, offset, size); + SPTD::Status status = cmd_write_buffer(sptd, &firmware_data[offset], size, mode, 0, offset, size); if(status.status_code) throw_line("failed to flash firmware, SCSI ({})", SPTD::StatusMessage(status)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/drive.ixx new/redumper-b768/drive.ixx --- old/redumper-b752/drive.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/drive.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -1,5 +1,6 @@ module; #include <algorithm> +#include <array> #include <chrono> #include <cstdint> #include <cstring> @@ -69,6 +70,18 @@ MTK2B }; +export enum class JamlessPlayMode : uint8_t +{ + OFF = 0xDD, + ON = 0xEE +}; + +export enum class JamlessPlayPersist : uint8_t +{ + TEMPORARY = 0x00, + FLASH = 0xFF +}; + export struct DriveConfig { std::string vendor_id; @@ -181,6 +194,7 @@ { "TSSTcorp", "DVD-ROM TS-H353C" , "ZZ00", "" , "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // MoriGM { "HL-DT-ST", "DVD-ROM DH41N" , "ZZ00", "" , "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // MoriGM { "PLDS" , "DVD-ROM DH-16D5S" , "ZZ00", "2025-06-18 15:03 Fen", "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // MoriGM + { "ATAPI" , "DVD D DH16D2S" , "GD0D", "2007/06/04 17:00" , "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // wakasio { "hp" , "DVD RW AD-7290H5" , "SE02", "" , "", +48, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // seri { "Optiarc" , "DVD RW AD-7173A" , "1-04", "seri-v1 BT-LIGGY" , "", +48, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // seri { "NEC" , "DVD_RW ND-3520AW" , "3.07", "seri-01 BT-LIGGY" , "", +48, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // seri @@ -224,6 +238,8 @@ { "HL-DT-ST", "BD-RE WH16NS40" , "1.05", "", "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // scrambled { "HL-DT-ST", "BD-RE WH16NS60" , "1.02", "", "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, // scrambled { "HL-DT-ST", "DVD+-RW GH50N" , "B103", "", "", +667, 0, -135, ReadMethod::BE, SectorOrder::DATA_SUB , Type::GENERIC }, + { "HL-DT-ST", "DVDRAM SP80NB60" , "PD00", "", "", +6, 0, 0, ReadMethod::BE, SectorOrder::DATA_SUB_C2, Type::GENERIC }, + { "HL-DT-ST", "DVDRAM GP57ES40" , "RF01", "", "", +6, 0, 0, ReadMethod::BE, SectorOrder::DATA_SUB_C2, Type::GENERIC }, { "CREATIVE", "CD5233E-N" , "0.20", "", "", +12, 0, -135, ReadMethod::BE, SectorOrder::DATA_C2_SUB, Type::GENERIC }, { "PLEXTOR" , "DVDR PX-740A" , "1.02", "", "", +618, 0, -135, ReadMethod::BE, SectorOrder::DATA_SUB , Type::GENERIC }, // doesn't stop on lead-out but always returns same sector { "PLEXTOR" , "DVDR PX-L890SA" , "1.07", "", "", +6, 0, -135, ReadMethod::BE, SectorOrder::DATA_SUB_C2, Type::GENERIC }, // scrambled @@ -438,6 +454,26 @@ } +export bool is_jamless_play_firmware(const DriveConfig &drive_config) +{ + return drive_config.vendor_id == "HL-DT-ST" && (drive_config.product_id.starts_with("DV") || drive_config.product_id.starts_with("BD")); +} + + +export bool drive_set_jamless_play(SPTD &sptd, JamlessPlayMode mode, JamlessPlayPersist persist) +{ + std::array<uint8_t, 4> response = {}; + uint32_t transferred = 0; + uint32_t allocation_length = (uint32_t)mode << 16 | (uint32_t)persist << 8; + auto status = cmd_read_buffer(sptd, response.data(), response.size(), READ_BUFFER_Mode::LG_JAMLESS_PLAY_CONTROL, 0x01, 0, allocation_length); + if(status.status_code) + return false; + + std::array<uint8_t, 4> expected_response = mode == JamlessPlayMode::OFF ? std::array<uint8_t, 4>{ 0xDD, 0x00, 0x00, 0x00 } : std::array<uint8_t, 4>{ 0xEE, 0x01, 0x00, 0x00 }; + return response == expected_response; +} + + export bool is_kreon_firmware(const DriveConfig &drive_config) { return drive_config.vendor_specific.starts_with("KREON V1.00"); @@ -479,4 +515,35 @@ } +export std::optional<uint32_t> is_spiraldrive_firmware(const DriveConfig &drive_config) +{ + constexpr std::string_view prefix = "SpiralDrive v"; + if(!drive_config.vendor_specific.starts_with(prefix)) + return std::nullopt; + + auto version = tokenize(drive_config.vendor_specific.substr(prefix.size()), ".", nullptr); + if(version.size() != 3 || std::count(drive_config.vendor_specific.begin(), drive_config.vendor_specific.end(), '.') != 2) + return std::nullopt; + + auto major = str_to_uint64(version[0]); + auto minor = str_to_uint64(version[1]); + auto patch = str_to_uint64(version[2]); + if(!major || !minor || !patch || *major > 0xff || *minor > 0xff || *patch > 0xff) + return std::nullopt; + + return (uint32_t)((*major << 16) | (*minor << 8) | *patch); +} + + +export std::string spiraldrive_version_string(uint32_t version) +{ + return omnidrive_version_string(version); +} + + +export uint32_t spiraldrive_minimum_version() +{ + return 0x00010000; +} + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/dvd/aacs.ixx new/redumper-b768/dvd/aacs.ixx --- old/redumper-b752/dvd/aacs.ixx 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b768/dvd/aacs.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -0,0 +1,47 @@ +// based on aacskeys + +module; + +#include <cstdint> +#include <cstring> + +export module bd.aacs; + +import filesystem.udf; +import readers.data_reader; +import utils.endian; + + + +namespace gpsxre +{ + +export class AACS +{ +public: + static int getMKBVersion(DataReader *reader) + { + auto root_directory = udf::Browser::rootDirectory(reader); + if(!root_directory) + { + return -1; + } + + auto media_key_block = root_directory->subEntry("AACS/MKB_RO.inf"); + if(!media_key_block) + { + return -1; + } + + auto data = media_key_block->read(); + + // The media key block version is stored as a big endian integer from 0x00000008 to 0x0000000B + uint32_t mkbv; + memcpy(&mkbv, data.data() + 8, sizeof(uint32_t)); + mkbv = endian_swap(mkbv); + + return mkbv; + } +}; + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/dvd/bd_key.ixx new/redumper-b768/dvd/bd_key.ixx --- old/redumper-b752/dvd/bd_key.ixx 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b768/dvd/bd_key.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -0,0 +1,37 @@ +module; +#include <string> + +export module bd.key; + +import bd.aacs; +import common; +import filesystem.udf; +import options; +import readers.disc_read_reader; +import utils.logger; + + + +namespace gpsxre +{ + +export int redumper_bdkey(Context &ctx, Options &options) +{ + int exit_code = 0; + + if(ctx.disc_type != DiscType::BLURAY) + return exit_code; + + Disc_READ_Reader reader(*ctx.sptd, 0); + + int mkbv = AACS::getMKBVersion(&reader); + if(mkbv != -1) + { + LOG("BD-Video Protection: AACS (MKB version {})", mkbv); + } + + return exit_code; +} + + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/dvd/dvd_dump.ixx new/redumper-b768/dvd/dvd_dump.ixx --- old/redumper-b752/dvd/dvd_dump.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/dvd/dvd_dump.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -834,6 +834,10 @@ LOG("kreon: failed to unlock drive, SCSI ({})", SPTD::StatusMessage(status)); } + // disable jamless play on LG firmware to prevent false-negative read errors + if(is_jamless_play_firmware(ctx.drive_config) && !drive_set_jamless_play(*ctx.sptd, JamlessPlayMode::OFF, JamlessPlayPersist::TEMPORARY)) + LOG("warning: failed to disable Jamless Play"); + // get capacity uint32_t sectors_count_capacity = get_sectors_count_capacity(*ctx.sptd); @@ -979,7 +983,7 @@ for(uint32_t i = 0; i + 1 < layer_lengths.size(); ++i) { layer_break += layer_lengths[i]; - LOG("layer break{}: {}", layer_lengths.size() > 1 ? std::format(" (layer: {})", i) : "", layer_break); + LOG("layer break{}: {}", layer_lengths.size() > 2 ? std::format(" (layer: {})", i) : "", layer_break); } LOG(""); @@ -1120,18 +1124,9 @@ } else if(raw && omnidrive_firmware) { - if(ctx.disc_type == DiscType::DVD) - { - // ensure default total transfer length is less than 65536 bytes (31 * 2064) - LOG("warning: setting dump read size to 31 for raw DVD dumping"); - dump_read_size = 31; - } - else if(ctx.disc_type == DiscType::BLURAY || ctx.disc_type == DiscType::BLURAY_R) - { - // ensure default total transfer length is less than 16384 (7 * 2072) - LOG("warning: setting dump read size to 7 for raw BD dumping"); - dump_read_size = 7; - } + // ensure default total transfer length is less than 65536 bytes (31 * 2070) + LOG("warning: setting dump read size to 31 for raw dumping"); + dump_read_size = 31; } else dump_read_size = DVD_READ_SIZE; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/filesystem/udf/udf.ixx new/redumper-b768/filesystem/udf/udf.ixx --- old/redumper-b752/filesystem/udf/udf.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/filesystem/udf/udf.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -2,3 +2,4 @@ export import :browser; export import :defs; +export import :entry; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/filesystem/udf/udf_browser.ixx new/redumper-b768/filesystem/udf/udf_browser.ixx --- old/redumper-b752/filesystem/udf/udf_browser.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/filesystem/udf/udf_browser.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -1,15 +1,14 @@ module; #include <cstdint> -#include <cstring> #include <memory> #include <optional> #include <string_view> -#include <variant> #include <vector> export module filesystem.udf:browser; import :defs; +import :entry; import cd.cdrom; import readers.data_reader; @@ -59,7 +58,7 @@ return {}; } - static std::variant<std::shared_ptr<udf::FileEntry>, std::shared_ptr<udf::ExtendedFileEntry>> rootDirectory(DataReader *data_reader) + static std::shared_ptr<Entry> rootDirectory(DataReader *data_reader) { // Try and find the AVDP so we can look through the volume descriptor sequence. auto const avdp = findAnchorVolumeDescriptorPointer(data_reader); @@ -129,29 +128,7 @@ 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_starting_locations.size() - 1 >= 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_starting_locations[root_directory_icb_extent.extent_location.partition_reference_number] + root_directory_icb_extent.extent_location.logical_block_number, 1); - - auto const &tag = (udf::DescriptorTag &)root_directory_data[0]; - if(tag.tag_identifier == udf::TagIdentifier::FILE_ENTRY) - { - 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; - } - else if(tag.tag_identifier == udf::TagIdentifier::EXTENDED_FILE_ENTRY) - { - auto root_directory_file_entry = std::shared_ptr<udf::ExtendedFileEntry>(new udf::ExtendedFileEntry()); - std::memcpy(root_directory_file_entry.get(), root_directory_data.data(), sizeof(udf::ExtendedFileEntry)); - - return root_directory_file_entry; - } - } + return std::make_shared<Entry>(data_reader, "", root_directory_icb_extent, partition_starting_locations); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/filesystem/udf/udf_defs.ixx new/redumper-b768/filesystem/udf/udf_defs.ixx --- old/redumper-b752/filesystem/udf/udf_defs.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/filesystem/udf/udf_defs.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -43,6 +43,16 @@ EXTENDED_FILE_ENTRY = 266 }; +// From ECMA 167 14.6.6, not fully filled out since we only handle DIRECTORY here anyway. +enum class FileType : uint8_t +{ + UNSPECIFIED, + UNALLOCATED_SPACE_ENTRY, + PARTITION_INTEGRITY_ENTRY, + INDIRECT_ENTRY, + DIRECTORY +}; + #pragma pack(push, 1) struct DescriptorTag { @@ -196,11 +206,22 @@ uint8_t strategy_parameter[2]; uint16_t maximum_number_of_entries; uint8_t reserved; - uint8_t file_type; + FileType file_type; lb_addr parent_icb_location; uint16_t flags; }; +struct FileIdentifierDescriptor +{ + DescriptorTag descriptor_tag; + uint16_t file_version_number; + uint8_t file_characteristics; + uint8_t length_of_file_identifier; + long_ad icb; + uint16_t length_of_implementation_use; + uint8_t implementation_use_and_file_identifier_and_padding[]; +}; + struct FileEntry { DescriptorTag descriptor_tag; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/filesystem/udf/udf_entry.ixx new/redumper-b768/filesystem/udf/udf_entry.ixx --- old/redumper-b752/filesystem/udf/udf_entry.ixx 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b768/filesystem/udf/udf_entry.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -0,0 +1,204 @@ +module; +#include <cstdint> +#include <cstring> +#include <list> +#include <memory> +#include <string> +#include <variant> +#include <vector> + +export module filesystem.udf:entry; + +import :defs; +import readers.data_reader; +import cd.cdrom; +import utils.misc; +import utils.strings; + + + +namespace gpsxre::udf +{ + +static std::variant<std::shared_ptr<udf::FileEntry>, std::shared_ptr<udf::ExtendedFileEntry>> getFileEntry(DataReader *data_reader, std::vector<uint32_t> partition_starting_locations, + lb_addr file_entry_extent) +{ + if(partition_starting_locations.size() - 1 >= file_entry_extent.partition_reference_number) + { + std::vector<uint8_t> data(FORM1_DATA_SIZE); + data_reader->read(data.data(), partition_starting_locations[file_entry_extent.partition_reference_number] + file_entry_extent.logical_block_number, 1); + + auto const &tag = (udf::DescriptorTag &)data[0]; + + if(tag.tag_identifier == udf::TagIdentifier::FILE_ENTRY) + { + // Parse the static part of the file entry and use it to determine the true size of the file entry. + auto const &static_file_entry = (udf::FileEntry &)data[0]; + std::size_t true_entry_size = sizeof(udf::FileEntry) + static_file_entry.length_of_extended_attributes + static_file_entry.length_of_allocation_descriptors; + + // Allocate the memory to store the entire file entry and read the file entry into that memory. + auto storage = std::make_shared<uint8_t[]>(true_entry_size); + auto file_entry = std::shared_ptr<udf::FileEntry>(storage, (udf::FileEntry *)storage.get()); + std::memcpy(file_entry.get(), data.data(), true_entry_size); + + return file_entry; + } + else if(tag.tag_identifier == udf::TagIdentifier::EXTENDED_FILE_ENTRY) + { + // Parse the static part of the file entry and use it to determine the true size of the file entry. + auto const &static_file_entry = (udf::ExtendedFileEntry &)data[0]; + std::size_t true_entry_size = sizeof(udf::ExtendedFileEntry) + static_file_entry.length_of_extended_attributes + static_file_entry.length_of_allocation_descriptors; + + // Allocate the memory to store the entire file entry and read the file entry into that memory. + auto storage = std::make_shared<uint8_t[]>(true_entry_size); + auto file_entry = std::shared_ptr<udf::ExtendedFileEntry>(storage, (udf::ExtendedFileEntry *)storage.get()); + std::memcpy(file_entry.get(), data.data(), true_entry_size); + + return file_entry; + } + } + + return {}; +} + +} + +export namespace gpsxre::udf +{ + +class Entry +{ +public: + Entry(DataReader *data_reader, std::string name, const udf::long_ad &icb, const std::vector<uint32_t> &partition_starting_locations) + : _dataReader(data_reader) + , _name(name) + , _icb(icb) + , _partitionStartingLocations(partition_starting_locations) + , _fileEntry(getFileEntry(data_reader, partition_starting_locations, icb.extent_location)) + { + ; + } + + const std::string &name() const + { + return _name; + } + + + bool isDirectory() const + { + return std::visit([](const auto &entry) { return entry->icb_tag.file_type == udf::FileType::DIRECTORY; }, _fileEntry); + } + + std::list<std::shared_ptr<Entry>> entries() + { + std::list<std::shared_ptr<Entry>> entries; + + if(isDirectory()) + { + // Read the entire directory into memory. + auto const directory_data = read(); + + // Loop over each file identifier descriptor and record their matching entries. + uint32_t offset = 0; + while(offset < directory_data.size()) + { + auto const &fid = (const udf::FileIdentifierDescriptor &)directory_data[offset]; + + // As per ECMA 167 14.4.3, use bit 3 of the file characteristics to determine if this descriptors ICB refers to the files parent directory, + // in which case we don't need to record it as an entry. + if((fid.file_characteristics & 0x08) == 0) + { + // Read the file identifier into a string, ignoring the first byte as that contains the Compression ID of the string. + const uint8_t *name_data = fid.implementation_use_and_file_identifier_and_padding + fid.length_of_implementation_use; + // The file identifier is technically stored in the OSTA Compressed Unicode format, but for now we just ignore that. + std::string name(name_data + 1, name_data + fid.length_of_file_identifier); + + entries.push_back(std::make_shared<Entry>(_dataReader, name, fid.icb, _partitionStartingLocations)); + } + + // Replicate the effect of the padding bytes at the end of FileIdentifierDescriptor as specified by ECMA 167 14.4.9 + offset += round_up(sizeof(udf::FileIdentifierDescriptor) + fid.length_of_implementation_use + fid.length_of_file_identifier, 4u); + } + } + + return entries; + } + + + std::shared_ptr<Entry> subEntry(const std::string &path) + { + std::shared_ptr<Entry> entry; + + auto components = tokenize(path, "/\\", nullptr); + for(auto const &c : components) + { + auto directory_entries = entry ? entry->entries() : entries(); + auto prospective_entry = std::ranges::find(directory_entries, c, [](auto const &e) { return e->name(); }); + + if(prospective_entry == directory_entries.end()) + { + entry.reset(); + break; + } + else + { + entry = *prospective_entry; + } + } + + return entry; + } + + std::vector<uint8_t> read() + { + // Determine how large the entry is and allocate the memory to read it into. + auto information_length = std::visit([](const auto &entry) -> uint32_t { return entry->information_length; }, _fileEntry); + std::vector<uint8_t> data(round_up(information_length, FORM1_DATA_SIZE)); + + // Determine where the allocation descriptors start and how long they continue for. + const uint8_t *start_of_allocation_descriptors = + std::visit([](const auto &entry) -> const uint8_t * { return entry->extended_attributes_and_allocation_descriptors + entry->length_of_extended_attributes; }, _fileEntry); + auto length_of_allocation_descriptors = std::visit([](const auto &entry) -> uint32_t { return entry->length_of_allocation_descriptors; }, _fileEntry); + + auto icb_tag = std::visit([](const auto &entry) -> auto { return entry->icb_tag; }, _fileEntry); + // As per ECMA 167 14.6.8, use bits 0-2 of the flags as a 3-bit unsigned binary number to determine the type of allocation descriptors. + auto short_descriptors = (icb_tag.flags & 0x07) == 0; + auto descriptor_size = short_descriptors ? sizeof(udf::short_ad) : sizeof(udf::long_ad); + + uint64_t write_offset = 0; + // Loop over every allocation descriptor and read them into data. + for(uint32_t i = 0; i < length_of_allocation_descriptors; i += descriptor_size) + { + if(short_descriptors) + { + udf::short_ad ad = (udf::short_ad &)*(start_of_allocation_descriptors + i); + // TODO: It might be possible to avoid needing to use _icb here, which would allow us to remove it entirely. + auto lba = _partitionStartingLocations[_icb.extent_location.partition_reference_number] + ad.extent_position; + + _dataReader->read(data.data() + write_offset, lba, scale_up(ad.extent_length, FORM1_DATA_SIZE)); + write_offset += ad.extent_length; + } + else + { + udf::long_ad ad = (udf::long_ad &)*(start_of_allocation_descriptors + i); + auto lba = _partitionStartingLocations[ad.extent_location.partition_reference_number] + ad.extent_location.logical_block_number; + + _dataReader->read(data.data() + write_offset, lba, scale_up(ad.extent_length, FORM1_DATA_SIZE)); + write_offset += ad.extent_length; + } + } + + data.resize(information_length); + return data; + } + +private: + DataReader *_dataReader; + std::string _name; + udf::long_ad _icb; + std::vector<uint32_t> _partitionStartingLocations; + std::variant<std::shared_ptr<udf::FileEntry>, std::shared_ptr<udf::ExtendedFileEntry>> _fileEntry; +}; + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/filesystem/udf/udf_size.ixx new/redumper-b768/filesystem/udf/udf_size.ixx --- old/redumper-b752/filesystem/udf/udf_size.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/filesystem/udf/udf_size.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -24,7 +24,7 @@ if(metadata_end >= std::numeric_limits<uint32_t>::max()) return std::nullopt; - return metadata_end + 1; + return static_cast<uint32_t>(metadata_end + 1); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/hash/aes128.ixx new/redumper-b768/hash/aes128.ixx --- old/redumper-b752/hash/aes128.ixx 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b768/hash/aes128.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -0,0 +1,121 @@ +module; +#include <array> +#include <cstdint> + +export module hash.aes128; + + + +namespace gpsxre +{ + +export class AES128 +{ +public: + static constexpr uint32_t BLOCK_SIZE = 16; + + explicit AES128(const std::array<uint8_t, BLOCK_SIZE> &key) + { + for(uint32_t i = 0; i < BLOCK_SIZE; ++i) + _roundKeys[i] = key[i]; + + uint8_t round_constant = 1; + for(uint32_t offset = BLOCK_SIZE; offset < _roundKeys.size(); offset += 4) + { + std::array<uint8_t, 4> word; + for(uint32_t i = 0; i < word.size(); ++i) + word[i] = _roundKeys[offset - word.size() + i]; + + if(offset % BLOCK_SIZE == 0) + { + uint8_t first = word[0]; + word[0] = _SBOX[word[1]] ^ round_constant; + word[1] = _SBOX[word[2]]; + word[2] = _SBOX[word[3]]; + word[3] = _SBOX[first]; + round_constant = xtime(round_constant); + } + + for(uint32_t i = 0; i < word.size(); ++i) + _roundKeys[offset + i] = _roundKeys[offset - BLOCK_SIZE + i] ^ word[i]; + } + } + + + std::array<uint8_t, BLOCK_SIZE> encryptBlock(const std::array<uint8_t, BLOCK_SIZE> &block) const + { + auto state = block; + addRoundKey(state, 0); + + for(uint32_t round = 1; round < 10; ++round) + { + subBytes(state); + shiftRows(state); + mixColumns(state); + addRoundKey(state, round); + } + + subBytes(state); + shiftRows(state); + addRoundKey(state, 10); + return state; + } + +private: + std::array<uint8_t, 11 * BLOCK_SIZE> _roundKeys; + + static constexpr std::array<uint8_t, 256> _SBOX{ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, + 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, + 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, + 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, + 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, + 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, + 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, + 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, + 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 }; + + static uint8_t xtime(uint8_t value) + { + return (uint8_t)((value << 1) ^ (value & 0x80 ? 0x1B : 0)); + } + + void addRoundKey(std::array<uint8_t, BLOCK_SIZE> &state, uint32_t round) const + { + for(uint32_t i = 0; i < BLOCK_SIZE; ++i) + state[i] ^= _roundKeys[round * BLOCK_SIZE + i]; + } + + static void subBytes(std::array<uint8_t, BLOCK_SIZE> &state) + { + for(auto &byte : state) + byte = _SBOX[byte]; + } + + static void shiftRows(std::array<uint8_t, BLOCK_SIZE> &state) + { + auto old = state; + for(uint32_t column = 0; column < 4; ++column) + for(uint32_t row = 1; row < 4; ++row) + state[4 * column + row] = old[4 * ((column + row) % 4) + row]; + } + + static void mixColumns(std::array<uint8_t, BLOCK_SIZE> &state) + { + for(uint32_t column = 0; column < 4; ++column) + { + uint32_t offset = 4 * column; + uint8_t a = state[offset]; + uint8_t b = state[offset + 1]; + uint8_t c = state[offset + 2]; + uint8_t d = state[offset + 3]; + uint8_t all = a ^ b ^ c ^ d; + + state[offset] ^= all ^ xtime(a ^ b); + state[offset + 1] ^= all ^ xtime(b ^ c); + state[offset + 2] ^= all ^ xtime(c ^ d); + state[offset + 3] ^= all ^ xtime(d ^ a); + } + } +}; + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/hash/cmac.ixx new/redumper-b768/hash/cmac.ixx --- old/redumper-b752/hash/cmac.ixx 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b768/hash/cmac.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -0,0 +1,111 @@ +module; +#include <algorithm> +#include <array> +#include <cstdint> + +export module hash.cmac; + +import hash.aes128; + + + +namespace gpsxre +{ + +export class CMAC +{ +public: + explicit CMAC(const std::array<uint8_t, AES128::BLOCK_SIZE> &key) + : _aes(key) + { + std::array<uint8_t, AES128::BLOCK_SIZE> zero_block; + zero_block.fill(0); + auto l = _aes.encryptBlock(zero_block); + _subkey1 = doubleBlock(l); + _subkey2 = doubleBlock(_subkey1); + reset(); + } + + CMAC &reset() + { + _chain.fill(0); + _tail.fill(0); + _tailSize = 0; + return *this; + } + + CMAC &update(const uint8_t *data, uint64_t size) + { + while(size != 0) + { + if(_tailSize == AES128::BLOCK_SIZE) + { + updateBlock(_tail); + _tail.fill(0); + _tailSize = 0; + } + + uint32_t count = (uint32_t)std::min<uint64_t>(AES128::BLOCK_SIZE - _tailSize, size); + std::copy_n(data, count, _tail.begin() + _tailSize); + data += count; + size -= count; + _tailSize += count; + } + return *this; + } + + std::array<uint8_t, AES128::BLOCK_SIZE> final() + { + auto last = _tail; + if(_tailSize == AES128::BLOCK_SIZE) + { + for(uint32_t i = 0; i < AES128::BLOCK_SIZE; ++i) + last[i] ^= _subkey1[i]; + } + else + { + last[_tailSize] = 0x80; + for(uint32_t i = 0; i < AES128::BLOCK_SIZE; ++i) + last[i] ^= _subkey2[i]; + } + + for(uint32_t i = 0; i < AES128::BLOCK_SIZE; ++i) + last[i] ^= _chain[i]; + + auto result = _aes.encryptBlock(last); + reset(); + return result; + } + +private: + AES128 _aes; + std::array<uint8_t, AES128::BLOCK_SIZE> _subkey1; + std::array<uint8_t, AES128::BLOCK_SIZE> _subkey2; + std::array<uint8_t, AES128::BLOCK_SIZE> _chain; + std::array<uint8_t, AES128::BLOCK_SIZE> _tail; + uint32_t _tailSize; + + static std::array<uint8_t, AES128::BLOCK_SIZE> doubleBlock(const std::array<uint8_t, AES128::BLOCK_SIZE> &block) + { + std::array<uint8_t, AES128::BLOCK_SIZE> doubled; + uint8_t carry = 0; + for(uint32_t i = AES128::BLOCK_SIZE; i > 0; --i) + { + doubled[i - 1] = (uint8_t)((block[i - 1] << 1) | carry); + carry = block[i - 1] >> 7; + } + if(carry) + doubled.back() ^= 0x87; + return doubled; + } + + void updateBlock(const std::array<uint8_t, AES128::BLOCK_SIZE> &block) + { + auto input = block; + for(uint32_t i = 0; i < AES128::BLOCK_SIZE; ++i) + input[i] ^= _chain[i]; + _chain = _aes.encryptBlock(input); + } +}; + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/options.ixx new/redumper-b768/options.ixx --- old/redumper-b752/options.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/options.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -431,11 +431,11 @@ LOG("\t--disc-type=VALUE \toverride detected disc type (current profile), possible values: CD, DVD, BLURAY, BLURAY-R, HD-DVD"); LOG(""); LOG("\t(drive configuration)"); - LOG("\t--drive-type=VALUE \toverride drive type, possible values: GENERIC, PLEXTOR, MTK2, MTK3, MTK8A, MTK8B, MTK8C"); + LOG("\t--drive-type=VALUE \toverride drive type, possible values: GENERIC, PLEXTOR, MTK2, MTK2B, MTK3, MTK8A, MTK8B, MTK8C"); LOG("\t--drive-read-offset=VALUE \toverride drive read offset"); LOG("\t--drive-c2-shift=VALUE \toverride drive C2 shift"); LOG("\t--drive-pregap-start=VALUE \toverride drive pre-gap start LBA"); - LOG("\t--drive-read-method=VALUE \toverride drive read method, possible values: BE, D8, BE_CDDA"); + LOG("\t--drive-read-method=VALUE \toverride drive read method, possible values: BE, D8"); LOG("\t--drive-sector-order=VALUE \toverride drive sector order, possible values: DATA_C2_SUB, DATA_SUB_C2, DATA_SUB, DATA_C2"); LOG("\t--auto-detect \tattempt to automatically detect drive sector order for generic drives"); LOG(""); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/redumper.ixx new/redumper-b768/redumper.ixx --- old/redumper-b752/redumper.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/redumper.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -14,6 +14,7 @@ export module redumper; +import bd.key; import cd.cd; import cd.dump; import cd.dump_extra; @@ -131,6 +132,7 @@ { "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 } }, + { "bdkey", { true, true, true, false, false, redumper_bdkey } }, { "eject", { true, false, false, false, false, redumper_eject } }, { "dvdisokey", { false, false, false, true, false, redumper_dvdisokey } }, { "protection", { false, false, false, true, false, redumper_protection } }, @@ -527,6 +529,13 @@ else LOG(" firmware: OmniDrive {}", omnidrive_version_string(*version)); } + else if(auto version = is_spiraldrive_firmware(ctx.drive_config)) + { + if(*version < spiraldrive_minimum_version()) + LOG(" warning: outdated SpiralDrive drive firmware (current: {}, recommended: {})", spiraldrive_version_string(*version), spiraldrive_version_string(spiraldrive_minimum_version())); + else + LOG(" firmware: SpiralDrive {}", spiraldrive_version_string(*version)); + } } if(!options.image_name.empty()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/scsi/cmd.ixx new/redumper-b768/scsi/cmd.ixx --- old/redumper-b752/scsi/cmd.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/scsi/cmd.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -273,7 +273,20 @@ cdb.include_sync_data = expected_sector_type == READ_CD_ExpectedSectorType::CD_DA ? 0 : 1; cdb.sub_channel_selection = (uint8_t)sub_channel; - return sptd.sendCommand(&cdb, sizeof(cdb), sectors, block_size * transfer_length).first; + // request exactly what the drive will return: some USB bridges (e.g. ASMedia ASM1153) corrupt + // data when the allocation length exceeds the actual transfer + uint32_t sector_size = CD_DATA_SIZE; + if(error_field == READ_CD_ErrorField::C2) + sector_size += CD_C2_SIZE; + else if(error_field == READ_CD_ErrorField::C2_BEB) + sector_size += CD_C2_SIZE + 2; + if(sub_channel == READ_CD_SubChannel::Q) + sector_size += 16; + else if(sub_channel != READ_CD_SubChannel::NONE) + sector_size += CD_SUBCODE_SIZE; + sector_size = std::min(sector_size, block_size); + + return sptd.sendCommand(&cdb, sizeof(cdb), sectors, sector_size * transfer_length).first; } @@ -288,7 +301,7 @@ cdb.sub_code = (uint8_t)sub_code; auto [status, transferred_length] = sptd.sendCommand(&cdb, sizeof(cdb), sectors, block_size * transfer_length); - if(!status.status_code && transferred_length != READ_CDDA_SIZES[(uint8_t)sub_code] * transfer_length) + if(!status.status_code && transferred_length < READ_CDDA_SIZES[(uint8_t)sub_code] * transfer_length) status.status_code = SPTD::HOST_SHORT_TRANSFER; return status; @@ -353,6 +366,33 @@ } +export SPTD::Status cmd_get_configuration_firmware_information(SPTD &sptd, GET_CONFIGURATION_FirmwareInformationBody *firmware_information) +{ + CDB10_GetConfiguration cdb = {}; + cdb.operation_code = (uint8_t)CDB_OperationCode::GET_CONFIGURATION; + cdb.requested_type = (uint8_t)GET_CONFIGURATION_RequestedType::ONE; + cdb.starting_feature_number = endian_swap<uint16_t>(0x10C); + + uint16_t size = sizeof(GET_CONFIGURATION_FeatureHeader) + sizeof(GET_CONFIGURATION_FeatureDescriptor) + sizeof(GET_CONFIGURATION_FirmwareInformationBody); + *(uint16_t *)cdb.allocation_length = endian_swap(size); + std::vector<uint8_t> buffer(size); + + auto status = sptd.sendCommand(&cdb, sizeof(cdb), buffer.data(), buffer.size()).first; + + GET_CONFIGURATION_FeatureHeader feature_header = {}; + memcpy(&feature_header, buffer.data(), sizeof(GET_CONFIGURATION_FeatureHeader)); + if(!status.status_code && feature_header.data_length == 4) + { + status.status_code = SPTD::HOST_SHORT_TRANSFER; + return status; + } + + memcpy(firmware_information, buffer.data() + sizeof(GET_CONFIGURATION_FeatureHeader) + sizeof(GET_CONFIGURATION_FeatureDescriptor), sizeof(*firmware_information)); + + return status; +} + + SPTD::Status cmd_get_configuration(SPTD &sptd) { CDB10_GetConfiguration cdb = {}; @@ -443,11 +483,12 @@ } -export SPTD::Status cmd_write_buffer(SPTD &sptd, const uint8_t *data, uint32_t data_size, WRITE_BUFFER_Mode mode, uint32_t buffer_offset, uint32_t parameter_list_length) +export SPTD::Status cmd_write_buffer(SPTD &sptd, const uint8_t *data, uint32_t data_size, WRITE_BUFFER_Mode mode, uint8_t buffer_id, uint32_t buffer_offset, uint32_t parameter_list_length) { CDB10_WriteBuffer cdb = {}; cdb.operation_code = (uint8_t)CDB_OperationCode::WRITE_BUFFER; cdb.mode = (uint8_t)mode; + cdb.buffer_id = buffer_id; cdb.buffer_offset[0] = ((uint8_t *)&buffer_offset)[2]; cdb.buffer_offset[1] = ((uint8_t *)&buffer_offset)[1]; cdb.buffer_offset[2] = ((uint8_t *)&buffer_offset)[0]; @@ -459,11 +500,12 @@ } -export SPTD::Status cmd_read_buffer(SPTD &sptd, uint8_t *data, uint32_t data_size, READ_BUFFER_Mode mode, uint32_t buffer_offset, uint32_t allocation_length) +export SPTD::Status cmd_read_buffer(SPTD &sptd, uint8_t *data, uint32_t data_size, READ_BUFFER_Mode mode, uint8_t buffer_id, uint32_t buffer_offset, uint32_t allocation_length) { CDB10_ReadBuffer cdb = {}; cdb.operation_code = (uint8_t)CDB_OperationCode::READ_BUFFER; cdb.mode = (uint8_t)mode; + cdb.buffer_id = buffer_id; cdb.buffer_offset[0] = ((uint8_t *)&buffer_offset)[2]; cdb.buffer_offset[1] = ((uint8_t *)&buffer_offset)[1]; cdb.buffer_offset[2] = ((uint8_t *)&buffer_offset)[0]; @@ -489,7 +531,24 @@ *(int32_t *)cdb.address = endian_swap(address); *(uint32_t *)cdb.transfer_blocks = endian_swap(transfer_length); - return sptd.sendCommand(&cdb, sizeof(cdb), buffer, block_size * transfer_length).first; + auto [status, transferred] = sptd.sendCommand(&cdb, sizeof(cdb), buffer, block_size * transfer_length); + + if(!status.status_code && transferred != block_size * transfer_length) + status.status_code = SPTD::HOST_SHORT_TRANSFER; + + return status; +} + + +export SPTD::Status cmd_spiraldrive(SPTD &sptd, uint8_t *buffer, uint32_t buffer_size, SpiralDrive_Operation operation, uint16_t timeout, int32_t value) +{ + CDB12_SpiralDrive cdb = {}; + cdb.operation_code = (uint8_t)CDB_OperationCode::SPIRALDRIVE; + cdb.operation = (uint8_t)operation; + cdb.timeout = endian_swap(timeout); + cdb.value = endian_swap(value); + + return sptd.sendCommand(&cdb, sizeof(cdb), buffer, buffer_size).first; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/scsi/mmc.ixx new/redumper-b768/scsi/mmc.ixx --- old/redumper-b752/scsi/mmc.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/scsi/mmc.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -28,6 +28,7 @@ READ_CD_MSF = 0xB9, READ_CD = 0xBE, READ_OMNIDRIVE = 0xC0, + SPIRALDRIVE = 0xC4, READ_CD_MSF_D5 = 0xD5, READ_CDDA = 0xD8, PLEXTOR_RESET = 0xEE, @@ -286,6 +287,13 @@ }; +export enum class SpiralDrive_Operation : uint8_t +{ + SEEK = 1, + READ = 2 +}; + + export struct CMD_ParameterListHeader { uint16_t data_length; @@ -397,6 +405,17 @@ uint8_t feature_dependent_data[0]; }; +export struct GET_CONFIGURATION_FirmwareInformationBody +{ + char century[2]; + char year[2]; + char month[2]; + char day[2]; + char hour[2]; + char minutes[2]; + char seconds[2]; + uint8_t reserved[2]; +}; export struct READ_DVD_STRUCTURE_StructureListEntry { @@ -914,7 +933,8 @@ DOWNLOAD_MICROCODE_WITH_OFFSETS_SAVE, ECHO_BUFFER = 0x0A, ECHO_BUFFER_DESCRIPTOR, - RESERVED2 + RESERVED2, + LG_JAMLESS_PLAY_CONTROL }; @@ -946,4 +966,14 @@ uint8_t control; }; + +export struct CDB12_SpiralDrive +{ + uint8_t operation_code; + uint8_t operation; + uint16_t timeout; + uint32_t value; + uint32_t reserved; +}; + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/scsi/sptd.ixx new/redumper-b768/scsi/sptd.ixx --- old/redumper-b752/scsi/sptd.ixx 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/scsi/sptd.ixx 2026-10-23 03:56:38.000000000 +0200 @@ -54,6 +54,7 @@ uint8_t sense_key; uint8_t asc; uint8_t ascq; + uint8_t information[4]; }; @@ -187,6 +188,7 @@ status.sense_key = sptd_sd.sd.SenseKey; status.asc = sptd_sd.sd.AdditionalSenseCode; status.ascq = sptd_sd.sd.AdditionalSenseCodeQualifier; + memcpy(status.information, sptd_sd.sd.Information, sizeof(sptd_sd.sd.Information)); } #elif defined(__APPLE__) auto task = make_unique_resource_checked((*_scsiTaskDeviceInterface.get())->CreateSCSITask(_scsiTaskDeviceInterface.get()), (SCSITaskInterface **)nullptr, @@ -226,6 +228,10 @@ status.sense_key = sense_data.SENSE_KEY & 0x0F; status.asc = sense_data.ADDITIONAL_SENSE_CODE; status.ascq = sense_data.ADDITIONAL_SENSE_CODE_QUALIFIER; + status.information[0] = sense_data.INFORMATION_1; + status.information[1] = sense_data.INFORMATION_2; + status.information[2] = sense_data.INFORMATION_3; + status.information[3] = sense_data.INFORMATION_4; } #else SenseData sense_data; @@ -253,6 +259,7 @@ status.sense_key = sense_data.sense_key; status.asc = sense_data.additional_sense_code; status.ascq = sense_data.additional_sense_code_qualifier; + memcpy(status.information, sense_data.information, sizeof(sense_data.information)); } #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/tests/gtest/CMakeLists.txt new/redumper-b768/tests/gtest/CMakeLists.txt --- old/redumper-b752/tests/gtest/CMakeLists.txt 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/tests/gtest/CMakeLists.txt 2026-10-23 03:56:38.000000000 +0200 @@ -76,6 +76,13 @@ "${CMAKE_SOURCE_DIR}/utils/misc.ixx" ) +add_gtest(gtest_aes_cmac + SOURCE "test_aes_cmac.cc" + MODULES + "${CMAKE_SOURCE_DIR}/hash/aes128.ixx" + "${CMAKE_SOURCE_DIR}/hash/cmac.ixx" +) + add_gtest(gtest_range SOURCE "test_range.cc" MODULES "${CMAKE_SOURCE_DIR}/range.ixx" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/tests/gtest/test_aes_cmac.cc new/redumper-b768/tests/gtest/test_aes_cmac.cc --- old/redumper-b752/tests/gtest/test_aes_cmac.cc 1970-01-01 01:00:00.000000000 +0100 +++ new/redumper-b768/tests/gtest/test_aes_cmac.cc 2026-10-23 03:56:38.000000000 +0200 @@ -0,0 +1,68 @@ +#include <algorithm> +#include <array> +#include <cstdint> +#include <gtest/gtest.h> +#include <string_view> +#include <vector> + +import hash.aes128; +import hash.cmac; + +using namespace gpsxre; + + +uint8_t hexDigit(char c) +{ + return (uint8_t)(c <= '9' ? c - '0' : c - 'a' + 10); +} + +std::vector<uint8_t> hexBytes(std::string_view hex) +{ + std::vector<uint8_t> bytes; + for(uint32_t i = 0; i < hex.size(); i += 2) + bytes.push_back((uint8_t)((hexDigit(hex[i]) << 4) | hexDigit(hex[i + 1]))); + return bytes; +} + +std::array<uint8_t, AES128::BLOCK_SIZE> hexBlock(std::string_view hex) +{ + auto bytes = hexBytes(hex); + std::array<uint8_t, AES128::BLOCK_SIZE> block; + std::copy(bytes.begin(), bytes.end(), block.begin()); + return block; +} + +const auto CMAC_KEY = hexBlock("2b7e151628aed2a6abf7158809cf4f3c"); +const auto CMAC_MESSAGE = hexBytes("6bc1bee22e409f96e93d7e117393172a" "ae2d8a571e03ac9c9eb76fac45af8e51" "30c81c46a35ce411e5fbc1191a0a52ef" "f69f2445df4f9b17ad2b417be66c3710"); + + +TEST(Aes128, EncryptBlock) +{ + AES128 aes(hexBlock("000102030405060708090a0b0c0d0e0f")); + EXPECT_EQ(aes.encryptBlock(hexBlock("00112233445566778899aabbccddeeff")), hexBlock("69c4e0d86a7b0430d8cdb78070b4c55a")); +} + + +TEST(Cmac, NistKnownAnswers) +{ + EXPECT_EQ(CMAC(CMAC_KEY).final(), hexBlock("bb1d6929e95937287fa37d129b756746")); + EXPECT_EQ(CMAC(CMAC_KEY).update(CMAC_MESSAGE.data(), 16).final(), hexBlock("070a16b46b4d4144f79bdd9dd04a287c")); + EXPECT_EQ(CMAC(CMAC_KEY).update(CMAC_MESSAGE.data(), 40).final(), hexBlock("dfa66747de9ae63030ca32611497c827")); + EXPECT_EQ(CMAC(CMAC_KEY).update(CMAC_MESSAGE.data(), 64).final(), hexBlock("51f0bebf7e3b9d92fc49741779363cfe")); +} + + +TEST(Cmac, SplitUpdatesAndReset) +{ + auto expected = hexBlock("dfa66747de9ae63030ca32611497c827"); + CMAC cmac(CMAC_KEY); + cmac.update(CMAC_MESSAGE.data(), 1).update(CMAC_MESSAGE.data() + 1, 15).update(CMAC_MESSAGE.data() + 16, 16).update(CMAC_MESSAGE.data() + 32, 8); + EXPECT_EQ(cmac.final(), expected); + + // final() resets the message state while retaining the key. + EXPECT_EQ(cmac.update(CMAC_MESSAGE.data(), 40).final(), expected); + + cmac.update(CMAC_MESSAGE.data(), 7).reset(); + EXPECT_EQ(cmac.update(CMAC_MESSAGE.data(), 40).final(), expected); + EXPECT_EQ(cmac.update(nullptr, 0).final(), hexBlock("bb1d6929e95937287fa37d129b756746")); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redumper-b752/tests/gtest/test_udf.cc new/redumper-b768/tests/gtest/test_udf.cc --- old/redumper-b752/tests/gtest/test_udf.cc 2026-09-18 02:05:14.000000000 +0200 +++ new/redumper-b768/tests/gtest/test_udf.cc 2026-10-23 03:56:38.000000000 +0200 @@ -36,3 +36,10 @@ EXPECT_EQ(udf::get_volume_sectors_count(1000, 0, 0, 0), std::nullopt); EXPECT_EQ(udf::get_volume_sectors_count(1000, std::numeric_limits<uint32_t>::max(), 2048, 2048), std::nullopt); } + + +TEST(UDF, VolumeSectorsCountHandlesUpperBoundary) +{ + EXPECT_EQ(udf::get_volume_sectors_count(std::numeric_limits<uint32_t>::max() - 1, 0, 0, 2048), std::numeric_limits<uint32_t>::max()); + EXPECT_EQ(udf::get_volume_sectors_count(std::numeric_limits<uint32_t>::max(), 0, 0, 2048), std::nullopt); +}
