Skip to content
Permalink
Browse files

disc: added Subchannel Q support - LibCrypt games are playable

  • Loading branch information
JaCzekanski committed Sep 2, 2019
1 parent a18e8dd commit 4dfa1bd8969a7dce2732f5d527a9bb46d5b4906f
@@ -32,6 +32,10 @@ void CDROM::step() {

auto pos = disc::Position::fromLba(readSector);
std::tie(rawSector, trackType) = disc->read(pos);
auto q = disc->getSubQ(pos);
if (q.validCrc()) {
this->lastQ = q;
}
readSector++;

if (trackType == disc::TrackType::AUDIO && stat.play) {
@@ -183,6 +183,7 @@ class CDROM {

disc::TrackType trackType;
std::unique_ptr<disc::Disc> disc;
disc::SubchannelQ lastQ;
bool mute = false;

CDROM(System* sys);
@@ -206,20 +206,15 @@ void CDROM::cmdGetlocL() {
}

void CDROM::cmdGetlocP() {
auto pos = disc::Position::fromLba(readSector);

int track = disc->getTrackByPosition(pos);
auto posInTrack = pos - disc->getTrackStart(track);

postInterrupt(3);
writeResponse(bcd::toBcd(track)); // track
writeResponse(0x01); // index
writeResponse(bcd::toBcd(posInTrack.mm)); // minute (track)
writeResponse(bcd::toBcd(posInTrack.ss)); // second (track)
writeResponse(bcd::toBcd(posInTrack.ff)); // sector (track)
writeResponse(bcd::toBcd(pos.mm)); // minute (disc)
writeResponse(bcd::toBcd(pos.ss)); // second (disc)
writeResponse(bcd::toBcd(pos.ff)); // sector (disc)
writeResponse(lastQ.data[0]); // track
writeResponse(lastQ.data[1]); // index
writeResponse(lastQ.data[2]); // minute (track)
writeResponse(lastQ.data[3]); // second (track)
writeResponse(lastQ.data[4]); // sector (track)
writeResponse(lastQ.data[6]); // minute (disc)
writeResponse(lastQ.data[7]); // second (disc)
writeResponse(lastQ.data[8]); // sector (disc)

if (verbose) {
printf("CDROM: cmdGetlocP -> (%s)\n", dumpFifo(CDROM_response).c_str());
@@ -238,33 +233,34 @@ void CDROM::cmdGetTN() {
}

void CDROM::cmdGetTD() {
int track = readParam();
postInterrupt(3);
writeResponse(stat._reg);
if (track == 0) // end of last track
{
int track = bcd::toBinary(readParam());

if (track == 0) { // end of last track
auto diskSize = disc->getDiskSize();
if (verbose) printf("GetTD(0): minute: %d, second: %d\n", diskSize.mm, diskSize.ss);

postInterrupt(3);
writeResponse(stat._reg);
writeResponse(bcd::toBcd(diskSize.mm));
writeResponse(bcd::toBcd(diskSize.ss));
} else { // Start of n track
if (track > (int)disc->getTrackCount()) {
// Error
return;
}

} else if (track <= disc->getTrackCount()) { // Start of n track
auto start = disc->getTrackStart(track);

if (verbose) printf("GetTD(%d): minute: %d, second: %d\n", track, start.mm, start.ss);
postInterrupt(3);
writeResponse(stat._reg);
writeResponse(bcd::toBcd(start.mm));
writeResponse(bcd::toBcd(start.ss));
} else { // error
postInterrupt(5);
writeResponse(0x10);

if (verbose) {
printf("GetTD(0x%02x): error\n", track);
}
return;
}

if (verbose) {
printf("CDROM: cmdGetTD(0x%02x) -> ", track);
dumpFifo(CDROM_response);
printf("\n");
printf("CDROM: cmdGetTD(0x%02x) -> (%s)\n", track, dumpFifo(CDROM_response).c_str());
}
}

@@ -0,0 +1,105 @@
#include "disc.h"
#include "utils/file.h"

namespace disc {
SubchannelQ Disc::getSubQ(Position pos) {
if (modifiedQ.find(pos) != modifiedQ.end()) {
return modifiedQ[pos];
}

TrackType type = read(pos).second;
int track = getTrackByPosition(pos);
auto posInTrack = pos - getTrackStart(track);

return SubchannelQ::generateForPosition(track, pos, posInTrack, type == TrackType::AUDIO);
}

bool Disc::loadSubchannel(const std::string& path) {
std::string basePath = getPath(path) + getFilename(path);

if (auto f = getFileContents(basePath + ".lsd"); !f.empty()) {
return loadLsd(f);
}
if (auto f = getFileContents(basePath + ".sbi"); !f.empty()) {
return loadSbi(f);
}

return false;
}

bool Disc::loadLsd(const std::vector<uint8_t>& lsd) {
if (lsd.empty()) {
printf("[DISC] LSD file is empty\n");
return false;
}

const int bytesPerEntry = 15;
const int count = lsd.size() / bytesPerEntry;

for (int i = 0; i < count; i++) {
int p = bytesPerEntry * i;

int mm = bcd::toBinary(lsd[p + 0]);
int ss = bcd::toBinary(lsd[p + 1]);
int ff = bcd::toBinary(lsd[p + 2]);

Position pos(mm, ss, ff);

SubchannelQ q;
q.control._ = lsd[p + 3];
for (int j = 0; j < 9; j++) {
q.data[j] = lsd[p + 4 + j];
}
q.crc16 = (lsd[p + 12] << 8) | lsd[p + 13];

modifiedQ[pos] = q;
}

printf("[DISC] Loaded LSD file\n");
return true;
}

bool Disc::loadSbi(const std::vector<uint8_t>& sbi) {
if (sbi.empty()) {
printf("[DISC] SBI file is empty\n");
return false;
}

if (sbi[0] != 'S' || sbi[1] != 'B' || sbi[2] != 'I' || sbi[3] != '\0') {
printf("[DISC] Invalid sbi header\n");
return false;
}

const int headerSize = 4;
const int bytesPerEntry = 14;
const int count = (sbi.size() - headerSize) / bytesPerEntry;

for (int i = 0; i < count; i++) {
int p = headerSize + bytesPerEntry * i;

int mm = bcd::toBinary(sbi[p + 0]);
int ss = bcd::toBinary(sbi[p + 1]);
int ff = bcd::toBinary(sbi[p + 2]);
int dummy = sbi[p + 3];
if (dummy != 1) {
printf("[DISC] Unsupported .sbi file, please create an issue on Github and attach this .sbi\n");
return false;
}

Position pos(mm, ss, ff);

SubchannelQ q;
q.control._ = sbi[p + 4];
for (int j = 0; j < 9; j++) {
q.data[j] = sbi[p + 5 + j];
}
// Sbi does not include CRC-16, LibCrypt checks only if crc is broken
q.crc16 = ~q.calculateCrc();

modifiedQ[pos] = q;
}

printf("[DISC] Loaded SBI file\n");
return true;
}
} // namespace disc
@@ -1,7 +1,9 @@
#pragma once
#include <cstdint>
#include <unordered_map>
#include <vector>
#include "position.h"
#include "subchannel_q.h"

namespace disc {
enum class TrackType { DATA, AUDIO, INVALID };
@@ -19,5 +21,13 @@ struct Disc {
virtual Position getTrackStart(int track) const = 0;
virtual Position getTrackLength(int track) const = 0;
virtual Position getDiskSize() const = 0;

SubchannelQ getSubQ(Position pos);
bool loadSubchannel(const std::string& path);

private:
std::unordered_map<Position, SubchannelQ> modifiedQ;
bool loadLsd(const std::vector<uint8_t>& lsd);
bool loadSbi(const std::vector<uint8_t>& sbi);
};
} // namespace disc
@@ -76,6 +76,8 @@ std::unique_ptr<Chd> Chd::open(const std::string& path) {
chd->tracks.push_back(track);
}

chd->loadSubchannel(path);

return chd;
}

@@ -1,17 +1,15 @@
#include "cue.h"

namespace disc {
namespace format {
std::string Cue::getFile() const {
return file;
}
namespace format {
std::string Cue::getFile() const { return file; }

Position Cue::getDiskSize() const {
int frames = 0;
for (auto t : tracks) {
frames += t.frames;
}
return Position::fromLba(frames) + Position{0,2,0};
return Position::fromLba(frames) + Position{0, 2, 0};
}

size_t Cue::getTrackCount() const { return tracks.size(); }
@@ -94,7 +92,9 @@ std::unique_ptr<Cue> Cue::fromBin(const char* file) {
cue->file = file;
cue->tracks.push_back(t);

cue->loadSubchannel(file);

return cue;
}
}
} // namespace utils
} // namespace format
} // namespace disc
@@ -4,7 +4,7 @@
#include "utils/string.h"

namespace disc {
namespace format{
namespace format {
bool CueParser::parseFile(std::string& line) {
std::smatch matches;
regex_search(line, matches, regexFile);
@@ -180,7 +180,10 @@ std::unique_ptr<Cue> CueParser::parse(const char* path) {
}

if (cue.getTrackCount() == 0) return {};

cue.loadSubchannel(path);

return std::make_unique<Cue>(cue);
}
};
} // namespace utils
}; // namespace format
} // namespace disc
@@ -1,4 +1,5 @@
#pragma once
#include <functional>
#include <string>

namespace disc {
@@ -19,4 +20,13 @@ struct Position {
bool operator>=(const Position& p) const;
bool operator<(const Position& p) const;
};
} // namespace utils
} // namespace disc

namespace std {
template <>
struct hash<disc::Position> {
std::size_t operator()(const disc::Position& p) const {
return ((p.mm ^ (p.ss << 1)) >> 1) ^ (p.ff << 1); //
}
};
} // namespace std
@@ -0,0 +1,43 @@
#include "subchannel_q.h"

namespace disc {

SubchannelQ SubchannelQ::generateForPosition(int track, disc::Position pos, disc::Position posInTrack, bool isAudio) {
SubchannelQ q;
q.control.adr = 1;
q.control.data = !isAudio;

q.data[0] = bcd::toBcd(track + 1); // Track
q.data[1] = bcd::toBcd(1); // Index
q.data[2] = bcd::toBcd(posInTrack.mm); // M - Track
q.data[3] = bcd::toBcd(posInTrack.ss); // S - Track
q.data[4] = bcd::toBcd(posInTrack.ff); // F - Track
q.data[5] = 0; // Reserved
q.data[6] = bcd::toBcd(pos.mm); // M - Disc
q.data[7] = bcd::toBcd(pos.ss); // S - Disc
q.data[8] = bcd::toBcd(pos.ff); // F - Disc
q.crc16 = q.calculateCrc();

return q;
}

uint16_t SubchannelQ::calculateCrc() {
uint8_t lsb = 0;
uint8_t msb = 0;
for (int i = 0; i < 0x0a; i++) {
uint8_t x;
if (i == 0) {
x = control._;
} else {
x = data[i - 1];
}
x ^= msb;
x = x ^ (x >> 4);
msb = lsb ^ (x >> 3) ^ (x << 4);
lsb = x ^ (x << 5);
}
return msb << 8 | lsb;
}

bool SubchannelQ::validCrc() { return crc16 == calculateCrc(); }
}; // namespace disc
@@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
#include "position.h"
#include "utils/bcd.h"

namespace disc {
struct SubchannelQ {
union Control {
struct {
uint8_t adr : 4;
uint8_t audioPreemphasis : 1;
uint8_t digitalCopy : 1;
uint8_t data : 1; // 0 - audio, 1 - data;
uint8_t quadAudio : 1; // 0 - stereo, 1 - quad
};

uint8_t _;

Control() : _(0) {}
};
// Sync skipped
Control control{};
uint8_t data[9]{};
uint16_t crc16{};

static SubchannelQ generateForPosition(int track, disc::Position pos, disc::Position posInTrack, bool isAudio);
uint16_t calculateCrc();
bool validCrc();
};
}; // namespace disc

0 comments on commit 4dfa1bd

Please sign in to comment.
You can’t perform that action at this time.