diff --git a/cd/cd_dump.ixx b/cd/cd_dump.ixx index 755f873a..745e5376 100644 --- a/cd/cd_dump.ixx +++ b/cd/cd_dump.ixx @@ -20,6 +20,7 @@ import cd.subcode; import cd.toc; import common; import drive; +import interval_set; import options; import range; import scsi.cmd; @@ -338,6 +339,9 @@ export bool redumper_dump_cd(Context &ctx, const Options &options, bool dump) int32_t subcode_shift = 0; uint32_t subcode_byte_desync_counter = 0; + IntervalSet scsi_error_intervals; + IntervalSet c2_error_intervals; + SignalINT signal; int32_t lba_overread = lba_end; @@ -432,7 +436,10 @@ export bool redumper_dump_cd(Context &ctx, const Options &options, bool dump) if(session_gap_range == nullptr && lba < lba_end) { if(dump) + { errors.scsi += CD_DATA_SIZE_SAMPLES; + scsi_error_intervals.add(lba); + } if(options.verbose) LOGC_R("[LBA: {:6}] SCSI error ({})", lba, SPTD::StatusMessage(status)); @@ -478,7 +485,10 @@ export bool redumper_dump_cd(Context &ctx, const Options &options, bool dump) if(c2_samples) { if(dump) + { errors.c2 += c2_samples; + c2_error_intervals.add(lba); + } if(options.verbose) { @@ -575,6 +585,16 @@ export bool redumper_dump_cd(Context &ctx, const Options &options, bool dump) LOG(" SCSI: {} samples", errors.scsi); LOG(" C2: {} samples", errors.c2); LOG(" Q: {}", errors.q); + + if(options.verbose && (!scsi_error_intervals.empty() || !c2_error_intervals.empty())) + { + LOG(""); + LOG("LBA error ranges: "); + if(!scsi_error_intervals.empty()) + LOG(" SCSI: {}", scsi_error_intervals.to_string()); + if(!c2_error_intervals.empty()) + LOG(" C2: {}", c2_error_intervals.to_string()); + } } else { diff --git a/debug.ixx b/debug.ixx index 89ef7f01..a448e420 100644 --- a/debug.ixx +++ b/debug.ixx @@ -13,6 +13,7 @@ module; export module debug; +import bd; import cd.cd; import cd.common; import cd.subcode; @@ -20,6 +21,7 @@ import cd.toc; import common; import drive; import drive.mediatek; +import dvd; import dvd.dump; import dvd.xbox; import options; @@ -414,6 +416,73 @@ export int redumper_debug(Context &ctx, Options &options) return exit_code; } +export int redumper_state(Context &ctx, Options &options) +{ + int exit_code = 0; + + std::string image_prefix = (std::filesystem::path(options.image_path) / options.image_name).string(); + + std::filesystem::path state_path(image_prefix + ".state"); + if(!std::filesystem::exists(state_path)) + throw_line("state file not found ({})", state_path.filename().string()); + + bool cd = std::filesystem::exists(image_prefix + ".scram"); + if(!cd && options.disc_type && *options.disc_type == "CD") + cd = true; + uint64_t entries_count = std::filesystem::file_size(state_path) / sizeof(State); + + 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()); + + int32_t offset = 0; + if(cd) + offset = LBA_START * CD_DATA_SIZE_SAMPLES; + else if(std::filesystem::exists(image_prefix + ".sdram")) + offset = dvd::LBA_START; + else if(std::filesystem::exists(image_prefix + ".sbram")) + offset = bd::LBA_START; + + static const char *STATE_NAME[] = { "Unread", "Read Error", "Success (No confirmed C2 data)", "Success (No SCSI state)", "Success" }; + + LOG("state file (unit: {}): ", cd ? "samples" : "sectors"); + + std::vector buffer(CHUNK_1KB + 1); + State current = (State)0xFF; + int64_t range_start = offset; + + for(uint64_t i = 0; i < entries_count; i += CHUNK_1KB) + { + uint32_t count = std::min((uint64_t)CHUNK_1KB, entries_count - i); + state_fs.read((char *)buffer.data(), count * sizeof(State)); + + // create state change at end of file to trigger final print + if(i + count >= entries_count) + buffer[count++] = (State) ~(uint8_t)current; + + for(uint32_t j = 0; j < count; ++j) + { + if(buffer[j] == current) + continue; + + int64_t range_end = offset + (int64_t)(i + j) - 1; + if(range_start <= range_end) + { + const char *state_name = (uint8_t)current < std::size(STATE_NAME) ? STATE_NAME[(uint8_t)current] : "Unknown"; + if(cd) + LOG(" {}..{} (LBA {}..{}): {}", range_start, range_end, sample_to_lba(range_start, 0), sample_to_lba(range_end, 0), state_name); + else + LOG(" {}..{}: {}", range_start, range_end, state_name); + } + + current = buffer[j]; + range_start = offset + (int64_t)(i + j); + } + } + + return exit_code; +} + export int redumper_flip(Context &ctx, Options &options) { int exit_code = 0; diff --git a/dvd/dvd_dump.ixx b/dvd/dvd_dump.ixx index 9e34ac36..0c1dac21 100644 --- a/dvd/dvd_dump.ixx +++ b/dvd/dvd_dump.ixx @@ -1378,6 +1378,12 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, bool dump) LOG("media errors: "); LOG(" SCSI: {}", errors.scsi); LOG(" EDC: {}", errors.edc); + + if(options.verbose && !error_intervals.empty()) + { + LOG(""); + LOG("LBA error ranges: {}", error_intervals.to_string()); + } } else { diff --git a/interval_set.ixx b/interval_set.ixx index 950cd5a9..132f10de 100644 --- a/interval_set.ixx +++ b/interval_set.ixx @@ -1,7 +1,9 @@ module; #include #include +#include #include +#include #include #include @@ -213,6 +215,19 @@ public: return std::nullopt; } + + std::string to_string() const + { + std::string str; + for(auto &r : _ranges) + { + if(!str.empty()) + str += ", "; + str += (r.second - r.first == 1) ? std::format("{}", r.first) : std::format("{}-{}", r.first, r.second - 1); + } + return str; + } + private: std::vector _ranges; }; diff --git a/redumper.ixx b/redumper.ixx index 101c3325..d8ec02d8 100644 --- a/redumper.ixx +++ b/redumper.ixx @@ -144,6 +144,7 @@ const std::map COMMANDS{ { "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 } }, + { "debug::state", { false, false, false, true, false, redumper_state } }, { "drive::test", { true, true, true, false, false, redumper_drive_test } }, };