From c6b3a48f49175e27232029e67e053a1599eed890 Mon Sep 17 00:00:00 2001 From: Hennadiy Brych Date: Thu, 7 Dec 2023 18:20:12 -0500 Subject: [PATCH] sources reorg: separate out protection code --- CMakeLists.txt | 1 + cd/protection.ixx | 271 +++++++++++++++++++++++++++++++++++++++++++ cd/split.ixx | 290 ---------------------------------------------- dump.ixx | 51 +++++++- redumper.ixx | 1 + 5 files changed, 323 insertions(+), 291 deletions(-) create mode 100644 cd/protection.ixx diff --git a/CMakeLists.txt b/CMakeLists.txt index 32f2a4a..2f41a00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ target_sources(redumper "cd/ecc.ixx" "cd/edc.ixx" "cd/offset_manager.ixx" + "cd/protection.ixx" "cd/scrambler.ixx" "cd/split.ixx" "cd/subcode.ixx" diff --git a/cd/protection.ixx b/cd/protection.ixx new file mode 100644 index 0000000..4634838 --- /dev/null +++ b/cd/protection.ixx @@ -0,0 +1,271 @@ +module; +#include +#include +#include +#include +#include +#include +#include "throw_line.hh" + +export module cd.protection; + +import cd.cd; +import cd.subcode; +import cd.toc; +import dump; +import filesystem.iso9660; +import options; +import readers.image_bin_form1_reader; +import utils.file_io; +import utils.logger; +import utils.misc; +import utils.strings; + + + +namespace gpsxre +{ + +export void redumper_protection(Context &ctx, Options &options) +{ + if(options.image_name.empty()) + throw_line("image name is not provided"); + + auto image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); + + std::filesystem::path scm_path(image_prefix + ".scram"); + std::filesystem::path scp_path(image_prefix + ".scrap"); + std::filesystem::path sub_path(image_prefix + ".subcode"); + std::filesystem::path state_path(image_prefix + ".state"); + std::filesystem::path toc_path(image_prefix + ".toc"); + std::filesystem::path fulltoc_path(image_prefix + ".fulltoc"); + + bool scrap = !std::filesystem::exists(scm_path) && std::filesystem::exists(scp_path); + auto scra_path(scrap ? scp_path : scm_path); + + //TODO: rework + uint32_t sectors_count = check_file(state_path, CD_DATA_SIZE_SAMPLES); + + // TOC + std::vector toc_buffer = read_vector(toc_path); + TOC toc(toc_buffer, false); + + // FULL TOC + if(std::filesystem::exists(fulltoc_path)) + { + std::vector fulltoc_buffer = read_vector(fulltoc_path); + TOC toc_full(fulltoc_buffer, true); + if(toc_full.sessions.size() > 1) + toc = toc_full; + } + + { + auto &t = toc.sessions.back().tracks.back(); + + // fake TOC + if(t.lba_end < 0) + { + LOG("warning: fake TOC detected, using default 74min disc size"); + t.lba_end = MSF_to_LBA(MSF{74, 0, 0}); + } + + // incomplete dump (dumped with --stop-lba) + if(t.lba_end > (int32_t)sectors_count + LBA_START) + { + LOG("warning: incomplete dump detected, using available dump size"); + t.lba_end = (int32_t)sectors_count + LBA_START; + } + } + + std::fstream scm_fs(scra_path, std::fstream::in | std::fstream::binary); + if(!scm_fs.is_open()) + throw_line("unable to open file ({})", scra_path.filename().string()); + + std::fstream state_fs(state_path, std::fstream::in | std::fstream::binary); + if(!state_fs.is_open()) + throw_line("unable to open file ({})", state_path.filename().string()); + + std::string protection("N/A"); + + // SafeDisc + // first track is data + if(toc.sessions.size() == 1 && toc.sessions.front().tracks.size() >= 2) + { + auto &t = toc.sessions.front().tracks[0]; + auto &t_next = toc.sessions.front().tracks[1]; + + if(t.control & (uint8_t)ChannelQ::Control::DATA) + { + int32_t write_offset = track_offset_by_sync(t.lba_start, t_next.lba_start, state_fs, scm_fs); + + if(write_offset != std::numeric_limits::max()) + { + uint32_t file_offset = (t.lba_start - LBA_START) * CD_DATA_SIZE + write_offset * CD_SAMPLE_SIZE; + auto form1_reader = std::make_unique(scm_fs, file_offset, t_next.lba_start - t.lba_start, !scrap); + + iso9660::PrimaryVolumeDescriptor pvd; + if(iso9660::Browser::findDescriptor((iso9660::VolumeDescriptor &)pvd, form1_reader.get(), iso9660::VolumeDescriptorType::PRIMARY)) + { + auto root_directory = iso9660::Browser::rootDirectory(form1_reader.get(), pvd); + auto entry = root_directory->subEntry("00000001.TMP"); + if(entry) + { + std::vector state(CD_DATA_SIZE_SAMPLES); + + int32_t lba_start = entry->sectorsOffset() + entry->sectorsSize(); + int32_t lba_end = lba_start; + auto entries = root_directory->entries(); + for(auto &e : entries) + { + if(e->isDirectory()) + continue; + + auto entry_offset = e->sectorsOffset(); + if(entry_offset <= lba_start) + continue; + + if(lba_end == lba_start || entry_offset < lba_end) + lba_end = entry_offset; + } + + // verified: 78 + const std::set safedisc_c2_samples = { 60, 66, 72, 78 }; + + std::vector errors; + for(int32_t lba = lba_start; lba < lba_end; ++lba) + { + read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba - LBA_START, 1, -write_offset, (uint8_t)State::ERROR_SKIP); + + uint32_t error_count = 0; + for(auto const &s : state) + if(s == State::ERROR_C2) + ++error_count; + + if(safedisc_c2_samples.find(error_count) != safedisc_c2_samples.end()) + errors.push_back(lba); + } + + if(!errors.empty()) + { + protection = std::format("SafeDisc {}, C2: {}, gap range: {}-{}", entry->name(), errors.size(), lba_start, lba_end - 1); + + auto skip_ranges = string_to_ranges(options.skip); + for(auto e : errors) + skip_ranges.emplace_back(e, e + 1); + options.skip = ranges_to_string(skip_ranges); + } + } + } + } + } + } + + + // PS2 Datel + // only one data track + if(toc.sessions.size() == 1 && toc.sessions.front().tracks.size() == 2) + { + auto &t = toc.sessions.front().tracks[0]; + auto &t_next = toc.sessions.front().tracks[1]; + + if(t.control & (uint8_t)ChannelQ::Control::DATA) + { + std::vector state(CD_DATA_SIZE_SAMPLES); + + int32_t write_offset = track_offset_by_sync(t.lba_start, t_next.lba_start, state_fs, scm_fs); + if(write_offset != std::numeric_limits::max()) + { + // preliminary check + bool candidate = false; + { + constexpr int32_t lba_check = 50; + if(lba_check >= t.lba_start && lba_check < t_next.lba_start) + { + read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba_check - LBA_START, 1, -write_offset, (uint8_t)State::ERROR_SKIP); + for(auto const &s : state) + if(s == State::ERROR_C2) + { + candidate = true; + break; + } + } + } + + if(candidate) + { + constexpr int32_t first_file_offset = 23; + + std::string protected_filename; + { + uint32_t file_offset = (t.lba_start - LBA_START) * CD_DATA_SIZE + write_offset * CD_SAMPLE_SIZE; + auto form1_reader = std::make_unique(scm_fs, file_offset, t_next.lba_start - t.lba_start, !scrap); + + iso9660::PrimaryVolumeDescriptor pvd; + if(iso9660::Browser::findDescriptor((iso9660::VolumeDescriptor &)pvd, form1_reader.get(), iso9660::VolumeDescriptorType::PRIMARY)) + { + auto root_directory = iso9660::Browser::rootDirectory(form1_reader.get(), pvd); + + static const std::string datel_files[] = { "DATA.DAT", "BIG.DAT", "DUMMY.ZIP" }; + for(auto const &f : datel_files) + { + // protection file exists + auto entry = root_directory->subEntry(f); + if(!entry) + continue; + + // first file on disc and starts from LBA 23 + if(entry->sectorsOffset() == first_file_offset) + { + protected_filename = entry->name(); + break; + } + } + } + } + + if(!protected_filename.empty()) + { + std::pair range(0, 0); + for(int32_t lba = first_file_offset, lba_end = std::min(t_next.lba_start, 5000); lba < lba_end; ++lba) + { + read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba - LBA_START, 1, -write_offset, (uint8_t)State::ERROR_SKIP); + + bool error = false; + for(auto const &s : state) + if(s == State::ERROR_C2) + { + error = true; + break; + } + + if(error) + { + if(!range.first) + range.first = lba; + range.second = lba + 1; + } + else + { + if(range.first) + break; + } + } + + if(range.second > range.first) + { + protection = std::format("PS2/Datel {}, C2: {}, range: {}-{}", protected_filename, range.second - range.first, range.first, range.second - 1); + + auto skip_ranges = string_to_ranges(options.skip); + skip_ranges.push_back(range); + options.skip = ranges_to_string(skip_ranges); + } + } + } + } + } + } + + LOG("protection: {}", protection); +} + +} diff --git a/cd/split.ixx b/cd/split.ixx index dd1601e..878084c 100644 --- a/cd/split.ixx +++ b/cd/split.ixx @@ -46,53 +46,6 @@ const uint32_t OFFSET_SHIFT_MAX_SECTORS = 4; const uint32_t OFFSET_SHIFT_SYNC_TOLERANCE = 2; -int32_t track_offset_by_sync(int32_t lba_start, int32_t lba_end, std::fstream &state_fs, std::fstream &scm_fs) -{ - int32_t write_offset = std::numeric_limits::max(); - - constexpr uint32_t sectors_to_check = 2; - - std::vector data(sectors_to_check * CD_DATA_SIZE); - std::vector state(sectors_to_check * CD_DATA_SIZE_SAMPLES); - - uint32_t groups_count = (lba_end - lba_start) / sectors_to_check; - for(uint32_t i = 0; i < groups_count; ++i) - { - int32_t lba = lba_start + i * sectors_to_check; - read_entry(scm_fs, data.data(), CD_DATA_SIZE, lba - LBA_START, sectors_to_check, 0, 0); - read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba - LBA_START, sectors_to_check, 0, (uint8_t)State::ERROR_SKIP); - - for(auto const &s : state) - if(s == State::ERROR_SKIP || s == State::ERROR_C2) - continue; - - auto it = std::search(data.begin(), data.end(), std::begin(CD_DATA_SYNC), std::end(CD_DATA_SYNC)); - if(it != data.end()) - { - auto sector_offset = (uint32_t)(it - data.begin()); - - // enough data for one sector - if(data.size() - sector_offset >= CD_DATA_SIZE) - { - Sector §or = *(Sector *)&data[sector_offset]; - Scrambler scrambler; - scrambler.descramble((uint8_t *)§or, nullptr); - - if(BCDMSF_valid(sector.header.address)) - { - int32_t sector_lba = BCDMSF_to_LBA(sector.header.address); - write_offset = ((int32_t)sector_offset - (sector_lba - lba) * (int32_t)CD_DATA_SIZE) / (int32_t)CD_SAMPLE_SIZE; - - break; - } - } - } - } - - return write_offset; -} - - int32_t byte_offset_by_magic(int32_t lba_start, int32_t lba_end, std::fstream &state_fs, std::fstream &scm_fs, const std::vector &magic) { int32_t write_offset = std::numeric_limits::max(); @@ -901,249 +854,6 @@ void disc_offset_normalize_records(std::vector &records, s } -export void redumper_protection(Context &ctx, Options &options) -{ - if(options.image_name.empty()) - throw_line("image name is not provided"); - - auto image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); - - std::filesystem::path scm_path(image_prefix + ".scram"); - std::filesystem::path scp_path(image_prefix + ".scrap"); - std::filesystem::path sub_path(image_prefix + ".subcode"); - std::filesystem::path state_path(image_prefix + ".state"); - std::filesystem::path toc_path(image_prefix + ".toc"); - std::filesystem::path fulltoc_path(image_prefix + ".fulltoc"); - - bool scrap = !std::filesystem::exists(scm_path) && std::filesystem::exists(scp_path); - auto scra_path(scrap ? scp_path : scm_path); - - //TODO: rework - uint32_t sectors_count = check_file(state_path, CD_DATA_SIZE_SAMPLES); - - // TOC - std::vector toc_buffer = read_vector(toc_path); - TOC toc(toc_buffer, false); - - // FULL TOC - if(std::filesystem::exists(fulltoc_path)) - { - std::vector fulltoc_buffer = read_vector(fulltoc_path); - TOC toc_full(fulltoc_buffer, true); - if(toc_full.sessions.size() > 1) - toc = toc_full; - } - - { - auto &t = toc.sessions.back().tracks.back(); - - // fake TOC - if(t.lba_end < 0) - { - LOG("warning: fake TOC detected, using default 74min disc size"); - t.lba_end = MSF_to_LBA(MSF{74, 0, 0}); - } - - // incomplete dump (dumped with --stop-lba) - if(t.lba_end > (int32_t)sectors_count + LBA_START) - { - LOG("warning: incomplete dump detected, using available dump size"); - t.lba_end = (int32_t)sectors_count + LBA_START; - } - } - - std::fstream scm_fs(scra_path, std::fstream::in | std::fstream::binary); - if(!scm_fs.is_open()) - throw_line("unable to open file ({})", scra_path.filename().string()); - - std::fstream state_fs(state_path, std::fstream::in | std::fstream::binary); - if(!state_fs.is_open()) - throw_line("unable to open file ({})", state_path.filename().string()); - - std::string protection("N/A"); - - // SafeDisc - // first track is data - if(toc.sessions.size() == 1 && toc.sessions.front().tracks.size() >= 2) - { - auto &t = toc.sessions.front().tracks[0]; - auto &t_next = toc.sessions.front().tracks[1]; - - if(t.control & (uint8_t)ChannelQ::Control::DATA) - { - int32_t write_offset = track_offset_by_sync(t.lba_start, t_next.lba_start, state_fs, scm_fs); - - if(write_offset != std::numeric_limits::max()) - { - uint32_t file_offset = (t.lba_start - LBA_START) * CD_DATA_SIZE + write_offset * CD_SAMPLE_SIZE; - auto form1_reader = std::make_unique(scm_fs, file_offset, t_next.lba_start - t.lba_start, !scrap); - - iso9660::PrimaryVolumeDescriptor pvd; - if(iso9660::Browser::findDescriptor((iso9660::VolumeDescriptor &)pvd, form1_reader.get(), iso9660::VolumeDescriptorType::PRIMARY)) - { - auto root_directory = iso9660::Browser::rootDirectory(form1_reader.get(), pvd); - auto entry = root_directory->subEntry("00000001.TMP"); - if(entry) - { - std::vector state(CD_DATA_SIZE_SAMPLES); - - int32_t lba_start = entry->sectorsOffset() + entry->sectorsSize(); - int32_t lba_end = lba_start; - auto entries = root_directory->entries(); - for(auto &e : entries) - { - if(e->isDirectory()) - continue; - - auto entry_offset = e->sectorsOffset(); - if(entry_offset <= lba_start) - continue; - - if(lba_end == lba_start || entry_offset < lba_end) - lba_end = entry_offset; - } - - // verified: 78 - const std::set safedisc_c2_samples = { 60, 66, 72, 78 }; - - std::vector errors; - for(int32_t lba = lba_start; lba < lba_end; ++lba) - { - read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba - LBA_START, 1, -write_offset, (uint8_t)State::ERROR_SKIP); - - uint32_t error_count = 0; - for(auto const &s : state) - if(s == State::ERROR_C2) - ++error_count; - - if(safedisc_c2_samples.find(error_count) != safedisc_c2_samples.end()) - errors.push_back(lba); - } - - if(!errors.empty()) - { - protection = std::format("SafeDisc {}, C2: {}, gap range: {}-{}", entry->name(), errors.size(), lba_start, lba_end - 1); - - auto skip_ranges = string_to_ranges(options.skip); - for(auto e : errors) - skip_ranges.emplace_back(e, e + 1); - options.skip = ranges_to_string(skip_ranges); - } - } - } - } - } - } - - - // PS2 Datel - // only one data track - if(toc.sessions.size() == 1 && toc.sessions.front().tracks.size() == 2) - { - auto &t = toc.sessions.front().tracks[0]; - auto &t_next = toc.sessions.front().tracks[1]; - - if(t.control & (uint8_t)ChannelQ::Control::DATA) - { - std::vector state(CD_DATA_SIZE_SAMPLES); - - int32_t write_offset = track_offset_by_sync(t.lba_start, t_next.lba_start, state_fs, scm_fs); - if(write_offset != std::numeric_limits::max()) - { - // preliminary check - bool candidate = false; - { - constexpr int32_t lba_check = 50; - if(lba_check >= t.lba_start && lba_check < t_next.lba_start) - { - read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba_check - LBA_START, 1, -write_offset, (uint8_t)State::ERROR_SKIP); - for(auto const &s : state) - if(s == State::ERROR_C2) - { - candidate = true; - break; - } - } - } - - if(candidate) - { - constexpr int32_t first_file_offset = 23; - - std::string protected_filename; - { - uint32_t file_offset = (t.lba_start - LBA_START) * CD_DATA_SIZE + write_offset * CD_SAMPLE_SIZE; - auto form1_reader = std::make_unique(scm_fs, file_offset, t_next.lba_start - t.lba_start, !scrap); - - iso9660::PrimaryVolumeDescriptor pvd; - if(iso9660::Browser::findDescriptor((iso9660::VolumeDescriptor &)pvd, form1_reader.get(), iso9660::VolumeDescriptorType::PRIMARY)) - { - auto root_directory = iso9660::Browser::rootDirectory(form1_reader.get(), pvd); - - static const std::string datel_files[] = { "DATA.DAT", "BIG.DAT", "DUMMY.ZIP" }; - for(auto const &f : datel_files) - { - // protection file exists - auto entry = root_directory->subEntry(f); - if(!entry) - continue; - - // first file on disc and starts from LBA 23 - if(entry->sectorsOffset() == first_file_offset) - { - protected_filename = entry->name(); - break; - } - } - } - } - - if(!protected_filename.empty()) - { - std::pair range(0, 0); - for(int32_t lba = first_file_offset, lba_end = std::min(t_next.lba_start, 5000); lba < lba_end; ++lba) - { - read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba - LBA_START, 1, -write_offset, (uint8_t)State::ERROR_SKIP); - - bool error = false; - for(auto const &s : state) - if(s == State::ERROR_C2) - { - error = true; - break; - } - - if(error) - { - if(!range.first) - range.first = lba; - range.second = lba + 1; - } - else - { - if(range.first) - break; - } - } - - if(range.second > range.first) - { - protection = std::format("PS2/Datel {}, C2: {}, range: {}-{}", protected_filename, range.second - range.first, range.first, range.second - 1); - - auto skip_ranges = string_to_ranges(options.skip); - skip_ranges.push_back(range); - options.skip = ranges_to_string(skip_ranges); - } - } - } - } - } - } - - LOG("protection: {}", protection); -} - - export void redumper_split(Context &ctx, Options &options) { if(options.image_name.empty()) diff --git a/dump.ixx b/dump.ixx index 6d641b2..947fa14 100644 --- a/dump.ixx +++ b/dump.ixx @@ -12,6 +12,8 @@ module; export module dump; import cd.cd; +import cd.cdrom; +import cd.scrambler; import cd.subcode; import cd.toc; import drive; @@ -237,7 +239,7 @@ export std::ostream &redump_print_subq(std::ostream &os, int32_t lba, const Chan MSF msf = LBA_to_MSF(lba); os << std::format("MSF: {:02}:{:02}:{:02} Q-Data: {:X}{:X}{:02X}{:02X} {:02X}:{:02X}:{:02X} {:02X} {:02X}:{:02X}:{:02X} {:04X}", msf.m, msf.s, msf.f, (uint8_t)Q.control, (uint8_t)Q.adr, Q.mode1.tno, Q.mode1.point_index, Q.mode1.msf.m, Q.mode1.msf.s, Q.mode1.msf.f, Q.mode1.zero, Q.mode1.a_msf.m, Q.mode1.a_msf.s, Q.mode1.a_msf.f, endian_swap(Q.crc)) << std::endl; - + return os; } @@ -283,4 +285,51 @@ export bool profile_is_hddvd(GET_CONFIGURATION_FeatureCode_ProfileList profile) || profile == GET_CONFIGURATION_FeatureCode_ProfileList::HDDVD_RW_DL; } + +export int32_t track_offset_by_sync(int32_t lba_start, int32_t lba_end, std::fstream &state_fs, std::fstream &scm_fs) +{ + int32_t write_offset = std::numeric_limits::max(); + + constexpr uint32_t sectors_to_check = 2; + + std::vector data(sectors_to_check * CD_DATA_SIZE); + std::vector state(sectors_to_check * CD_DATA_SIZE_SAMPLES); + + uint32_t groups_count = (lba_end - lba_start) / sectors_to_check; + for(uint32_t i = 0; i < groups_count; ++i) + { + int32_t lba = lba_start + i * sectors_to_check; + read_entry(scm_fs, data.data(), CD_DATA_SIZE, lba - LBA_START, sectors_to_check, 0, 0); + read_entry(state_fs, (uint8_t *)state.data(), CD_DATA_SIZE_SAMPLES, lba - LBA_START, sectors_to_check, 0, (uint8_t)State::ERROR_SKIP); + + for(auto const &s : state) + if(s == State::ERROR_SKIP || s == State::ERROR_C2) + continue; + + auto it = std::search(data.begin(), data.end(), std::begin(CD_DATA_SYNC), std::end(CD_DATA_SYNC)); + if(it != data.end()) + { + auto sector_offset = (uint32_t)(it - data.begin()); + + // enough data for one sector + if(data.size() - sector_offset >= CD_DATA_SIZE) + { + Sector §or = *(Sector *)&data[sector_offset]; + Scrambler scrambler; + scrambler.descramble((uint8_t *)§or, nullptr); + + if(BCDMSF_valid(sector.header.address)) + { + int32_t sector_lba = BCDMSF_to_LBA(sector.header.address); + write_offset = ((int32_t)sector_offset - (sector_lba - lba) * (int32_t)CD_DATA_SIZE) / (int32_t)CD_SAMPLE_SIZE; + + break; + } + } + } + } + + return write_offset; +} + } diff --git a/redumper.ixx b/redumper.ixx index 31d0c0f..8ffd389 100644 --- a/redumper.ixx +++ b/redumper.ixx @@ -15,6 +15,7 @@ export module redumper; import cd.cd; import cd.dump; +import cd.protection; import cd.scrambler; import cd.split; import cd.subcode;