245 行
6.6 KiB
C++
245 行
6.6 KiB
C++
#pragma once
|
|
|
|
#include <nall/file.hpp>
|
|
#include <nall/maybe.hpp>
|
|
#include <nall/string.hpp>
|
|
#include <libchdr/chd.h>
|
|
|
|
namespace nall::Decode {
|
|
|
|
struct CHD {
|
|
~CHD();
|
|
struct Index {
|
|
auto sectorCount() const -> u32;
|
|
|
|
u8 number = 0xff; //00-99
|
|
s32 lba = -1;
|
|
s32 end = -1;
|
|
s32 chd_lba = -1;
|
|
};
|
|
|
|
struct Track {
|
|
auto sectorCount() const -> u32;
|
|
|
|
u8 number = 0xff; //01-99
|
|
string type;
|
|
vector<Index> indices;
|
|
maybe<s32> pregap;
|
|
maybe<s32> postgap;
|
|
};
|
|
|
|
auto load(const string& location) -> bool;
|
|
auto read(u32 sector) const -> vector<u8>;
|
|
auto sectorCount() const -> u32;
|
|
|
|
vector<Track> tracks;
|
|
private:
|
|
file_buffer fp;
|
|
chd_file* chd = nullptr;
|
|
static constexpr int chd_sector_size = 2352 + 96;
|
|
size_t chd_hunk_size;
|
|
mutable vector<u8> chd_hunk_buffer;
|
|
mutable int chd_current_hunk = -1;
|
|
};
|
|
|
|
inline CHD::~CHD() {
|
|
if (chd != nullptr) {
|
|
chd_close(chd);
|
|
}
|
|
}
|
|
|
|
inline auto CHD::load(const string& location) -> bool {
|
|
fp = file::open(location, file::mode::read);
|
|
if(!fp) {
|
|
print("CHD: Failed to open ", location, "\n");
|
|
return false;
|
|
}
|
|
|
|
chd_error err = chd_open_file(fp.handle(), CHD_OPEN_READ, nullptr, &chd);
|
|
if (err != CHDERR_NONE) {
|
|
print("CHD: Failed to open ", location, ": ", chd_error_string(err), "\n");
|
|
return false;
|
|
}
|
|
|
|
const chd_header* header = chd_get_header(chd);
|
|
chd_hunk_size = header->hunkbytes;
|
|
|
|
if ((chd_hunk_size % chd_sector_size) != 0) {
|
|
print("CHD: hunk size (", chd_hunk_size, ") is not a multiple of ", chd_sector_size, "\n");
|
|
return false;
|
|
}
|
|
|
|
chd_hunk_buffer.resize(chd_hunk_size);
|
|
u32 disc_lba = 0;
|
|
u32 chd_lba = 0;
|
|
|
|
// Fetch track structure
|
|
while(true) {
|
|
char metadata[256];
|
|
char type[256];
|
|
char subtype[256];
|
|
char pgtype[256];
|
|
char pgsub[256];
|
|
u32 metadata_size;
|
|
|
|
int track_no;
|
|
int frames;
|
|
int pregap_frames;
|
|
int postgap_frames;
|
|
|
|
// First, attempt to fetch CDROMv2 metadata
|
|
err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, tracks.size(), metadata, sizeof(metadata), &metadata_size, nullptr, nullptr);
|
|
if (err == CHDERR_NONE) {
|
|
if (std::sscanf(metadata, CDROM_TRACK_METADATA2_FORMAT, &track_no, type, subtype, &frames, &pregap_frames, pgtype, pgsub, &postgap_frames) != 8) {
|
|
print("CHD: Invalid track v2 metadata: ", metadata, "\n");
|
|
return false;
|
|
}
|
|
} else {
|
|
// That failed, so try to fetch CDROM (old) metadata
|
|
err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, tracks.size(), metadata, sizeof(metadata), &metadata_size, nullptr, nullptr);
|
|
if (err != CHDERR_NONE) {
|
|
// Both meta-data types failed to fetch, so assume there are no further tracks
|
|
break;
|
|
}
|
|
|
|
if (std::sscanf(metadata, CDROM_TRACK_METADATA_FORMAT, &track_no, type, subtype, &frames) != 4) {
|
|
print("CHD: Invalid track metadata: ", metadata, "\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We currently only support RAW and audio tracks; log an error and exit if we see anything different
|
|
auto typeStr = string{type};
|
|
if (!(typeStr.find("_RAW") || typeStr.find("AUDIO") || typeStr.find("MODE1"))) {
|
|
print("CHD: Unsupported track type: ", type, "\n");
|
|
return false;
|
|
}
|
|
|
|
const bool pregap_in_file = (pregap_frames > 0 && pgtype[0] == 'V');
|
|
|
|
// First track should have 2 second pregap as standard
|
|
if(track_no == 1 && !pregap_in_file) pregap_frames = 2 * 75;
|
|
|
|
// Add the new track
|
|
Track track;
|
|
track.number = track_no;
|
|
track.type = type;
|
|
track.pregap = pregap_frames;
|
|
track.postgap = postgap_frames;
|
|
|
|
// index0 = Pregap
|
|
if (pregap_frames > 0) {
|
|
Index index;
|
|
index.number = 0;
|
|
index.lba = disc_lba;
|
|
index.end = disc_lba + pregap_frames - 1;
|
|
|
|
if (pregap_in_file) {
|
|
if (pregap_frames > frames) {
|
|
print("CHD: pregap length ", pregap_frames, " exceeds track length ", frames, "\n");
|
|
return false;
|
|
}
|
|
|
|
index.chd_lba = chd_lba;
|
|
chd_lba += pregap_frames;
|
|
frames -= pregap_frames;
|
|
}
|
|
|
|
disc_lba += pregap_frames;
|
|
track.indices.append(index);
|
|
}
|
|
|
|
// index1 = track data
|
|
{
|
|
Index index;
|
|
index.number = 1;
|
|
index.lba = disc_lba;
|
|
index.end = disc_lba + frames - 1;
|
|
index.chd_lba = chd_lba;
|
|
track.indices.append(index);
|
|
disc_lba += frames;
|
|
chd_lba += frames;
|
|
|
|
// chdman pads each track to a 4-frame boundary
|
|
chd_lba = (chd_lba + 3) / 4 * 4;
|
|
}
|
|
|
|
// index2 = postgap
|
|
if (postgap_frames > 0) {
|
|
Index index;
|
|
index.number = 2;
|
|
index.lba = disc_lba;
|
|
index.end = disc_lba + postgap_frames - 1;
|
|
track.indices.append(index);
|
|
disc_lba += postgap_frames;
|
|
}
|
|
|
|
tracks.append(track);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline auto CHD::read(u32 sector) const -> vector<u8> {
|
|
// Convert LBA in CD-ROM to LBA in CHD
|
|
for(auto& track : tracks) {
|
|
for(auto& index : track.indices) {
|
|
if (sector >= index.lba && sector <= index.end) {
|
|
auto chd_lba = (sector - index.lba) + index.chd_lba;
|
|
|
|
vector<u8> output;
|
|
output.resize(track.type == "MODE1" ? 2048 : 2352);
|
|
|
|
int hunk = (chd_lba * chd_sector_size) / chd_hunk_size;
|
|
int offset = (chd_lba * chd_sector_size) % chd_hunk_size;
|
|
|
|
if (hunk != chd_current_hunk) {
|
|
chd_read(chd, hunk, chd_hunk_buffer.data());
|
|
chd_current_hunk = hunk;
|
|
}
|
|
|
|
// Audio data is in big-endian, so we need to byteswap
|
|
if (track.type == "AUDIO") {
|
|
u8* src_ptr = chd_hunk_buffer.data() + offset;
|
|
u8* dst_ptr = output.data();
|
|
const int value_count = 2352 / sizeof(uint16_t);
|
|
for (int i = 0; i < value_count; i++) {
|
|
u16 value;
|
|
memcpy(&value, src_ptr, sizeof(value));
|
|
value = (value << 8) | (value >> 8);
|
|
memcpy(dst_ptr, &value, sizeof(value));
|
|
src_ptr += sizeof(value);
|
|
dst_ptr += sizeof(value);
|
|
}
|
|
} else {
|
|
std::copy(chd_hunk_buffer.data() + offset, chd_hunk_buffer.data() + offset + output.size(), output.data());
|
|
}
|
|
|
|
return output;
|
|
}
|
|
}
|
|
}
|
|
|
|
print("CHD: Attempting to read from unmapped sector ", sector, "\n");
|
|
return {};
|
|
}
|
|
|
|
inline auto CHD::sectorCount() const -> u32 {
|
|
u32 count = 0;
|
|
for(auto& track : tracks) count += track.sectorCount();
|
|
return count;
|
|
}
|
|
|
|
inline auto CHD::Track::sectorCount() const -> u32 {
|
|
u32 count = 0;
|
|
for(auto& index : indices) count += index.sectorCount();
|
|
return count;
|
|
}
|
|
|
|
inline auto CHD::Index::sectorCount() const -> u32 {
|
|
if(end < 0) return 0;
|
|
return end - lba + 1;
|
|
}
|
|
|
|
}
|