mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
feat(runtime): add CHD discs and mod-owned loading modes
This commit is contained in:
+240
-23
@@ -1,9 +1,12 @@
|
||||
#include "iso_reader.h"
|
||||
#include "cue_sheet.h"
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include "iso_reader.h"
|
||||
#include "cue_sheet.h"
|
||||
#include <libchdr/cdrom.h>
|
||||
#include <libchdr/chd.h>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
namespace PS1 {
|
||||
|
||||
@@ -17,9 +20,102 @@ constexpr size_t RAW_SECTOR_SIZE = 2352;
|
||||
constexpr size_t RAW_DATA_OFFSET = 24;
|
||||
|
||||
// Primary Volume Descriptor location
|
||||
constexpr uint32_t PVD_SECTOR = 16;
|
||||
|
||||
ISOReader::ISOReader()
|
||||
constexpr uint32_t PVD_SECTOR = 16;
|
||||
|
||||
enum class CHDTrackMode {
|
||||
Mode1,
|
||||
Mode1Raw,
|
||||
Mode2,
|
||||
Mode2Form1,
|
||||
Mode2Form2,
|
||||
Mode2FormMix,
|
||||
Mode2Raw,
|
||||
Audio,
|
||||
};
|
||||
|
||||
struct CHDSpan {
|
||||
uint32_t disc_start = 0;
|
||||
uint32_t sector_count = 0;
|
||||
uint64_t chd_frame_start = 0;
|
||||
CHDTrackMode mode = CHDTrackMode::Mode2Raw;
|
||||
bool stored = true;
|
||||
};
|
||||
|
||||
struct CHDState {
|
||||
chd_file* file = nullptr;
|
||||
uint32_t hunk_bytes = 0;
|
||||
uint32_t frames_per_hunk = 0;
|
||||
uint32_t current_hunk = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t disc_sector_count = 0;
|
||||
std::vector<uint8_t> hunk;
|
||||
std::vector<CHDSpan> spans;
|
||||
|
||||
~CHDState() {
|
||||
if (file) chd_close(file);
|
||||
}
|
||||
};
|
||||
|
||||
static bool parse_chd_track_mode(const char* text, CHDTrackMode& mode) {
|
||||
if (std::strcmp(text, "MODE1") == 0) mode = CHDTrackMode::Mode1;
|
||||
else if (std::strcmp(text, "MODE1_RAW") == 0) mode = CHDTrackMode::Mode1Raw;
|
||||
else if (std::strcmp(text, "MODE2") == 0) mode = CHDTrackMode::Mode2;
|
||||
else if (std::strcmp(text, "MODE2_FORM1") == 0) mode = CHDTrackMode::Mode2Form1;
|
||||
else if (std::strcmp(text, "MODE2_FORM2") == 0) mode = CHDTrackMode::Mode2Form2;
|
||||
else if (std::strcmp(text, "MODE2_FORM_MIX") == 0) mode = CHDTrackMode::Mode2FormMix;
|
||||
else if (std::strcmp(text, "MODE2_RAW") == 0) mode = CHDTrackMode::Mode2Raw;
|
||||
else if (std::strcmp(text, "AUDIO") == 0) mode = CHDTrackMode::Audio;
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const CHDSpan* chd_span_for_lba(const CHDState& chd, uint32_t lba) {
|
||||
for (const CHDSpan& span : chd.spans) {
|
||||
if (lba >= span.disc_start &&
|
||||
lba - span.disc_start < span.sector_count) {
|
||||
return &span;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool read_chd_raw(CHDState& chd, uint32_t lba, uint8_t* buffer,
|
||||
CHDTrackMode* mode_out) {
|
||||
const CHDSpan* span = chd_span_for_lba(chd, lba);
|
||||
if (!span) return false;
|
||||
if (mode_out) *mode_out = span->mode;
|
||||
if (!span->stored) {
|
||||
std::memset(buffer, 0, RAW_SECTOR_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint64_t frame =
|
||||
span->chd_frame_start + (uint64_t)(lba - span->disc_start);
|
||||
const uint64_t hunk_index64 = frame / chd.frames_per_hunk;
|
||||
if (hunk_index64 > std::numeric_limits<uint32_t>::max()) return false;
|
||||
const uint32_t hunk_index = (uint32_t)hunk_index64;
|
||||
const size_t hunk_offset =
|
||||
(size_t)(frame % chd.frames_per_hunk) * CD_FRAME_SIZE;
|
||||
if (hunk_offset + CD_MAX_SECTOR_DATA > chd.hunk.size()) return false;
|
||||
|
||||
if (chd.current_hunk != hunk_index) {
|
||||
if (chd_read(chd.file, hunk_index, chd.hunk.data()) != CHDERR_NONE) {
|
||||
chd.current_hunk = std::numeric_limits<uint32_t>::max();
|
||||
return false;
|
||||
}
|
||||
chd.current_hunk = hunk_index;
|
||||
}
|
||||
|
||||
std::memcpy(buffer, chd.hunk.data() + hunk_offset, RAW_SECTOR_SIZE);
|
||||
if (span->mode == CHDTrackMode::Audio) {
|
||||
// CD audio in CHD is canonical big-endian PCM. The existing BIN/CUE
|
||||
// path and CD-ROM mixer exchange little-endian signed samples.
|
||||
for (size_t i = 0; i < RAW_SECTOR_SIZE; i += 2)
|
||||
std::swap(buffer[i], buffer[i + 1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ISOReader::ISOReader()
|
||||
: is_open_(false) {
|
||||
root_dir_.lba = 0;
|
||||
root_dir_.size = 0;
|
||||
@@ -29,15 +125,120 @@ ISOReader::~ISOReader() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool ISOReader::Open(const std::string& filename) {
|
||||
bool ISOReader::Open(const std::string& filename) {
|
||||
// Close any previously opened image (also resets tracks/segments)
|
||||
Close();
|
||||
|
||||
// Check if file exists
|
||||
if (!std::filesystem::exists(filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PSXRecompV4::path_has_extension_ci(
|
||||
std::filesystem::path(filename), ".chd")) {
|
||||
auto state = std::make_unique<CHDState>();
|
||||
if (chd_open(filename.c_str(), CHD_OPEN_READ, nullptr, &state->file) !=
|
||||
CHDERR_NONE) {
|
||||
return false;
|
||||
}
|
||||
const chd_header* header = chd_get_header(state->file);
|
||||
if (!header || header->hunkbytes == 0 ||
|
||||
(header->hunkbytes % CD_FRAME_SIZE) != 0) {
|
||||
return false;
|
||||
}
|
||||
state->hunk_bytes = header->hunkbytes;
|
||||
state->frames_per_hunk = header->hunkbytes / CD_FRAME_SIZE;
|
||||
state->hunk.resize(header->hunkbytes);
|
||||
|
||||
uint32_t disc_lba = 0;
|
||||
uint64_t chd_frame = 0;
|
||||
for (uint32_t index = 0; index < CD_MAX_TRACKS; ++index) {
|
||||
char metadata[256] = {};
|
||||
uint32_t metadata_length = 0;
|
||||
int number = 0;
|
||||
int frames = 0;
|
||||
int pregap = 0;
|
||||
int postgap = 0;
|
||||
char type[64] = {};
|
||||
char subtype[64] = {};
|
||||
char pgtype[64] = {};
|
||||
char pgsub[64] = {};
|
||||
|
||||
chd_error err = chd_get_metadata(
|
||||
state->file, CDROM_TRACK_METADATA2_TAG, index,
|
||||
metadata, sizeof(metadata) - 1, &metadata_length, nullptr, nullptr);
|
||||
bool v2 = err == CHDERR_NONE;
|
||||
if (v2) {
|
||||
if (std::sscanf(
|
||||
metadata,
|
||||
"TRACK:%d TYPE:%63s SUBTYPE:%63s FRAMES:%d "
|
||||
"PREGAP:%d PGTYPE:%63s PGSUB:%63s POSTGAP:%d",
|
||||
&number, type, subtype, &frames, &pregap,
|
||||
pgtype, pgsub, &postgap) != 8) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
err = chd_get_metadata(
|
||||
state->file, CDROM_TRACK_METADATA_TAG, index,
|
||||
metadata, sizeof(metadata) - 1, &metadata_length, nullptr, nullptr);
|
||||
if (err == CHDERR_METADATA_NOT_FOUND) break;
|
||||
if (err != CHDERR_NONE ||
|
||||
std::sscanf(metadata,
|
||||
"TRACK:%d TYPE:%63s SUBTYPE:%63s FRAMES:%d",
|
||||
&number, type, subtype, &frames) != 4) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CHDTrackMode mode;
|
||||
if (number != (int)index + 1 || frames <= 0 ||
|
||||
!parse_chd_track_mode(type, mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool pregap_stored =
|
||||
v2 && pregap > 0 && (pgtype[0] == 'V' || pgtype[0] == 'v');
|
||||
const uint32_t stored_pregap =
|
||||
pregap_stored ? std::min<uint32_t>((uint32_t)pregap,
|
||||
(uint32_t)frames) : 0;
|
||||
const uint32_t virtual_pregap =
|
||||
!pregap_stored && pregap > 0 ? (uint32_t)pregap : 0;
|
||||
const uint32_t pregap_lba = disc_lba;
|
||||
|
||||
if (virtual_pregap) {
|
||||
state->spans.push_back(
|
||||
{disc_lba, virtual_pregap, 0, mode, false});
|
||||
disc_lba += virtual_pregap;
|
||||
}
|
||||
|
||||
state->spans.push_back(
|
||||
{disc_lba, (uint32_t)frames, chd_frame, mode, true});
|
||||
CDTrack track;
|
||||
track.number = number;
|
||||
track.is_audio = mode == CHDTrackMode::Audio;
|
||||
track.start_lba = disc_lba + stored_pregap;
|
||||
track.pregap_lba = pregap_lba;
|
||||
tracks_.push_back(track);
|
||||
|
||||
disc_lba += (uint32_t)frames;
|
||||
chd_frame += (uint32_t)frames;
|
||||
chd_frame = (chd_frame + CD_TRACK_PADDING - 1) &
|
||||
~(uint64_t)(CD_TRACK_PADDING - 1);
|
||||
}
|
||||
if (tracks_.empty()) return false;
|
||||
|
||||
state->disc_sector_count = disc_lba;
|
||||
bin_path_ = filename;
|
||||
chd_ = std::move(state);
|
||||
is_open_ = true;
|
||||
if (!ParseVolumeDescriptor()) {
|
||||
volume_id_.clear();
|
||||
root_dir_.lba = 0;
|
||||
root_dir_.size = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ordered list of BINARY files backing the disc. A bare .bin/.iso is a
|
||||
// single-entry list; a .cue contributes one entry per FILE line (redump
|
||||
// multi-track dumps ship one file per track).
|
||||
@@ -139,7 +340,8 @@ bool ISOReader::Open(const std::string& filename) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ISOReader::Close() {
|
||||
void ISOReader::Close() {
|
||||
chd_.reset();
|
||||
for (BinSegment& seg : segments_) {
|
||||
if (seg.file.is_open()) {
|
||||
seg.file.close();
|
||||
@@ -164,12 +366,23 @@ BinSegment* ISOReader::SegmentForLBA(uint32_t lba) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ISOReader::ReadSector(uint32_t lba, uint8_t* buffer) {
|
||||
bool ISOReader::ReadSector(uint32_t lba, uint8_t* buffer) {
|
||||
if (!is_open_ || !buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BinSegment* seg = SegmentForLBA(lba);
|
||||
if (chd_) {
|
||||
uint8_t raw[RAW_SECTOR_SIZE];
|
||||
CHDTrackMode mode;
|
||||
if (!read_chd_raw(*chd_, lba, raw, &mode)) return false;
|
||||
const size_t offset =
|
||||
(mode == CHDTrackMode::Mode1 ||
|
||||
mode == CHDTrackMode::Mode1Raw) ? 16 : RAW_DATA_OFFSET;
|
||||
std::memcpy(buffer, raw + offset, SECTOR_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
BinSegment* seg = SegmentForLBA(lba);
|
||||
if (!seg) {
|
||||
return false;
|
||||
}
|
||||
@@ -212,12 +425,14 @@ bool ISOReader::ReadSector(uint32_t lba, uint8_t* buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ISOReader::ReadRawSector(uint32_t lba, uint8_t* buffer) {
|
||||
bool ISOReader::ReadRawSector(uint32_t lba, uint8_t* buffer) {
|
||||
if (!is_open_ || !buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BinSegment* seg = SegmentForLBA(lba);
|
||||
if (chd_) return read_chd_raw(*chd_, lba, buffer, nullptr);
|
||||
|
||||
BinSegment* seg = SegmentForLBA(lba);
|
||||
if (!seg || !seg->raw) {
|
||||
return false;
|
||||
}
|
||||
@@ -250,10 +465,12 @@ std::string ISOReader::GetBinPath() const {
|
||||
return bin_path_;
|
||||
}
|
||||
|
||||
uint32_t ISOReader::GetSectorCount() {
|
||||
if (!is_open_ || segments_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t ISOReader::GetSectorCount() {
|
||||
if (!is_open_) {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user