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 bb02ebff8f937db98e1622fad1c0b6da7cdb084b
Showing with 248 additions and 40 deletions.
  1. +4 βˆ’0 README.md
  2. +4 βˆ’0 src/device/cdrom/cdrom.cpp
  3. +1 βˆ’0 src/device/cdrom/cdrom.h
  4. +25 βˆ’29 src/device/cdrom/commands.cpp
  5. +105 βˆ’0 src/disc/disc.cpp
  6. +10 βˆ’0 src/disc/disc.h
  7. +2 βˆ’0 src/disc/format/chd_format.cpp
  8. +7 βˆ’7 src/disc/format/cue.cpp
  9. +6 βˆ’3 src/disc/format/cue_parser.cpp
  10. +11 βˆ’1 src/disc/position.h
  11. +43 βˆ’0 src/disc/subchannel_q.cpp
  12. +30 βˆ’0 src/disc/subchannel_q.h
@@ -13,6 +13,8 @@ See [Avocado compatibility list](https://avocado-db.czekanski.info)

## Changelog

*2.09.2019* - Anti-Modchip and LibCrypt protected games support

*13.03.2019* - merged MDEC (video decoder) support

*8.12.2018* - created Compatibility list webapp
@@ -73,6 +75,8 @@ You can run the included Playstation firmware replacement *Caetla* with the **Se

To load a .cue/.bin/.img/.chd file just drag and drop it.

PAL games with LibCrypt protection need additional subchannel info - download proper file .SBI or .LSD file from [Redump](http://redump.org/discs/system/psx/), place it in the same folder as game image and make sure has identical name as .cue/.bin/... file.

## Controls

- **Space** - pause/resume emulation
@@ -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 comments on commit bb02ebf

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