initial chd support

This commit is contained in:
Romain Tisserand 2017-07-27 23:29:23 -04:00 committed by Anthony Pesch
parent 1cfecc637a
commit 6b1345795d
3 changed files with 245 additions and 0 deletions

234
src/guest/gdrom/chd.c Normal file
View File

@ -0,0 +1,234 @@
#include <chd.h>
#include "core/assert.h"
#include "guest/gdrom/disc.h"
#include "guest/gdrom/gdrom_types.h"
struct chd {
struct disc;
struct session sessions[DISC_MAX_SESSIONS];
int num_sessions;
struct track tracks[DISC_MAX_TRACKS];
int num_tracks;
chd_file *chd;
/* hunk data cache */
uint8_t *hunkmem;
/* last hunknum read */
int oldhunk;
};
static void chd_read_sector(struct disc *disc, struct track *track, int fad,
void *dst) {
struct chd *chd = (struct chd *)disc;
const chd_header *head = chd_get_header(chd->chd);
int cad = fad - track->file_offset;
int hunknum = (cad * head->unitbytes) / head->hunkbytes;
int hunkofs = (cad * head->unitbytes) % head->hunkbytes;
/* each hunk holds ~8 sectors, optimize when reading contiguous sectors */
if (hunknum != chd->oldhunk) {
int err = chd_read(chd->chd, hunknum, chd->hunkmem);
CHECK_EQ(err, CHDERR_NONE, "chd_read_sector failed fad=%d", fad);
}
memcpy(dst, chd->hunkmem + hunkofs + track->header_size, 2048);
}
static void chd_get_toc(struct disc *disc, int area, struct track **first_track,
struct track **last_track, int *leadin_fad,
int *leadout_fad) {
struct chd *chd = (struct chd *)disc;
/* chd's have one toc per area, and there is one session per area */
struct session *session = &chd->sessions[area];
*first_track = &chd->tracks[session->first_track];
*last_track = &chd->tracks[session->last_track];
*leadin_fad = session->leadin_fad;
*leadout_fad = session->leadout_fad;
}
static struct track *chd_get_track(struct disc *disc, int n) {
struct chd *chd = (struct chd *)disc;
CHECK_LT(n, chd->num_tracks);
return &chd->tracks[n];
}
static int chd_get_num_tracks(struct disc *disc) {
struct chd *chd = (struct chd *)disc;
return chd->num_tracks;
}
static struct session *chd_get_session(struct disc *disc, int n) {
struct chd *chd = (struct chd *)disc;
CHECK_LT(n, chd->num_sessions);
return &chd->sessions[n];
}
static int chd_get_num_sessions(struct disc *disc) {
struct chd *chd = (struct chd *)disc;
return chd->num_sessions;
}
static int chd_get_format(struct disc *disc) {
return GD_DISC_GDROM;
}
static void chd_destroy(struct disc *disc) {
struct chd *chd = (struct chd *)disc;
free(chd->hunkmem);
chd_close(chd->chd);
}
static int chd_parse(struct disc *disc, const char *filename) {
struct chd *chd = (struct chd *)disc;
chd_error err = chd_open(filename, CHD_OPEN_READ, 0, &chd->chd);
if (err != CHDERR_NONE) {
return 0;
}
/* allocate storage for sector reads */
const chd_header *head = chd_get_header(chd->chd);
chd->hunkmem = malloc(head->hunkbytes);
chd->oldhunk = -1;
/* parse tracks */
char tmp[512];
int cad = 0;
int fad = GDROM_PREGAP;
while (1) {
int tkid = 0, frames = 0, pad = 0, pregap = 0, postgap = 0;
char type[64], subtype[32], pgtype[32], pgsub[32];
/* try to read the new v5 metadata tag */
err = chd_get_metadata(chd->chd, GDROM_TRACK_METADATA_TAG, chd->num_tracks,
tmp, sizeof(tmp), NULL, NULL, NULL);
if (err == CHDERR_NONE) {
sscanf(tmp, GDROM_TRACK_METADATA_FORMAT, &tkid, type, subtype, &frames,
&pad, &pregap, pgtype, pgsub, &postgap);
} else {
/* try to read the v3/v4 metadata tag */
err =
chd_get_metadata(chd->chd, CDROM_TRACK_METADATA2_TAG, chd->num_tracks,
tmp, sizeof(tmp), NULL, NULL, NULL);
if (err == CHDERR_NONE) {
sscanf(tmp, CDROM_TRACK_METADATA2_FORMAT, &tkid, type, subtype, &frames,
&pregap, pgtype, pgsub, &postgap);
} else {
/* try to read the old v3/v4 metadata tag */
err = chd_get_metadata(chd->chd, CDROM_TRACK_METADATA_TAG,
chd->num_tracks, tmp, sizeof(tmp), NULL, NULL,
NULL);
if (err == CHDERR_NONE) {
sscanf(tmp, CDROM_TRACK_METADATA_FORMAT, &tkid, type, subtype,
&frames);
} else {
/* if there's no valid metadata, this is the end of the TOC */
break;
}
}
}
/* sanity checks */
CHECK_EQ(tkid, chd->num_tracks + 1);
if (strcmp(type, "MODE1") && strcmp(type, "MODE1_RAW") &&
strcmp(type, "AUDIO")) {
LOG_WARNING("chd_parse track type %s unsupported", type);
return 0;
}
if (strcmp(subtype, "NONE")) {
LOG_WARNING("chd_parse track subtype %s unsupported", subtype);
return 0;
}
if (pregap != 0 || postgap != 0) {
LOG_WARNING("chd_parse expected zero-length pre and postgap", type);
return 0;
}
/* add track */
CHECK_LT(chd->num_tracks, array_size(chd->tracks));
struct track *track = &chd->tracks[chd->num_tracks++];
track->num = chd->num_tracks;
track->fad = fad;
track->ctrl = strcmp(type, "AUDIO") == 0 ? 0 : 4;
track->sector_fmt = !strcmp(type, "AUDIO") ? GD_SECTOR_CDDA : GD_SECTOR_M1;
track->sector_size = !strcmp(type, "MODE1") ? 2048 : 2352;
switch (track->sector_fmt) {
case GD_SECTOR_CDDA:
track->header_size = 0;
track->error_size = 0;
track->data_size = 2352;
break;
case GD_SECTOR_M1:
track->header_size = 16;
track->error_size = 280;
track->data_size = 2048;
break;
default:
LOG_WARNING("chd_parse unexpected sector format %d", track->sector_fmt);
return 0;
}
track->file_offset = fad - cad;
LOG_INFO("chd_parse '%s' track=%d fad=%d secsz=%d", tmp, track->num,
track->fad, track->sector_size);
/* chd block addresses are padded to a 4-frame boundary */
cad += align_up(frames, 4);
fad += frames;
}
/* gdroms contains two sessions, one for the single density area (tracks 0-1)
and one for the high density area (tracks 3+) */
chd->num_sessions = 2;
/* single density area starts at 00:00:00 (fad 0x0) and can hold up to 4
minutes of data (18,000 sectors at 75 sectors per second) */
struct session *single = &chd->sessions[0];
single->leadin_fad = 0x0;
single->leadout_fad = 0x4650;
single->first_track = 0;
single->last_track = 0;
/* high density area starts at 10:00:00 (fad 0xb05e) and can hold up to
504,300 sectors (112 minutes, 4 seconds at 75 sectors per second) */
struct session *high = &chd->sessions[1];
high->leadin_fad = 0xb05e;
high->leadout_fad = 0x861b4;
high->first_track = 2;
high->last_track = chd->num_tracks - 1;
return 1;
}
struct disc *chd_create(const char *filename) {
struct chd *chd = calloc(1, sizeof(struct chd));
chd->destroy = &chd_destroy;
chd->get_format = &chd_get_format;
chd->get_num_sessions = &chd_get_num_sessions;
chd->get_session = &chd_get_session;
chd->get_num_tracks = &chd_get_num_tracks;
chd->get_track = &chd_get_track;
chd->get_toc = &chd_get_toc;
chd->read_sector = &chd_read_sector;
struct disc *disc = (struct disc *)chd;
if (!chd_parse(disc, filename)) {
chd_destroy(disc);
return NULL;
}
return disc;
}

8
src/guest/gdrom/chd.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef CHD_H
#define CHD_H
struct disc;
struct disc *chd_create(const char *filename);
#endif

View File

@ -2,6 +2,7 @@
#include "core/assert.h"
#include "core/string.h"
#include "guest/gdrom/cdi.h"
#include "guest/gdrom/chd.h"
#include "guest/gdrom/gdi.h"
#include "guest/gdrom/iso.h"
#include "guest/gdrom/patch.h"
@ -199,6 +200,8 @@ struct disc *disc_create(const char *filename) {
if (strstr(filename, ".cdi")) {
disc = cdi_create(filename);
} else if (strstr(filename, ".chd")) {
disc = chd_create(filename);
} else if (strstr(filename, ".gdi")) {
disc = gdi_create(filename);
}