BizHawk/waterbox/ares64/ares/nall/vfs/cdrom.hpp

262 lines
9.8 KiB
C++

#pragma once
#include <nall/array-span.hpp>
#include <nall/cd.hpp>
#include <nall/file.hpp>
#include <nall/string.hpp>
#include <nall/decode/cue.hpp>
#include <nall/decode/chd.hpp>
#include <nall/decode/wav.hpp>
namespace nall::vfs {
struct cdrom : file {
static auto open(const string& location) -> shared_pointer<cdrom> {
auto instance = shared_pointer<cdrom>{new cdrom};
if(location.iendsWith(".cue") && instance->loadCue(location)) return instance;
if(location.iendsWith(".chd") && instance->loadChd(location)) return instance;
return {};
}
auto writable() const -> bool override { return false; }
auto data() const -> const u8* override { return _image.data(); }
auto data() -> u8* override { return _image.data(); }
auto size() const -> u64 override { return _image.size(); }
auto offset() const -> u64 override { return _offset; }
auto resize(u64 size) -> bool override {
//unsupported
return false;
}
auto seek(s64 offset, index mode) -> void override {
if(mode == index::absolute) _offset = (u64)offset;
if(mode == index::relative) _offset += (s64)offset;
}
auto read() -> u8 override {
if(_offset >= _image.size()) return 0x00;
return _image[_offset++];
}
auto write(u8 data) -> void override {
//CD-ROMs are read-only; but allow writing anyway if needed, since the image is in memory
if(_offset >= _image.size()) return;
_image[_offset++] = data;
}
private:
auto loadCue(const string& cueLocation) -> bool {
Decode::CUE cuesheet;
if(!cuesheet.load(cueLocation)) return false;
CD::Session session;
session.leadIn.lba = -LeadInSectors;
session.leadIn.end = -1;
s32 lbaFileBase = 0;
// add 2 sec pregap to 1st track
if(!cuesheet.files[0].tracks[0].pregap)
cuesheet.files[0].tracks[0].pregap = Track1Pregap;
else
cuesheet.files[0].tracks[0].pregap = Track1Pregap + cuesheet.files[0].tracks[0].pregap();
if(cuesheet.files[0].tracks[0].indices[0].number == 1) {
session.tracks[1].indices[0].lba = 0;
session.tracks[1].indices[0].end =
cuesheet.files[0].tracks[0].pregap() + cuesheet.files[0].tracks[0].indices[0].lba - 1;
}
s32 lbaIndex = 0;
for(auto& file : cuesheet.files) {
for(auto& track : file.tracks) {
session.tracks[track.number].control = track.type == "audio" ? 0b0000 : 0b0100;
if(track.pregap) lbaFileBase += track.pregap();
for(auto& index : track.indices) {
if(index.lba >= 0) {
session.tracks[track.number].indices[index.number].lba = lbaFileBase + index.lba;
session.tracks[track.number].indices[index.number].end = lbaFileBase + index.end;
if(index.number == 0 && track.pregap) {
session.tracks[track.number].indices[index.number].lba -= track.pregap();
session.tracks[track.number].indices[index.number].end -= track.pregap();
}
} else {
// insert gap
session.tracks[track.number].indices[index.number].lba = lbaIndex;
if(index.number == 0)
session.tracks[track.number].indices[index.number].end = lbaIndex + track.pregap() - 1;
else
session.tracks[track.number].indices[index.number].end = lbaIndex + track.postgap() - 1;
}
lbaIndex = session.tracks[track.number].indices[index.number].end + 1;
}
if(track.postgap) lbaFileBase += track.postgap();
}
lbaFileBase = lbaIndex;
}
session.leadOut.lba = lbaFileBase;
session.leadOut.end = lbaFileBase + LeadOutSectors - 1;
// determine track and index ranges
session.firstTrack = 0xff;
for(u32 track : range(100)) {
if(!session.tracks[track]) continue;
if(session.firstTrack > 99) session.firstTrack = track;
// find first index
for(u32 indexID : range(100)) {
auto& index = session.tracks[track].indices[indexID];
if(index) { session.tracks[track].firstIndex = indexID; break; }
}
// find last index
for(u32 indexID : reverse(range(100))) {
auto& index = session.tracks[track].indices[indexID];
if(index) { session.tracks[track].lastIndex = indexID; break; }
}
session.lastTrack = track;
}
_image.resize(2448 * (LeadInSectors + lbaFileBase + LeadOutSectors));
lbaFileBase = 0;
for(auto& file : cuesheet.files) {
auto location = string{Location::path(cueLocation), file.name};
auto filedata = nall::file::open(location, nall::file::mode::read);
if(file.type == "wave") filedata.seek(44); //skip RIFF header
for(auto& track : file.tracks) {
if(track.pregap) lbaFileBase += track.pregap();
for(auto& index : track.indices) {
if(index.lba < 0) continue; // ignore gaps (not in file)
for(s32 sector : range(index.sectorCount())) {
auto target = _image.data() + 2448ull * (LeadInSectors + lbaFileBase + index.lba + sector);
auto length = track.sectorSize();
if(length == 2048) {
//ISO: generate header + parity data
memory::assign(target + 0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff); //sync
memory::assign(target + 6, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00); //sync
auto [minute, second, frame] = CD::MSF(lbaFileBase + index.lba + sector);
target[12] = BCD::encode(minute);
target[13] = BCD::encode(second);
target[14] = BCD::encode(frame);
target[15] = 0x01; //mode
filedata.read({target + 16, length});
CD::RSPC::encodeMode1({target, 2352});
}
if(length == 2352) {
//BIN + WAV: direct copy
filedata.read({target, length});
}
}
}
if(track.postgap) lbaFileBase += track.postgap();
}
lbaFileBase += file.tracks.last().indices.last().end + 1;
}
auto subchannel = session.encode(LeadInSectors + session.leadOut.end + 1);
if(auto overlay = nall::file::read({Location::notsuffix(cueLocation), ".sub"})) {
auto target = subchannel.data() + 96 * (LeadInSectors + Track1Pregap);
auto length = (s64)subchannel.size() - 96 * (LeadInSectors + Track1Pregap);
memory::copy(target, length, overlay.data(), overlay.size());
}
for(u64 sector : range(size() / 2448)) {
auto source = subchannel.data() + sector * 96;
auto target = _image.data() + sector * 2448 + 2352;
memory::copy(target, source, 96);
}
return true;
}
auto loadChd(const string& location) -> bool {
Decode::CHD chd;
if(!chd.load(location)) return false;
CD::Session session;
session.leadIn.lba = -LeadInSectors;
session.leadIn.end = -1;
s32 lbaIndex = 0;
for(auto& track : chd.tracks) {
session.tracks[track.number].control = track.type == "AUDIO" ? 0b0000 : 0b0100;
for(auto& index : track.indices) {
session.tracks[track.number].indices[index.number].lba = index.lba;
session.tracks[track.number].indices[index.number].end = index.end;
lbaIndex = session.tracks[track.number].indices[index.number].end + 1;
}
}
session.leadOut.lba = lbaIndex;
session.leadOut.end = lbaIndex + LeadOutSectors - 1;
// determine track and index ranges
session.firstTrack = 0xff;
for(u32 track : range(100)) {
if(!session.tracks[track]) continue;
if(session.firstTrack > 99) session.firstTrack = track;
// find first index
for(u32 indexID : range(100)) {
auto& index = session.tracks[track].indices[indexID];
if(index) { session.tracks[track].firstIndex = indexID; break; }
}
// find last index
for(u32 indexID : reverse(range(100))) {
auto& index = session.tracks[track].indices[indexID];
if(index) { session.tracks[track].lastIndex = indexID; break; }
}
session.lastTrack = track;
}
_image.resize(2448 * (LeadInSectors + lbaIndex + LeadOutSectors));
s32 lba = 0;
for(auto& track : chd.tracks) {
for(auto& index : track.indices) {
for(s32 sector : range(index.sectorCount())) {
auto target = _image.data() + 2448ull * (LeadInSectors + index.lba + sector);
auto sectorData = chd.read(lba);
if(sectorData.size() == 2048) {
//ISO: generate header + parity data
memory::assign(target + 0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff); //sync
memory::assign(target + 6, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00); //sync
auto [minute, second, frame] = CD::MSF(index.lba + sector);
target[12] = BCD::encode(minute);
target[13] = BCD::encode(second);
target[14] = BCD::encode(frame);
target[15] = 0x01; //mode
memory::copy(target + 16, 2048, sectorData.data(), sectorData.size());
CD::RSPC::encodeMode1({target, 2352});
} else {
memory::copy(target, 2352, sectorData.data(), sectorData.size());
}
lba++;
}
}
}
auto subchannel = session.encode(LeadInSectors + session.leadOut.end + 1);
if(auto overlay = nall::file::read({Location::notsuffix(location), ".sub"})) {
auto target = subchannel.data() + 96 * (LeadInSectors + Track1Pregap);
auto length = (s64)subchannel.size() - 96 * (LeadInSectors + Track1Pregap);
memory::copy(target, length, overlay.data(), overlay.size());
}
for(u64 sector : range(size() / 2448)) {
auto source = subchannel.data() + sector * 96;
auto target = _image.data() + sector * 2448 + 2352;
memory::copy(target, source, 96);
}
return true;
}
vector<u8> _image;
u64 _offset = 0;
static constexpr s32 LeadInSectors = 7500;
static constexpr s32 Track1Pregap = 150;
static constexpr s32 LeadOutSectors = 6750;
};
}