mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Merge pull request #389 from RetroPortingToolKit/fix/cue-pregap
Honour cue PREGAP/POSTGAP in the BIN/CUE mount (fixes #388)
This commit is contained in:
@@ -393,6 +393,15 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(iso_reader_cdda_test PRIVATE chdr-static)
|
||||
add_test(NAME iso_reader_cdda_test COMMAND iso_reader_cdda_test)
|
||||
|
||||
# Cue PREGAP/POSTGAP (gaps the dump does not store) shift the disc layout.
|
||||
add_executable(iso_reader_pregap_test
|
||||
tests/test_iso_reader_pregap.cpp
|
||||
src/iso_reader.cpp
|
||||
src/cue_sheet.cpp)
|
||||
target_include_directories(iso_reader_pregap_test PRIVATE include ${PSXRECOMP_ROOT}/recompiler/include)
|
||||
target_link_libraries(iso_reader_pregap_test PRIVATE chdr-static)
|
||||
add_test(NAME iso_reader_pregap_test COMMAND iso_reader_pregap_test)
|
||||
|
||||
add_executable(iso_reader_sbi_test
|
||||
tests/test_iso_reader_sbi.cpp
|
||||
src/iso_reader.cpp
|
||||
|
||||
@@ -309,6 +309,10 @@ typedef struct CDROMCommandHistoryEntry {
|
||||
uint8_t pending_pending;
|
||||
uint8_t queued_cmd;
|
||||
uint8_t queued_pending;
|
||||
/* Immediate response FIFO as the guest will read it (e.g. the GetTD MSF,
|
||||
* the GetTN track range), so a TOC can be checked from the ring. */
|
||||
uint8_t response_count;
|
||||
uint8_t response[16];
|
||||
} CDROMCommandHistoryEntry;
|
||||
|
||||
typedef struct CDROMSectorHistoryEntry {
|
||||
|
||||
@@ -40,6 +40,11 @@ struct CueTrackRef {
|
||||
uint32_t index01 = 0; // track start, file-relative LBA
|
||||
uint32_t index00 = 0; // pregap start, file-relative LBA
|
||||
bool has_index00 = false;
|
||||
// PREGAP / POSTGAP lengths in sectors. Unlike INDEX 00, these gaps are
|
||||
// NOT stored in the file: the mount must insert them as silent sectors,
|
||||
// shifting every later disc LBA (and the TOC) accordingly.
|
||||
uint32_t pregap = 0;
|
||||
uint32_t postgap = 0;
|
||||
};
|
||||
|
||||
struct CueSheet {
|
||||
|
||||
@@ -55,7 +55,10 @@ struct CDTrack {
|
||||
// Single-FILE cues: equals the .bin-relative INDEX time.
|
||||
// Multi-FILE cues (redump "(Track N).bin" dumps): the
|
||||
// owning file's first disc sector plus its INDEX time.
|
||||
uint32_t pregap_lba; // disc-relative INDEX 00, or INDEX 01 when absent
|
||||
// Either way, plus every cue PREGAP/POSTGAP before it
|
||||
// (gaps the dump does not store).
|
||||
uint32_t pregap_lba; // disc-relative start of the track's pregap: its cue
|
||||
// PREGAP if any, else INDEX 00, else INDEX 01
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -67,9 +70,23 @@ struct CDTrack {
|
||||
struct BinSegment {
|
||||
std::string path; // resolved on-disk path
|
||||
std::ifstream file; // opened for the reader's lifetime
|
||||
uint32_t start_lba; // first disc-relative sector of this file
|
||||
uint32_t start_lba; // disc sector of this file's first stored sector
|
||||
uint32_t sector_count; // sectors stored in this file
|
||||
bool raw; // 2352-byte raw sectors (BIN) vs 2048 (ISO)
|
||||
};
|
||||
|
||||
/**
|
||||
* A contiguous run of disc sectors. A stored span maps onto sectors of one
|
||||
* BinSegment; a virtual span is a cue PREGAP/POSTGAP that the dump does not
|
||||
* store and that reads back as zero (silence), as the CHD path already does.
|
||||
* Without PREGAP/POSTGAP lines each file is exactly one stored span, so the
|
||||
* disc layout is the plain concatenation of the files.
|
||||
*/
|
||||
struct BinSpan {
|
||||
uint32_t disc_start; // first disc-relative sector
|
||||
uint32_t sector_count;
|
||||
int segment; // index into segments_, or -1 for a virtual gap
|
||||
uint32_t file_sector; // first sector within the segment's file
|
||||
};
|
||||
|
||||
class ISOReader {
|
||||
@@ -220,10 +237,10 @@ private:
|
||||
std::vector<ISOFileEntry> ListFilesByLBA(uint32_t lba, uint32_t dir_size);
|
||||
|
||||
/**
|
||||
* Helper: find the BinSegment containing a disc-relative LBA.
|
||||
* @return segment pointer, or nullptr when lba is past the disc end
|
||||
* Helper: find the span containing a disc-relative LBA.
|
||||
* @return span pointer, or nullptr when lba is past the disc end
|
||||
*/
|
||||
BinSegment* SegmentForLBA(uint32_t lba);
|
||||
const BinSpan* SpanForLBA(uint32_t lba) const;
|
||||
|
||||
bool LoadSBICompanion(const std::string& image_path);
|
||||
|
||||
@@ -233,6 +250,8 @@ private:
|
||||
RootDirectoryInfo root_dir_;
|
||||
std::vector<CDTrack> tracks_; // from the .cue TOC; >=1 entry after Open()
|
||||
std::vector<BinSegment> segments_; // cue FILE entries in disc order; >=1 after Open()
|
||||
std::vector<BinSpan> spans_; // disc layout over segments_ + cue gaps
|
||||
uint32_t disc_sector_count_ = 0; // BIN/CUE: end of the last span
|
||||
std::unique_ptr<CHDState> chd_; // present only for a directly mounted CHD
|
||||
std::unordered_map<uint32_t, std::array<uint8_t, 12>> subq_replacements_;
|
||||
};
|
||||
|
||||
@@ -835,6 +835,13 @@ static void record_command_history(uint8_t kind, uint8_t cmd,
|
||||
e->pending_pending = (uint8_t)(pending.pending ? 1 : 0);
|
||||
e->queued_cmd = queued_cmd.cmd;
|
||||
e->queued_pending = (uint8_t)(queued_cmd.pending ? 1 : 0);
|
||||
{
|
||||
int rn = response_count;
|
||||
if (rn < 0) rn = 0;
|
||||
if (rn > (int)sizeof(e->response)) rn = (int)sizeof(e->response);
|
||||
e->response_count = (uint8_t)rn;
|
||||
memcpy(e->response, response_fifo, (size_t)rn);
|
||||
}
|
||||
}
|
||||
|
||||
static int xa_is_audio_realtime(const CDROMSectorDelivery *d) {
|
||||
|
||||
@@ -127,6 +127,10 @@ CueSheet parse_cue_sheet(const fs::path& cue_path) {
|
||||
bool cur_track_audio = false;
|
||||
uint32_t cur_index00 = 0;
|
||||
bool cur_has_index00 = false;
|
||||
uint32_t cur_pregap = 0;
|
||||
// A TRACK whose INDEX 01 has been recorded, so a trailing POSTGAP line
|
||||
// (which follows INDEX 01) still attaches to it.
|
||||
int last_pushed_track = -1;
|
||||
|
||||
while (std::getline(cue, line)) {
|
||||
// Strip a trailing CR so cues with CRLF endings parse on POSIX.
|
||||
@@ -150,6 +154,8 @@ CueSheet parse_cue_sheet(const fs::path& cue_path) {
|
||||
cur_track_num = -1;
|
||||
cur_has_index00 = false;
|
||||
cur_index00 = 0;
|
||||
cur_pregap = 0;
|
||||
last_pushed_track = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -160,10 +166,23 @@ CueSheet parse_cue_sheet(const fs::path& cue_path) {
|
||||
cur_track_audio = (std::string(type_buf).find("AUDIO") != std::string::npos);
|
||||
cur_index00 = 0;
|
||||
cur_has_index00 = false;
|
||||
cur_pregap = 0;
|
||||
last_pushed_track = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx = 0, mm = 0, ss = 0, ff = 0;
|
||||
if (std::sscanf(upper.c_str(), " PREGAP %d:%d:%d", &mm, &ss, &ff) == 3) {
|
||||
if (cur_track_num >= 1)
|
||||
cur_pregap = (uint32_t)(((mm * 60 + ss) * 75) + ff);
|
||||
continue;
|
||||
}
|
||||
if (std::sscanf(upper.c_str(), " POSTGAP %d:%d:%d", &mm, &ss, &ff) == 3) {
|
||||
if (last_pushed_track >= 0 && !sheet.tracks.empty() &&
|
||||
sheet.tracks.back().number == last_pushed_track)
|
||||
sheet.tracks.back().postgap = (uint32_t)(((mm * 60 + ss) * 75) + ff);
|
||||
continue;
|
||||
}
|
||||
if (std::sscanf(upper.c_str(), " INDEX %d %d:%d:%d", &idx, &mm, &ss, &ff) == 4 &&
|
||||
cur_track_num >= 1 && !sheet.files.empty()) {
|
||||
const uint32_t index_lba = (uint32_t)(((mm * 60 + ss) * 75) + ff);
|
||||
@@ -180,7 +199,9 @@ CueSheet parse_cue_sheet(const fs::path& cue_path) {
|
||||
t.index01 = index_lba;
|
||||
t.index00 = cur_index00;
|
||||
t.has_index00 = cur_has_index00;
|
||||
t.pregap = cur_pregap;
|
||||
sheet.tracks.push_back(t);
|
||||
last_pushed_track = cur_track_num;
|
||||
cur_track_num = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5768,7 +5768,7 @@ static void handle_cdrom_command_history(int id, const char *json)
|
||||
uint64_t oldest = (total > CDROM_COMMAND_HISTORY_CAP)
|
||||
? total - CDROM_COMMAND_HISTORY_CAP : 0;
|
||||
|
||||
size_t bufsz = 256u + (size_t)count * 640u;
|
||||
size_t bufsz = 256u + (size_t)count * 800u;
|
||||
char *buf = (char *)malloc(bufsz);
|
||||
if (!buf) { send_err(id, "oom"); return; }
|
||||
|
||||
@@ -5781,7 +5781,7 @@ static void handle_cdrom_command_history(int id, const char *json)
|
||||
(unsigned long long)oldest);
|
||||
|
||||
uint64_t seq = total;
|
||||
while (seq > oldest && emitted < count && pos < bufsz - 640) {
|
||||
while (seq > oldest && emitted < count && pos < bufsz - 800) {
|
||||
seq--;
|
||||
const CDROMCommandHistoryEntry *e =
|
||||
&entries[seq % CDROM_COMMAND_HISTORY_CAP];
|
||||
@@ -5809,13 +5809,18 @@ static void handle_cdrom_command_history(int id, const char *json)
|
||||
"\"reading\":%u,\"pending_cmd\":\"0x%02X\","
|
||||
"\"pending\":%u,\"queued_cmd\":\"0x%02X\","
|
||||
"\"queued\":%u,\"func\":\"0x%08X\",\"pc\":\"0x%08X\","
|
||||
"\"i_stat\":\"0x%08X\"}",
|
||||
"\"i_stat\":\"0x%08X\",\"response\":[",
|
||||
e->stat, e->request, e->irq_enable, e->irq_flag,
|
||||
e->mode, e->seek_min, e->seek_sec, e->seek_sect,
|
||||
e->read_min, e->read_sec, e->read_sect, e->read_cmd,
|
||||
e->reading, e->pending_cmd, e->pending_pending,
|
||||
e->queued_cmd, e->queued_pending, e->func, e->pc,
|
||||
e->i_stat);
|
||||
for (uint8_t i = 0; i < e->response_count && i < 16 && pos < bufsz - 16; i++) {
|
||||
pos += snprintf(buf + pos, bufsz - pos,
|
||||
"%s\"0x%02X\"", i ? "," : "", e->response[i]);
|
||||
}
|
||||
pos += snprintf(buf + pos, bufsz - pos, "]}");
|
||||
emitted++;
|
||||
}
|
||||
|
||||
|
||||
+92
-27
@@ -310,7 +310,6 @@ bool ISOReader::Open(const std::string& filename) {
|
||||
// Build the segment table: open every file and lay it out at the next
|
||||
// disc-relative sector. Every file must open — a multi-file dump with a
|
||||
// missing track file would otherwise silently read the wrong sectors.
|
||||
uint32_t next_lba = 0;
|
||||
for (const std::string& bin_name : bin_files) {
|
||||
BinSegment seg;
|
||||
seg.path = bin_name;
|
||||
@@ -330,21 +329,71 @@ bool ISOReader::Open(const std::string& filename) {
|
||||
seg.raw = (size % RAW_SECTOR_SIZE) == 0;
|
||||
seg.sector_count = static_cast<uint32_t>(size / (seg.raw ? RAW_SECTOR_SIZE
|
||||
: SECTOR_SIZE));
|
||||
seg.start_lba = next_lba;
|
||||
next_lba += seg.sector_count;
|
||||
seg.start_lba = 0; // assigned by the layout pass below
|
||||
segments_.push_back(std::move(seg));
|
||||
}
|
||||
|
||||
// Convert the pending cue tracks to disc-relative LBAs.
|
||||
for (const PendingTrack& p : pending_tracks) {
|
||||
CDTrack t;
|
||||
t.number = p.number;
|
||||
t.is_audio = p.is_audio;
|
||||
t.start_lba = segments_[p.file_index].start_lba + p.index01;
|
||||
t.pregap_lba = segments_[p.file_index].start_lba +
|
||||
(p.has_index00 ? p.index00 : p.index01);
|
||||
tracks_.push_back(t);
|
||||
// Lay the files out on the disc and convert the pending cue tracks to
|
||||
// disc-relative LBAs. INDEX times are file-relative; a cue PREGAP (before
|
||||
// a track's INDEX 00/01) or POSTGAP (after its data) is a run of sectors
|
||||
// the dump does not store, so it becomes a virtual span and moves every
|
||||
// later disc LBA. Real discs carry those sectors, and games locate tracks
|
||||
// through the TOC (GetTD), so dropping them shifts every later track
|
||||
// earlier than the game expects. Without gap lines each file is one stored
|
||||
// span and the layout is the plain concatenation of the files.
|
||||
uint32_t disc = 0;
|
||||
auto push_stored = [&](size_t seg_index, uint32_t from, uint32_t to) {
|
||||
if (to <= from) return;
|
||||
spans_.push_back({disc, to - from, (int)seg_index, from});
|
||||
disc += to - from;
|
||||
};
|
||||
auto push_virtual = [&](uint32_t count) {
|
||||
if (count == 0) return;
|
||||
spans_.push_back({disc, count, -1, 0});
|
||||
disc += count;
|
||||
};
|
||||
for (size_t si = 0; si < segments_.size(); ++si) {
|
||||
BinSegment& seg = segments_[si];
|
||||
const uint32_t n = seg.sector_count;
|
||||
seg.start_lba = disc;
|
||||
uint32_t cursor = 0;
|
||||
for (size_t pi = 0; pi < pending_tracks.size(); ++pi) {
|
||||
const PendingTrack& p = pending_tracks[pi];
|
||||
if (p.file_index != si) continue;
|
||||
// The track's region in the file starts at INDEX 00 when it has
|
||||
// one; its PREGAP (if any) sits just before that point.
|
||||
const uint32_t region =
|
||||
std::min(std::max(p.has_index00 ? p.index00 : p.index01, cursor), n);
|
||||
push_stored(si, cursor, region);
|
||||
cursor = region;
|
||||
|
||||
CDTrack t;
|
||||
t.number = p.number;
|
||||
t.is_audio = p.is_audio;
|
||||
t.pregap_lba = disc;
|
||||
push_virtual(p.pregap);
|
||||
t.start_lba = disc + (p.index01 > region ? p.index01 - region : 0);
|
||||
tracks_.push_back(t);
|
||||
|
||||
if (p.postgap) {
|
||||
// POSTGAP follows the track's stored data: the next track of
|
||||
// this file starts its region there, else the file ends.
|
||||
uint32_t end = n;
|
||||
for (size_t qi = pi + 1; qi < pending_tracks.size(); ++qi) {
|
||||
const PendingTrack& q = pending_tracks[qi];
|
||||
if (q.file_index != si) continue;
|
||||
end = std::min(std::max(q.has_index00 ? q.index00 : q.index01,
|
||||
cursor), n);
|
||||
break;
|
||||
}
|
||||
push_stored(si, cursor, end);
|
||||
cursor = end;
|
||||
push_virtual(p.postgap);
|
||||
}
|
||||
}
|
||||
push_stored(si, cursor, n);
|
||||
}
|
||||
disc_sector_count_ = disc;
|
||||
|
||||
// Synthesize a single data track for a bare .bin/.iso or a .cue with no
|
||||
// parseable TRACK entries, so TrackCount() is always >= 1.
|
||||
@@ -384,6 +433,8 @@ void ISOReader::Close() {
|
||||
}
|
||||
}
|
||||
segments_.clear();
|
||||
spans_.clear();
|
||||
disc_sector_count_ = 0;
|
||||
tracks_.clear();
|
||||
subq_replacements_.clear();
|
||||
bin_path_.clear();
|
||||
@@ -466,11 +517,12 @@ bool ISOReader::ReadSubChannelQ(uint32_t lba, uint8_t* buffer, bool* valid) cons
|
||||
return true;
|
||||
}
|
||||
|
||||
BinSegment* ISOReader::SegmentForLBA(uint32_t lba) {
|
||||
// Linear scan: images carry a handful of segments (one per track file).
|
||||
for (BinSegment& seg : segments_) {
|
||||
if (lba >= seg.start_lba && lba - seg.start_lba < seg.sector_count) {
|
||||
return &seg;
|
||||
const BinSpan* ISOReader::SpanForLBA(uint32_t lba) const {
|
||||
// Linear scan: images carry a handful of spans (one per track file, plus
|
||||
// one per cue gap).
|
||||
for (const BinSpan& span : spans_) {
|
||||
if (lba >= span.disc_start && lba - span.disc_start < span.sector_count) {
|
||||
return &span;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -492,11 +544,17 @@ bool ISOReader::ReadSector(uint32_t lba, uint8_t* buffer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BinSegment* seg = SegmentForLBA(lba);
|
||||
if (!seg) {
|
||||
const BinSpan* span = SpanForLBA(lba);
|
||||
if (!span) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t local_lba = lba - seg->start_lba;
|
||||
if (span->segment < 0) {
|
||||
// Cue PREGAP/POSTGAP: not stored in the dump; reads back as zero.
|
||||
std::memset(buffer, 0, SECTOR_SIZE);
|
||||
return true;
|
||||
}
|
||||
BinSegment* seg = &segments_[(size_t)span->segment];
|
||||
const uint32_t local_lba = span->file_sector + (lba - span->disc_start);
|
||||
|
||||
// Clear any error flags from a previous failed read
|
||||
seg->file.clear();
|
||||
@@ -542,11 +600,21 @@ bool ISOReader::ReadRawSector(uint32_t lba, uint8_t* buffer) {
|
||||
|
||||
if (chd_) return read_chd_raw(*chd_, lba, buffer, nullptr);
|
||||
|
||||
BinSegment* seg = SegmentForLBA(lba);
|
||||
if (!seg || !seg->raw) {
|
||||
const BinSpan* span = SpanForLBA(lba);
|
||||
if (!span) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t local_lba = lba - seg->start_lba;
|
||||
if (span->segment < 0) {
|
||||
// Cue PREGAP/POSTGAP: not stored in the dump; digital silence, the
|
||||
// same as the CHD path's virtual pregap.
|
||||
std::memset(buffer, 0, RAW_SECTOR_SIZE);
|
||||
return true;
|
||||
}
|
||||
BinSegment* seg = &segments_[(size_t)span->segment];
|
||||
if (!seg->raw) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t local_lba = span->file_sector + (lba - span->disc_start);
|
||||
|
||||
seg->file.clear();
|
||||
std::streampos offset = static_cast<std::streampos>(local_lba) * RAW_SECTOR_SIZE;
|
||||
@@ -580,10 +648,7 @@ uint32_t ISOReader::GetSectorCount() {
|
||||
return 0;
|
||||
}
|
||||
if (chd_) return chd_->disc_sector_count;
|
||||
if (segments_.empty()) return 0;
|
||||
|
||||
const BinSegment& last = segments_.back();
|
||||
return last.start_lba + last.sector_count;
|
||||
return disc_sector_count_;
|
||||
}
|
||||
|
||||
int ISOReader::TrackCount() const {
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
// Cue PREGAP / POSTGAP are gaps the dump does NOT store. The mount must insert
|
||||
// them as silent sectors so the disc layout (and the TOC games read through
|
||||
// GetTD) matches the physical disc. The oracle here is layout equivalence: a
|
||||
// cue that stores its pregap in the file (INDEX 00) and one that declares it
|
||||
// with PREGAP describe the same disc, so every track LBA, the sector count and
|
||||
// every sector's contents must agree.
|
||||
|
||||
#include "iso_reader.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static int g_failures = 0;
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Sector i of a file is filled with byte `markers[i]` (0 = a silent sector).
|
||||
static void write_bin(const fs::path& path, const std::vector<uint8_t>& markers) {
|
||||
std::ofstream out(path, std::ios::binary);
|
||||
for (uint8_t m : markers) {
|
||||
std::vector<uint8_t> sector(2352, m);
|
||||
out.write(reinterpret_cast<const char*>(sector.data()), sector.size());
|
||||
}
|
||||
}
|
||||
|
||||
static void write_text(const fs::path& path, const char* text) {
|
||||
std::ofstream out(path);
|
||||
out << text;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> run(uint8_t first, int count) {
|
||||
std::vector<uint8_t> v;
|
||||
for (int i = 0; i < count; ++i) v.push_back((uint8_t)(first + i));
|
||||
return v;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> cat(std::vector<uint8_t> a, const std::vector<uint8_t>& b) {
|
||||
a.insert(a.end(), b.begin(), b.end());
|
||||
return a;
|
||||
}
|
||||
|
||||
// Every observable of the two mounts must agree.
|
||||
static void check_same_disc(const fs::path& a_cue, const fs::path& b_cue) {
|
||||
PS1::ISOReader a, b;
|
||||
CHECK(a.Open(a_cue.string()));
|
||||
CHECK(b.Open(b_cue.string()));
|
||||
CHECK(a.TrackCount() == b.TrackCount());
|
||||
CHECK(a.GetSectorCount() == b.GetSectorCount());
|
||||
for (int t = 1; t <= a.TrackCount(); ++t) {
|
||||
CHECK(a.TrackStartLBA(t) == b.TrackStartLBA(t));
|
||||
CHECK(a.TrackPregapLBA(t) == b.TrackPregapLBA(t));
|
||||
CHECK(a.TrackIsAudio(t) == b.TrackIsAudio(t));
|
||||
}
|
||||
uint8_t ra[2352], rb[2352];
|
||||
for (uint32_t lba = 0; lba < a.GetSectorCount(); ++lba) {
|
||||
const bool oa = a.ReadRawSector(lba, ra);
|
||||
const bool ob = b.ReadRawSector(lba, rb);
|
||||
CHECK(oa && ob);
|
||||
if (oa && ob && ra[0] != rb[0]) {
|
||||
std::printf(" sector %u differs: %02x vs %02x\n", lba, ra[0], rb[0]);
|
||||
++g_failures;
|
||||
}
|
||||
}
|
||||
CHECK(!a.ReadRawSector(a.GetSectorCount(), ra));
|
||||
}
|
||||
|
||||
int main() {
|
||||
const fs::path dir = fs::temp_directory_path() / "psxrecomp-cue-pregap-test";
|
||||
fs::remove_all(dir);
|
||||
fs::create_directories(dir);
|
||||
|
||||
// Single-file dump shaped like Xi (issue #388): data track, then the first
|
||||
// audio track with a 3-sector PREGAP that is not in the file, then an audio
|
||||
// track whose pregap IS in the file (INDEX 00).
|
||||
const std::vector<uint8_t> data = run(0x10, 10); // file sectors 0..9
|
||||
const std::vector<uint8_t> aud2 = run(0x40, 3); // file sectors 10..12
|
||||
const std::vector<uint8_t> aud3 = run(0x60, 3); // file sectors 13..15
|
||||
write_bin(dir / "gap.bin", cat(cat(data, aud2), aud3));
|
||||
write_text(dir / "gap.cue",
|
||||
"FILE \"gap.bin\" BINARY\n"
|
||||
" TRACK 01 MODE2/2352\n"
|
||||
" INDEX 01 00:00:00\n"
|
||||
" TRACK 02 AUDIO\n"
|
||||
" PREGAP 00:00:03\n"
|
||||
" INDEX 01 00:00:10\n"
|
||||
" TRACK 03 AUDIO\n"
|
||||
" INDEX 00 00:00:13\n"
|
||||
" INDEX 01 00:00:14\n");
|
||||
|
||||
{
|
||||
PS1::ISOReader r;
|
||||
CHECK(r.Open((dir / "gap.cue").string()));
|
||||
CHECK(r.TrackCount() == 3);
|
||||
CHECK(r.TrackStartLBA(1) == 0);
|
||||
CHECK(r.TrackPregapLBA(2) == 10); // the virtual gap starts here
|
||||
CHECK(r.TrackStartLBA(2) == 13); // INDEX 01 moved by the gap
|
||||
CHECK(r.TrackPregapLBA(3) == 16);
|
||||
CHECK(r.TrackStartLBA(3) == 17);
|
||||
CHECK(r.GetSectorCount() == 19);
|
||||
uint8_t raw[2352];
|
||||
CHECK(r.ReadRawSector(9, raw) && raw[0] == 0x19);
|
||||
CHECK(r.ReadRawSector(10, raw) && raw[0] == 0x00 && raw[2351] == 0x00);
|
||||
CHECK(r.ReadRawSector(12, raw) && raw[0] == 0x00);
|
||||
CHECK(r.ReadRawSector(13, raw) && raw[0] == 0x40);
|
||||
CHECK(r.ReadRawSector(18, raw) && raw[0] == 0x62);
|
||||
uint8_t user[2048];
|
||||
CHECK(r.ReadSector(11, user) && user[0] == 0x00);
|
||||
}
|
||||
|
||||
// The same physical disc, with the pregap stored in the file as INDEX 00.
|
||||
write_bin(dir / "stored.bin",
|
||||
cat(cat(cat(data, std::vector<uint8_t>(3, 0)), aud2), aud3));
|
||||
write_text(dir / "stored.cue",
|
||||
"FILE \"stored.bin\" BINARY\n"
|
||||
" TRACK 01 MODE2/2352\n"
|
||||
" INDEX 01 00:00:00\n"
|
||||
" TRACK 02 AUDIO\n"
|
||||
" INDEX 00 00:00:10\n"
|
||||
" INDEX 01 00:00:13\n"
|
||||
" TRACK 03 AUDIO\n"
|
||||
" INDEX 00 00:00:16\n"
|
||||
" INDEX 01 00:00:17\n");
|
||||
check_same_disc(dir / "gap.cue", dir / "stored.cue");
|
||||
|
||||
// Multi-file (redump layout) with every audio pregap stripped from its
|
||||
// file and declared as PREGAP, versus the redump original, plus a POSTGAP
|
||||
// on the data track.
|
||||
write_bin(dir / "t1.bin", run(0x10, 4));
|
||||
write_bin(dir / "t2_stored.bin", cat(std::vector<uint8_t>(2, 0), run(0x40, 3)));
|
||||
write_bin(dir / "t3_stored.bin", cat(std::vector<uint8_t>(2, 0), run(0x60, 2)));
|
||||
write_bin(dir / "t2_gap.bin", run(0x40, 3));
|
||||
write_bin(dir / "t3_gap.bin", run(0x60, 2));
|
||||
write_text(dir / "multi_stored.cue",
|
||||
"FILE \"t1.bin\" BINARY\n"
|
||||
" TRACK 01 MODE2/2352\n"
|
||||
" INDEX 01 00:00:00\n"
|
||||
"FILE \"t2_stored.bin\" BINARY\n"
|
||||
" TRACK 02 AUDIO\n"
|
||||
" INDEX 00 00:00:00\n"
|
||||
" INDEX 01 00:00:02\n"
|
||||
"FILE \"t3_stored.bin\" BINARY\n"
|
||||
" TRACK 03 AUDIO\n"
|
||||
" INDEX 00 00:00:00\n"
|
||||
" INDEX 01 00:00:02\n");
|
||||
write_text(dir / "multi_gap.cue",
|
||||
"FILE \"t1.bin\" BINARY\n"
|
||||
" TRACK 01 MODE2/2352\n"
|
||||
" INDEX 01 00:00:00\n"
|
||||
"FILE \"t2_gap.bin\" BINARY\n"
|
||||
" TRACK 02 AUDIO\n"
|
||||
" PREGAP 00:00:02\n"
|
||||
" INDEX 01 00:00:00\n"
|
||||
"FILE \"t3_gap.bin\" BINARY\n"
|
||||
" TRACK 03 AUDIO\n"
|
||||
" PREGAP 00:00:02\n"
|
||||
" INDEX 01 00:00:00\n");
|
||||
check_same_disc(dir / "multi_gap.cue", dir / "multi_stored.cue");
|
||||
|
||||
// POSTGAP follows the track's stored data and precedes the next track.
|
||||
write_text(dir / "postgap.cue",
|
||||
"FILE \"t1.bin\" BINARY\n"
|
||||
" TRACK 01 MODE2/2352\n"
|
||||
" INDEX 01 00:00:00\n"
|
||||
" POSTGAP 00:00:02\n"
|
||||
"FILE \"t2_gap.bin\" BINARY\n"
|
||||
" TRACK 02 AUDIO\n"
|
||||
" INDEX 01 00:00:00\n");
|
||||
{
|
||||
PS1::ISOReader r;
|
||||
CHECK(r.Open((dir / "postgap.cue").string()));
|
||||
CHECK(r.TrackPregapLBA(2) == 6);
|
||||
CHECK(r.TrackStartLBA(2) == 6);
|
||||
CHECK(r.GetSectorCount() == 9);
|
||||
uint8_t raw[2352];
|
||||
CHECK(r.ReadRawSector(3, raw) && raw[0] == 0x13);
|
||||
CHECK(r.ReadRawSector(4, raw) && raw[0] == 0x00);
|
||||
CHECK(r.ReadRawSector(5, raw) && raw[0] == 0x00);
|
||||
CHECK(r.ReadRawSector(6, raw) && raw[0] == 0x40);
|
||||
}
|
||||
|
||||
// No gap lines: the layout is the plain concatenation, as before.
|
||||
{
|
||||
PS1::ISOReader r;
|
||||
CHECK(r.Open((dir / "multi_stored.cue").string()));
|
||||
CHECK(r.TrackPregapLBA(2) == 4);
|
||||
CHECK(r.TrackStartLBA(2) == 6);
|
||||
CHECK(r.TrackPregapLBA(3) == 9);
|
||||
CHECK(r.TrackStartLBA(3) == 11);
|
||||
CHECK(r.GetSectorCount() == 13);
|
||||
}
|
||||
|
||||
fs::remove_all(dir);
|
||||
if (g_failures) {
|
||||
std::printf("iso_reader_pregap_test: %d failure(s)\n", g_failures);
|
||||
return 1;
|
||||
}
|
||||
std::printf("iso_reader_pregap_test: OK\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user