pcsxr/libpcsxcore/cdriso.c

2132 lines
58 KiB
C
Executable File

/***************************************************************************
* Copyright (C) 2007 PCSX-df Team *
* Copyright (C) 2009 Wei Mingzhi *
* Copyright (C) 2012 notaz *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "psxcommon.h"
#include "plugins.h"
#include "cdrom.h"
#include "cdriso.h"
#include "ppf.h"
#include "ecm.h"
#ifdef _WIN32
#include <process.h>
#include <windows.h>
#define strcasecmp _stricmp
#else
#include <sys/time.h>
#include <unistd.h>
#include <limits.h>
#endif
#include <zlib.h>
#ifdef ENABLE_CCDDA
#include "libavcodec/avcodec.h"
#include "libavutil/mathematics.h"
#include <libavutil/opt.h>
#include <libavutil/timestamp.h>
#include "libavformat/avformat.h"
#include <libswresample/swresample.h>
#endif
unsigned int cdrIsoMultidiskCount;
unsigned int cdrIsoMultidiskSelect;
static FILE *cdHandle = NULL;
static FILE *subHandle = NULL;
static boolean subChanMixed = FALSE;
static boolean subChanRaw = FALSE;
static boolean subChanMissing = FALSE;
static boolean multifile = FALSE;
static boolean isMode1ISO = FALSE; // TODO: use sector size/mode info from CUE also?
static unsigned char cdbuffer[CD_FRAMESIZE_RAW];
static unsigned char subbuffer[SUB_FRAMESIZE];
static boolean playing = FALSE;
static boolean cddaBigEndian = FALSE;
static unsigned int cddaCurPos = 0;
/* Frame offset into CD image where pregap data would be found if it was there.
* If a game seeks there we must *not* return subchannel data since it's
* not in the CD image, so that cdrom code can fake subchannel data instead.
* XXX: there could be multiple pregaps but PSX dumps only have one? */
static unsigned int pregapOffset;
// compressed image stuff
static struct {
unsigned char buff_raw[16][CD_FRAMESIZE_RAW];
unsigned char buff_compressed[CD_FRAMESIZE_RAW * 16 + 100];
unsigned int *index_table;
unsigned int index_len;
unsigned int block_shift;
unsigned int current_block;
unsigned int sector_in_blk;
} *compr_img;
int (*cdimg_read_func)(FILE *f, unsigned int base, void *dest, int sector);
char* CALLBACK CDR__getDriveLetter(void);
long CALLBACK CDR__configure(void);
long CALLBACK CDR__test(void);
void CALLBACK CDR__about(void);
long CALLBACK CDR__setfilename(char *filename);
long CALLBACK CDR__getStatus(struct CdrStat *stat);
static void DecodeRawSubData(void);
struct trackinfo {
enum {DATA=1, CDDA} type;
u8 start[3]; // MSF-format
u8 length[3]; // MSF-format
FILE *handle; // for multi-track images CDDA
enum {NONE=0, BIN=1, CCDDA=2
} cddatype; // BIN, WAV, MP3, APE
void* decoded_buffer;
u32 len_decoded_buffer;
char filepath[256];
u32 start_offset; // byte offset from start of above file
};
#define MAXTRACKS 100 /* How many tracks can a CD hold? */
static int numtracks = 0;
static struct trackinfo ti[MAXTRACKS];
// get a sector from a msf-array
unsigned int msf2sec(char *msf) {
return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
}
void sec2msf(unsigned int s, char *msf) {
msf[0] = s / 75 / 60;
s = s - msf[0] * 75 * 60;
msf[1] = s / 75;
s = s - msf[1] * 75;
msf[2] = s;
}
// divide a string of xx:yy:zz into m, s, f
static void tok2msf(char *time, char *msf) {
char *token;
token = strtok(time, ":");
if (token) {
msf[0] = atoi(token);
}
else {
msf[0] = 0;
}
token = strtok(NULL, ":");
if (token) {
msf[1] = atoi(token);
}
else {
msf[1] = 0;
}
token = strtok(NULL, ":");
if (token) {
msf[2] = atoi(token);
}
else {
msf[2] = 0;
}
}
static int get_cdda_type(const char *str)
{
const size_t lenstr = strlen(str);
if (strncmp((str+lenstr-3), "bin", 3) == 0) {
return BIN;
}
#ifdef ENABLE_CCDDA
else {
return CCDDA;
}
#else
else {
static boolean ccddaWarn = TRUE;
if (ccddaWarn) {
SysMessage(_(" -> Compressed CDDA support is not compiled with this version. Such tracks will be silent."));
ccddaWarn = FALSE;
}
}
#endif
return BIN; // no valid extension or no support; assume bin
}
int get_compressed_cdda_track_length(const char* filepath) {
int seconds = -1;
#ifdef ENABLE_CCDDA
av_log_set_level(AV_LOG_QUIET);
av_register_all();
AVFormatContext * inAudioFormat = NULL;
inAudioFormat = avformat_alloc_context();
int errorCode = avformat_open_input(&inAudioFormat, filepath, NULL, NULL);
avformat_find_stream_info(inAudioFormat, NULL);
seconds = (int)ceil((double)inAudioFormat->duration/(double)AV_TIME_BASE);
avformat_close_input(&inAudioFormat);
#endif
return seconds;
}
#ifdef ENABLE_CCDDA
int decode_packet(int *got_frame, AVPacket pkt, int audio_stream_idx, AVFrame* frame, AVCodecContext* audio_dec_ctx, void* buf, int* size, SwrContext* swr) {
int ret = 0;
int decoded = pkt.size;
*got_frame = 0;
if (pkt.stream_index == audio_stream_idx) {
ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
SysPrintf(_("Error decoding audio frame\n"));
return ret;
}
/* Some audio decoders decode only part of the packet, and have to be
* called again with the remainder of the packet data.
* Sample: fate-suite/lossless-audio/luckynight-partial.shn
* Also, some decoders might over-read the packet. */
decoded = FFMIN(ret, pkt.size);
if (*got_frame) {
size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(frame->format);
swr_convert(swr, (uint8_t**)&buf, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples);
(*size)+=(unpadded_linesize*2);
}
}
return decoded;
}
int open_codec_context(int *stream_idx, AVFormatContext *fmt_ctx, enum AVMediaType type) {
int ret, stream_index;
AVStream *st;
AVCodecContext *dec_ctx = NULL;
AVCodec *dec = NULL;
AVDictionary *opts = NULL;
ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
if (ret < 0) {
SysPrintf(_("Could not find %s stream in input file\n"),
av_get_media_type_string(type));
return ret;
} else {
stream_index = ret;
st = fmt_ctx->streams[stream_index];
dec_ctx = st->codec;
dec = avcodec_find_decoder(dec_ctx->codec_id);
if (!dec) {
SysPrintf(_("Failed to find %s codec\n"),
av_get_media_type_string(type));
return AVERROR(EINVAL);
}
/* Init the decoders, with or without reference counting */
if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
SysPrintf(_("Failed to open %s codec\n"),
av_get_media_type_string(type));
return ret;
}
*stream_idx = stream_index;
}
return 0;
}
int decode_compressed_cdda_track(char* buf, char* src_filename, int* size) {
AVFormatContext *fmt_ctx = NULL;
AVCodecContext *audio_dec_ctx;
AVStream *audio_stream = NULL;
int audio_stream_idx = -1;
AVFrame *frame = NULL;
AVPacket pkt;
SwrContext *resample_context;
int ret = 0, got_frame;
av_register_all();
if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
SysPrintf(_("Could not open source file %s\n"), src_filename);
return -1;
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
SysPrintf(_("Could not find stream information\n"));
ret = -1;
goto end;
}
if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
audio_stream = fmt_ctx->streams[audio_stream_idx];
audio_dec_ctx = audio_stream->codec;
}
if (!audio_stream) {
SysPrintf(_("Could not find audio stream in the input, aborting\n"));
ret = -1;
goto end;
}
// init and configure resampler
resample_context = swr_alloc();
if (!resample_context)
{
SysPrintf(_("Could not allocate resample context"));
ret = -1;
goto end;
}
av_opt_set_int(resample_context, "in_channel_layout", audio_dec_ctx->channel_layout, 0);
av_opt_set_int(resample_context, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(resample_context, "in_sample_rate", audio_dec_ctx->sample_rate, 0);
av_opt_set_int(resample_context, "out_sample_rate", 44100, 0);
av_opt_set_sample_fmt(resample_context, "in_sample_fmt", audio_dec_ctx->sample_fmt, 0);
av_opt_set_sample_fmt(resample_context, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
if (swr_init(resample_context) < 0)
{
SysPrintf(_("Could not open resample context"));
ret = -1;
goto end;
}
frame = av_frame_alloc();
if (!frame) {
SysPrintf(_("Could not allocate frame\n"));
ret = AVERROR(ENOMEM);
goto end;
}
/* initialize packet, set data to NULL, let the demuxer fill it */
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
/* read frames from the file */
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
AVPacket orig_pkt = pkt;
do {
ret = decode_packet(&got_frame, pkt, audio_stream_idx, frame, audio_dec_ctx, buf+(*size), size, resample_context);
if (ret < 0)
break;
pkt.data += ret;
pkt.size -= ret;
} while (pkt.size > 0);
av_packet_unref(&orig_pkt);
}
/* flush cached frames */
pkt.data = NULL;
pkt.size = 0;
do {
decode_packet(&got_frame, pkt, audio_stream_idx, frame, audio_dec_ctx, buf+(*size), size, resample_context);
} while (got_frame);
end:
swr_free(&resample_context);
avcodec_close(audio_dec_ctx);
avformat_close_input(&fmt_ctx);
av_frame_free(&frame);
return ret < 0;
}
#endif
int do_decode_cdda(struct trackinfo* tri, u32 tracknumber) {
#ifndef ENABLE_CCDDA
return 0; // support is not compiled in
#else
tri->decoded_buffer = malloc(tri->len_decoded_buffer);
memset(tri->decoded_buffer,0,tri->len_decoded_buffer-1);
if (tri->decoded_buffer == NULL) {
SysMessage(_("Could not allocate memory to decode CDDA TRACK: %s\n"), tri->filepath);
fclose(tri->handle); // encoded file handle not needed anymore
tri->handle = fmemopen(NULL, 1, "rb"); // change handle to decoded one
tri->cddatype = BIN;
return 0;
}
fclose(tri->handle); // encoded file handle not needed anymore
int ret;
SysPrintf(_("Decoding audio tr#%u (%s)..."), tracknumber, tri->filepath);
int len=0;
if ((ret=decode_compressed_cdda_track(tri->decoded_buffer, tri->filepath, &len)) == 0) {
if (len > tri->len_decoded_buffer) {
SysPrintf(_("Buffer overflow..."));
SysPrintf(_("Actual %i vs. %i estimated\n"), len, tri->len_decoded_buffer);
len = tri->len_decoded_buffer; // we probably segfaulted already, oh well...
}
tri->handle = fmemopen(tri->decoded_buffer, len, "rb"); // change handle to decoded one
SysPrintf(_("OK\n"), tri->filepath);
}
tri->cddatype = BIN;
return len;
#endif
}
// this function tries to get the .toc file of the given .bin
// the necessary data is put into the ti (trackinformation)-array
static int parsetoc(const char *isofile) {
char tocname[MAXPATHLEN], filename[MAXPATHLEN], *ptr;
FILE *fi;
char linebuf[256], tmp[256], name[256];
char *token;
char time[20], time2[20];
unsigned int t, sector_offs, sector_size;
unsigned int current_zero_gap = 0;
numtracks = 0;
// copy name of the iso and change extension from .bin to .toc
strncpy(tocname, isofile, sizeof(tocname));
tocname[MAXPATHLEN - 1] = '\0';
if (strlen(tocname) >= 4) {
strcpy(tocname + strlen(tocname) - 4, ".toc");
}
else {
return -1;
}
if ((fi = fopen(tocname, "r")) == NULL) {
// try changing extension to .cue (to satisfy some stupid tutorials)
strcpy(tocname + strlen(tocname) - 4, ".cue");
if ((fi = fopen(tocname, "r")) == NULL) {
// if filename is image.toc.bin, try removing .bin (for Brasero)
strcpy(tocname, isofile);
t = strlen(tocname);
if (t >= 8 && strcmp(tocname + t - 8, ".toc.bin") == 0) {
tocname[t - 4] = '\0';
if ((fi = fopen(tocname, "r")) == NULL) {
return -1;
}
}
else {
return -1;
}
}
}
strcpy(filename, tocname);
if ((ptr = strrchr(filename, '/')) == NULL)
ptr = strrchr(filename, '\\');
if (ptr == NULL)
*ptr = 0;
else
*(ptr + 1) = 0;
memset(&ti, 0, sizeof(ti));
cddaBigEndian = TRUE; // cdrdao uses big-endian for CD Audio
sector_size = CD_FRAMESIZE_RAW;
sector_offs = 2 * 75;
// parse the .toc file
while (fgets(linebuf, sizeof(linebuf), fi) != NULL) {
// search for tracks
strncpy(tmp, linebuf, sizeof(linebuf));
token = strtok(tmp, " ");
if (token == NULL) continue;
if (!strcmp(token, "TRACK")) {
sector_offs += current_zero_gap;
current_zero_gap = 0;
// get type of track
token = strtok(NULL, " ");
numtracks++;
if (!strncmp(token, "MODE2_RAW", 9)) {
ti[numtracks].type = DATA;
sec2msf(2 * 75, ti[numtracks].start); // assume data track on 0:2:0
// check if this image contains mixed subchannel data
token = strtok(NULL, " ");
if (token != NULL && !strncmp(token, "RW", 2)) {
sector_size = CD_FRAMESIZE_RAW + SUB_FRAMESIZE;
subChanMixed = TRUE;
if (!strncmp(token, "RW_RAW", 6))
subChanRaw = TRUE;
}
}
else if (!strncmp(token, "AUDIO", 5)) {
ti[numtracks].type = CDDA;
}
}
else if (!strcmp(token, "DATAFILE")) {
if (ti[numtracks].type == CDDA) {
sscanf(linebuf, "DATAFILE \"%[^\"]\" #%d %8s", name, &t, time2);
ti[numtracks].start_offset = t;
t = t / sector_size + sector_offs;
sec2msf(t, (char *)&ti[numtracks].start);
tok2msf((char *)&time2, (char *)&ti[numtracks].length);
}
else {
sscanf(linebuf, "DATAFILE \"%[^\"]\" %8s", name, time);
tok2msf((char *)&time, (char *)&ti[numtracks].length);
strcat(filename, name);
ti[numtracks].handle = fopen(filename, "rb");
}
}
else if (!strcmp(token, "FILE")) {
sscanf(linebuf, "FILE \"%[^\"]\" #%d %8s %8s", name, &t, time, time2);
tok2msf((char *)&time, (char *)&ti[numtracks].start);
t += msf2sec(ti[numtracks].start) * sector_size;
ti[numtracks].start_offset = t;
t = t / sector_size + sector_offs;
sec2msf(t, (char *)&ti[numtracks].start);
tok2msf((char *)&time2, (char *)&ti[numtracks].length);
}
else if (!strcmp(token, "ZERO") || !strcmp(token, "SILENCE")) {
// skip unneeded optional fields
while (token != NULL) {
token = strtok(NULL, " ");
if (strchr(token, ':') != NULL)
break;
}
if (token != NULL) {
tok2msf(token, tmp);
current_zero_gap = msf2sec(tmp);
}
if (numtracks > 1) {
t = ti[numtracks - 1].start_offset;
t /= sector_size;
pregapOffset = t + msf2sec(ti[numtracks - 1].length);
}
}
else if (!strcmp(token, "START")) {
token = strtok(NULL, " ");
if (token != NULL && strchr(token, ':')) {
tok2msf(token, tmp);
t = msf2sec(tmp);
ti[numtracks].start_offset += (t - current_zero_gap) * sector_size;
t = msf2sec(ti[numtracks].start) + t;
sec2msf(t, (char *)&ti[numtracks].start);
}
}
}
if (numtracks > 0)
cdHandle = fopen(filename, "rb");
fclose(fi);
return 0;
}
int handlearchive(const char *isoname, s32* accurate_length);
// this function tries to get the .cue file of the given .bin
// the necessary data is put into the ti (trackinformation)-array
static int parsecue(const char *isofile) {
char cuename[MAXPATHLEN];
char filepath[MAXPATHLEN];
char *incue_fname;
FILE *fi;
char *token;
char time[20];
char *tmp;
char linebuf[256], tmpb[256], dummy[256];
unsigned int incue_max_len;
unsigned int t, file_len, mode, sector_offs;
unsigned int sector_size = 2352;
numtracks = 0;
// copy name of the iso and change extension from .bin to .cue
strncpy(cuename, isofile, sizeof(cuename));
cuename[MAXPATHLEN - 1] = '\0';
if (strlen(cuename) >= 4) {
strcpy(cuename + strlen(cuename) - 4, ".cue");
}
else {
return -1;
}
if ((fi = fopen(cuename, "r")) == NULL) {
return -1;
}
// Some stupid tutorials wrongly tell users to use cdrdao to rip a
// "bin/cue" image, which is in fact a "bin/toc" image. So let's check
// that...
if (fgets(linebuf, sizeof(linebuf), fi) != NULL) {
if (!strncmp(linebuf, "CD_ROM_XA", 9)) {
// Don't proceed further, as this is actually a .toc file rather
// than a .cue file.
fclose(fi);
return parsetoc(isofile);
}
fseek(fi, 0, SEEK_SET);
}
// build a path for files referenced in .cue
strncpy(filepath, cuename, sizeof(filepath));
tmp = strrchr(filepath, '/');
if (tmp == NULL)
tmp = strrchr(filepath, '\\');
if (tmp != NULL)
tmp++;
else
tmp = filepath;
*tmp = 0;
filepath[sizeof(filepath) - 1] = 0;
incue_fname = tmp;
incue_max_len = sizeof(filepath) - (tmp - filepath) - 1;
memset(&ti, 0, sizeof(ti));
file_len = 0;
sector_offs = 2 * 75;
while (fgets(linebuf, sizeof(linebuf), fi) != NULL) {
strncpy(dummy, linebuf, sizeof(linebuf));
token = strtok(dummy, " ");
if (token == NULL) {
continue;
}
if (!strcmp(token, "TRACK")) {
numtracks++;
sector_size = 0;
if (strstr(linebuf, "AUDIO") != NULL) {
ti[numtracks].type = CDDA;
sector_size = CD_FRAMESIZE_RAW;
// Check if extension is mp3, etc, for compressed audio formats
if (multifile && (ti[numtracks].cddatype = get_cdda_type(filepath)) > BIN) {
int seconds = get_compressed_cdda_track_length(filepath) + 0;
const boolean lazy_decode = TRUE; // TODO: config param
// TODO: get frame length for compressed audio as well
ti[numtracks].len_decoded_buffer = 44100 * (16/8) * 2 * seconds;
strcpy(ti[numtracks].filepath, filepath);
file_len = ti[numtracks].len_decoded_buffer/CD_FRAMESIZE_RAW;
// Send to decoder if not lazy decoding
if (!lazy_decode) {
SysPrintf("\n");
file_len = do_decode_cdda(&(ti[numtracks]), numtracks) / CD_FRAMESIZE_RAW;
}
}
}
else if (sscanf(linebuf, " TRACK %u MODE%u/%u", &t, &mode, &sector_size) == 3) {
s32 accurate_len;
// TODO: if 2048 frame length -> recalculate file_len?
ti[numtracks].type = DATA;
// detect if ECM or compressed & get accurate length
if (handleecm(filepath, cdHandle, &accurate_len) == 0 ||
handlearchive(filepath, &accurate_len) == 0) {
file_len = accurate_len;
}
} else {
SysPrintf(".cue: failed to parse TRACK\n");
ti[numtracks].type = numtracks == 1 ? DATA : CDDA;
}
if (sector_size == 0) // TODO isMode1ISO?
sector_size = CD_FRAMESIZE_RAW;
}
else if (!strcmp(token, "INDEX")) {
if (sscanf(linebuf, " INDEX %02d %8s", &t, time) != 2)
SysPrintf(".cue: failed to parse INDEX\n");
tok2msf(time, (char *)&ti[numtracks].start);
t = msf2sec(ti[numtracks].start);
ti[numtracks].start_offset = t * sector_size;
t += sector_offs;
sec2msf(t, ti[numtracks].start);
// default track length to file length
t = file_len - ti[numtracks].start_offset / sector_size;
sec2msf(t, ti[numtracks].length);
if (numtracks > 1 && ti[numtracks].handle == NULL) {
// this track uses the same file as the last,
// start of this track is last track's end
t = msf2sec(ti[numtracks].start) - msf2sec(ti[numtracks - 1].start);
sec2msf(t, ti[numtracks - 1].length);
}
if (numtracks > 1 && pregapOffset == -1)
pregapOffset = ti[numtracks].start_offset / sector_size;
}
else if (!strcmp(token, "PREGAP")) {
if (sscanf(linebuf, " PREGAP %8s", time) == 1) {
tok2msf(time, dummy);
sector_offs += msf2sec(dummy);
}
pregapOffset = -1; // mark to fill track start_offset
}
else if (!strcmp(token, "FILE")) {
t = sscanf(linebuf, " FILE \"%255[^\"]\"", tmpb);
if (t != 1)
sscanf(linebuf, " FILE %255s", tmpb);
// absolute path?
ti[numtracks + 1].handle = fopen(tmpb, "rb");
if (ti[numtracks + 1].handle == NULL) {
// relative to .cue?
tmp = strrchr(tmpb, '\\');
if (tmp == NULL)
tmp = strrchr(tmpb, '/');
if (tmp != NULL)
tmp++;
else
tmp = tmpb;
strncpy(incue_fname, tmp, incue_max_len);
ti[numtracks + 1].handle = fopen(filepath, "rb");
}
// update global offset if this is not first file in this .cue
if (numtracks + 1 > 1) {
multifile = TRUE;
sector_offs += file_len;
}
file_len = 0;
if (ti[numtracks + 1].handle == NULL) {
SysMessage(_("\ncould not open: %s\n"), filepath);
continue;
}
// File length, compressed audio length will be calculated in AUDIO tag
fseek(ti[numtracks + 1].handle, 0, SEEK_END);
file_len = ftell(ti[numtracks + 1].handle) / CD_FRAMESIZE_RAW;
if (numtracks == 0 && strlen(isofile) >= 4 &&
strcmp(isofile + strlen(isofile) - 4, ".cue") == 0)
{
// user selected .cue as image file, use its data track instead
fclose(cdHandle);
cdHandle = fopen(filepath, "rb");
}
}
}
fclose(fi);
return 0;
}
// this function tries to get the .ccd file of the given .img
// the necessary data is put into the ti (trackinformation)-array
static int parseccd(const char *isofile) {
char ccdname[MAXPATHLEN];
FILE *fi;
char linebuf[256];
unsigned int t;
numtracks = 0;
// copy name of the iso and change extension from .img to .ccd
strncpy(ccdname, isofile, sizeof(ccdname));
ccdname[MAXPATHLEN - 1] = '\0';
if (strlen(ccdname) >= 4) {
strcpy(ccdname + strlen(ccdname) - 4, ".ccd");
}
else {
return -1;
}
if ((fi = fopen(ccdname, "r")) == NULL) {
return -1;
}
memset(&ti, 0, sizeof(ti));
while (fgets(linebuf, sizeof(linebuf), fi) != NULL) {
if (!strncmp(linebuf, "[TRACK", 6)){
numtracks++;
}
else if (!strncmp(linebuf, "MODE=", 5)) {
sscanf(linebuf, "MODE=%d", &t);
ti[numtracks].type = ((t == 0) ? CDDA : DATA);
}
else if (!strncmp(linebuf, "INDEX 1=", 8)) {
sscanf(linebuf, "INDEX 1=%d", &t);
sec2msf(t + 2 * 75, ti[numtracks].start);
ti[numtracks].start_offset = t * 2352;
// If we've already seen another track, this is its end
if (numtracks > 1) {
t = msf2sec(ti[numtracks].start) - msf2sec(ti[numtracks - 1].start);
sec2msf(t, ti[numtracks - 1].length);
}
}
}
fclose(fi);
// Fill out the last track's end based on size
if (numtracks >= 1) {
fseek(cdHandle, 0, SEEK_END);
t = ftell(cdHandle) / CD_FRAMESIZE_RAW - msf2sec(ti[numtracks].start) + 2 * 75;
sec2msf(t, ti[numtracks].length);
}
return 0;
}
// this function tries to get the .mds file of the given .mdf
// the necessary data is put into the ti (trackinformation)-array
static int parsemds(const char *isofile) {
char mdsname[MAXPATHLEN];
FILE *fi;
unsigned int offset, extra_offset, l, i;
unsigned short s;
numtracks = 0;
// copy name of the iso and change extension from .mdf to .mds
strncpy(mdsname, isofile, sizeof(mdsname));
mdsname[MAXPATHLEN - 1] = '\0';
if (strlen(mdsname) >= 4) {
strcpy(mdsname + strlen(mdsname) - 4, ".mds");
}
else {
return -1;
}
if ((fi = fopen(mdsname, "rb")) == NULL) {
return -1;
}
memset(&ti, 0, sizeof(ti));
// check if it's a valid mds file
fread(&i, 1, sizeof(unsigned int), fi);
i = SWAP32(i);
if (i != 0x4944454D) {
// not an valid mds file
fclose(fi);
return -1;
}
// get offset to session block
fseek(fi, 0x50, SEEK_SET);
fread(&offset, 1, sizeof(unsigned int), fi);
offset = SWAP32(offset);
// get total number of tracks
offset += 14;
fseek(fi, offset, SEEK_SET);
fread(&s, 1, sizeof(unsigned short), fi);
s = SWAP16(s);
numtracks = s;
// get offset to track blocks
fseek(fi, 4, SEEK_CUR);
fread(&offset, 1, sizeof(unsigned int), fi);
offset = SWAP32(offset);
// skip lead-in data
while (1) {
fseek(fi, offset + 4, SEEK_SET);
if (fgetc(fi) < 0xA0) {
break;
}
offset += 0x50;
}
// check if the image contains mixed subchannel data
fseek(fi, offset + 1, SEEK_SET);
subChanMixed = subChanRaw = (fgetc(fi) ? TRUE : FALSE);
// read track data
for (i = 1; i <= numtracks; i++) {
fseek(fi, offset, SEEK_SET);
// get the track type
ti[i].type = ((fgetc(fi) == 0xA9) ? CDDA : DATA);
fseek(fi, 8, SEEK_CUR);
// get the track starting point
ti[i].start[0] = fgetc(fi);
ti[i].start[1] = fgetc(fi);
ti[i].start[2] = fgetc(fi);
fread(&extra_offset, 1, sizeof(unsigned int), fi);
extra_offset = SWAP32(extra_offset);
// get track start offset (in .mdf)
fseek(fi, offset + 0x28, SEEK_SET);
fread(&l, 1, sizeof(unsigned int), fi);
l = SWAP32(l);
ti[i].start_offset = l;
// get pregap
fseek(fi, extra_offset, SEEK_SET);
fread(&l, 1, sizeof(unsigned int), fi);
l = SWAP32(l);
if (l != 0 && i > 1)
pregapOffset = msf2sec(ti[i].start);
// get the track length
fread(&l, 1, sizeof(unsigned int), fi);
l = SWAP32(l);
sec2msf(l, ti[i].length);
offset += 0x50;
}
fclose(fi);
return 0;
}
static int handlepbp(const char *isofile) {
struct {
unsigned int sig;
unsigned int dontcare[8];
unsigned int psar_offs;
} pbp_hdr;
struct {
unsigned char type;
unsigned char pad0;
unsigned char track;
char index0[3];
char pad1;
char index1[3];
} toc_entry;
struct {
unsigned int offset;
unsigned int size;
unsigned int dontcare[6];
} index_entry;
char psar_sig[11];
unsigned int t, cd_length, cdimg_base;
unsigned int offsettab[8], psisoimg_offs;
const char *ext = NULL;
int i, ret;
if (strlen(isofile) >= 4)
ext = isofile + strlen(isofile) - 4;
if (ext == NULL || (strcmp(ext, ".pbp") != 0 && strcmp(ext, ".PBP") != 0))
return -1;
fseek(cdHandle, 0, SEEK_SET);
numtracks = 0;
ret = fread(&pbp_hdr, 1, sizeof(pbp_hdr), cdHandle);
if (ret != sizeof(pbp_hdr)) {
SysPrintf("failed to read pbp\n");
goto fail_io;
}
ret = fseek(cdHandle, pbp_hdr.psar_offs, SEEK_SET);
if (ret != 0) {
SysPrintf("failed to seek to %x\n", pbp_hdr.psar_offs);
goto fail_io;
}
psisoimg_offs = pbp_hdr.psar_offs;
fread(psar_sig, 1, sizeof(psar_sig), cdHandle);
psar_sig[10] = 0;
if (strcmp(psar_sig, "PSTITLEIMG") == 0) {
// multidisk image?
ret = fseek(cdHandle, pbp_hdr.psar_offs + 0x200, SEEK_SET);
if (ret != 0) {
SysPrintf("failed to seek to %x\n", pbp_hdr.psar_offs + 0x200);
goto fail_io;
}
if (fread(&offsettab, 1, sizeof(offsettab), cdHandle) != sizeof(offsettab)) {
SysPrintf("failed to read offsettab\n");
goto fail_io;
}
for (i = 0; i < sizeof(offsettab) / sizeof(offsettab[0]); i++) {
if (offsettab[i] == 0)
break;
}
cdrIsoMultidiskCount = i;
if (cdrIsoMultidiskCount == 0) {
SysPrintf("multidisk eboot has 0 images?\n");
goto fail_io;
}
if (cdrIsoMultidiskSelect >= cdrIsoMultidiskCount)
cdrIsoMultidiskSelect = 0;
psisoimg_offs += offsettab[cdrIsoMultidiskSelect];
ret = fseek(cdHandle, psisoimg_offs, SEEK_SET);
if (ret != 0) {
SysPrintf("failed to seek to %x\n", psisoimg_offs);
goto fail_io;
}
fread(psar_sig, 1, sizeof(psar_sig), cdHandle);
psar_sig[10] = 0;
}
if (strcmp(psar_sig, "PSISOIMG00") != 0) {
SysPrintf("bad psar_sig: %s\n", psar_sig);
goto fail_io;
}
// seek to TOC
ret = fseek(cdHandle, psisoimg_offs + 0x800, SEEK_SET);
if (ret != 0) {
SysPrintf("failed to seek to %x\n", psisoimg_offs + 0x800);
goto fail_io;
}
// first 3 entries are special
fseek(cdHandle, sizeof(toc_entry), SEEK_CUR);
fread(&toc_entry, 1, sizeof(toc_entry), cdHandle);
numtracks = btoi(toc_entry.index1[0]);
fread(&toc_entry, 1, sizeof(toc_entry), cdHandle);
cd_length = btoi(toc_entry.index1[0]) * 60 * 75 +
btoi(toc_entry.index1[1]) * 75 + btoi(toc_entry.index1[2]);
for (i = 1; i <= numtracks; i++) {
fread(&toc_entry, 1, sizeof(toc_entry), cdHandle);
ti[i].type = (toc_entry.type == 1) ? CDDA : DATA;
ti[i].start_offset = btoi(toc_entry.index0[0]) * 60 * 75 +
btoi(toc_entry.index0[1]) * 75 + btoi(toc_entry.index0[2]);
ti[i].start_offset *= 2352;
ti[i].start[0] = btoi(toc_entry.index1[0]);
ti[i].start[1] = btoi(toc_entry.index1[1]);
ti[i].start[2] = btoi(toc_entry.index1[2]);
if (i > 1) {
t = msf2sec(ti[i].start) - msf2sec(ti[i - 1].start);
sec2msf(t, ti[i - 1].length);
}
}
t = cd_length - ti[numtracks].start_offset / 2352;
sec2msf(t, ti[numtracks].length);
// seek to ISO index
ret = fseek(cdHandle, psisoimg_offs + 0x4000, SEEK_SET);
if (ret != 0) {
SysPrintf("failed to seek to ISO index\n");
goto fail_io;
}
compr_img = calloc(1, sizeof(*compr_img));
if (compr_img == NULL)
goto fail_io;
compr_img->block_shift = 4;
compr_img->current_block = (unsigned int)-1;
compr_img->index_len = (0x100000 - 0x4000) / sizeof(index_entry);
compr_img->index_table = malloc((compr_img->index_len + 1) * sizeof(compr_img->index_table[0]));
if (compr_img->index_table == NULL)
goto fail_io;
cdimg_base = psisoimg_offs + 0x100000;
for (i = 0; i < compr_img->index_len; i++) {
ret = fread(&index_entry, 1, sizeof(index_entry), cdHandle);
if (ret != sizeof(index_entry)) {
SysPrintf("failed to read index_entry #%d\n", i);
goto fail_index;
}
if (index_entry.size == 0)
break;
compr_img->index_table[i] = cdimg_base + index_entry.offset;
}
compr_img->index_table[i] = cdimg_base + index_entry.offset + index_entry.size;
return 0;
fail_index:
free(compr_img->index_table);
compr_img->index_table = NULL;
fail_io:
if (compr_img != NULL) {
free(compr_img);
compr_img = NULL;
}
return -1;
}
static int handlecbin(const char *isofile) {
struct
{
char magic[4];
unsigned int header_size;
unsigned long long total_bytes;
unsigned int block_size;
unsigned char ver; // 1
unsigned char align;
unsigned char rsv_06[2];
} ciso_hdr;
const char *ext = NULL;
unsigned int index = 0, plain;
int i, ret;
if (strlen(isofile) >= 5)
ext = isofile + strlen(isofile) - 5;
if (ext == NULL || (strcasecmp(ext + 1, ".cbn") != 0 && strcasecmp(ext, ".cbin") != 0))
return -1;
fseek(cdHandle, 0, SEEK_SET);
ret = fread(&ciso_hdr, 1, sizeof(ciso_hdr), cdHandle);
if (ret != sizeof(ciso_hdr)) {
SysPrintf("failed to read ciso header\n");
return -1;
}
if (strncmp(ciso_hdr.magic, "CISO", 4) != 0 || ciso_hdr.total_bytes <= 0 || ciso_hdr.block_size <= 0) {
SysPrintf("bad ciso header\n");
return -1;
}
if (ciso_hdr.header_size != 0 && ciso_hdr.header_size != sizeof(ciso_hdr)) {
ret = fseek(cdHandle, ciso_hdr.header_size, SEEK_SET);
if (ret != 0) {
SysPrintf("failed to seek to %x\n", ciso_hdr.header_size);
return -1;
}
}
compr_img = calloc(1, sizeof(*compr_img));
if (compr_img == NULL)
goto fail_io;
compr_img->block_shift = 0;
compr_img->current_block = (unsigned int)-1;
compr_img->index_len = ciso_hdr.total_bytes / ciso_hdr.block_size;
compr_img->index_table = malloc((compr_img->index_len + 1) * sizeof(compr_img->index_table[0]));
if (compr_img->index_table == NULL)
goto fail_io;
ret = fread(compr_img->index_table, sizeof(compr_img->index_table[0]), compr_img->index_len, cdHandle);
if (ret != compr_img->index_len) {
SysPrintf("failed to read index table\n");
goto fail_index;
}
for (i = 0; i < compr_img->index_len + 1; i++) {
index = compr_img->index_table[i];
plain = index & 0x80000000;
index &= 0x7fffffff;
compr_img->index_table[i] = (index << ciso_hdr.align) | plain;
}
if ((long long)index << ciso_hdr.align >= 0x80000000ll)
SysPrintf("warning: ciso img too large, expect problems\n");
return 0;
fail_index:
free(compr_img->index_table);
compr_img->index_table = NULL;
fail_io:
if (compr_img != NULL) {
free(compr_img);
compr_img = NULL;
}
return -1;
}
// this function tries to get the .sub file of the given .img
static int opensubfile(const char *isoname) {
char subname[MAXPATHLEN];
// copy name of the iso and change extension from .img to .sub
strncpy(subname, isoname, sizeof(subname));
subname[MAXPATHLEN - 1] = '\0';
if (strlen(subname) >= 4) {
strcpy(subname + strlen(subname) - 4, ".sub");
}
subHandle = fopen(subname, "rb");
if (subHandle != NULL) {
return 0;
}
if (strlen(subname) >= 8) {
strcpy(subname + strlen(subname) - 8, ".sub");
}
subHandle = fopen(subname, "rb");
if (subHandle == NULL) {
return -1;
}
return 0;
}
static int opensbifile(const char *isoname) {
char sbiname[MAXPATHLEN];
strncpy(sbiname, isoname, sizeof(sbiname));
sbiname[MAXPATHLEN - 1] = '\0';
if (strlen(sbiname) >= 4) {
strcpy(sbiname + strlen(sbiname) - 4, ".sbi");
}
else {
return -1;
}
return LoadSBI(sbiname);
}
static int cdread_normal(FILE *f, unsigned int base, void *dest, int sector)
{
fseek(f, base + sector * CD_FRAMESIZE_RAW, SEEK_SET);
return fread(dest, 1, CD_FRAMESIZE_RAW, f);
}
static int cdread_sub_mixed(FILE *f, unsigned int base, void *dest, int sector)
{
int ret;
fseek(f, base + sector * (CD_FRAMESIZE_RAW + SUB_FRAMESIZE), SEEK_SET);
ret = fread(dest, 1, CD_FRAMESIZE_RAW, f);
fread(subbuffer, 1, SUB_FRAMESIZE, f);
if (subChanRaw) DecodeRawSubData();
return ret;
}
static int uncompress2_internal(void *out, unsigned long *out_size, void *in, unsigned long in_size)
{
static z_stream z;
int ret = 0;
if (z.zalloc == NULL) {
// XXX: one-time leak here..
z.next_in = Z_NULL;
z.avail_in = 0;
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
ret = inflateInit2(&z, -15);
}
else
ret = inflateReset(&z);
if (ret != Z_OK)
return ret;
z.next_in = in;
z.avail_in = in_size;
z.next_out = out;
z.avail_out = *out_size;
ret = inflate(&z, Z_NO_FLUSH);
//inflateEnd(&z);
*out_size -= z.avail_out;
return ret == 1 ? 0 : ret;
}
static int cdread_compressed(FILE *f, unsigned int base, void *dest, int sector)
{
unsigned long cdbuffer_size, cdbuffer_size_expect;
unsigned int start_byte, size;
int is_compressed;
int ret, block;
if (base)
sector += base / 2352;
block = sector >> compr_img->block_shift;
compr_img->sector_in_blk = sector & ((1 << compr_img->block_shift) - 1);
if (block == compr_img->current_block) {
//printf("hit sect %d\n", sector);
goto finish;
}
if (sector >= compr_img->index_len * 16) {
SysPrintf("sector %d is past img end\n", sector);
return -1;
}
start_byte = compr_img->index_table[block] & 0x7fffffff;
if (fseek(cdHandle, start_byte, SEEK_SET) != 0) {
SysPrintf("seek error for block %d at %x: ",
block, start_byte);
perror(NULL);
return -1;
}
is_compressed = !(compr_img->index_table[block] & 0x80000000);
size = (compr_img->index_table[block + 1] & 0x7fffffff) - start_byte;
if (size > sizeof(compr_img->buff_compressed)) {
SysPrintf("block %d is too large: %u\n", block, size);
return -1;
}
if (fread(is_compressed ? compr_img->buff_compressed : compr_img->buff_raw[0],
1, size, cdHandle) != size) {
SysPrintf("read error for block %d at %x: ", block, start_byte);
perror(NULL);
return -1;
}
if (is_compressed) {
cdbuffer_size_expect = sizeof(compr_img->buff_raw[0]) << compr_img->block_shift;
cdbuffer_size = cdbuffer_size_expect;
ret = uncompress2_internal(compr_img->buff_raw[0], &cdbuffer_size, compr_img->buff_compressed, size);
if (ret != 0) {
SysPrintf("uncompress failed with %d for block %d, sector %d\n",
ret, block, sector);
return -1;
}
if (cdbuffer_size != cdbuffer_size_expect)
SysPrintf("cdbuffer_size: %lu != %lu, sector %d\n", cdbuffer_size,
cdbuffer_size_expect, sector);
}
// done at last!
compr_img->current_block = block;
finish:
if (dest != cdbuffer) // copy avoid HACK
memcpy(dest, compr_img->buff_raw[compr_img->sector_in_blk],
CD_FRAMESIZE_RAW);
return CD_FRAMESIZE_RAW;
}
static int cdread_2048(FILE *f, unsigned int base, void *dest, int sector)
{
int ret;
fseek(f, base + sector * 2048, SEEK_SET);
ret = fread((char *)dest + 12 * 2, 1, 2048, f);
// not really necessary, fake mode 2 header
memset(cdbuffer, 0, 12 * 2);
sec2msf(sector + 2 * 75, (char *)&cdbuffer[12]);
cdbuffer[12 + 3] = 1;
return ret;
}
/* Adapted from ecm.c:unecmify() (C) Neill Corlett */
//TODO: move this func to ecm.h
static int cdread_ecm_decode(FILE *f, unsigned int base, void *dest, int sector) {
u32 output_edc=0, b=0, writebytecount=0, num;
s32 sectorcount=0;
s8 type = 0; // mode type 0 (META) or 1, 2 or 3 for CDROM type
u8 sector_buffer[CD_FRAMESIZE_RAW];
boolean processsectors = (boolean)decoded_ecm_sectors; // this flag tells if to decode all sectors or just skip to wanted sector
ECMFILELUT* pos = &(ecm_savetable[0]); // points always to beginning of ECM DATA
// If not pointing to ECM file but CDDA file or some other track
if(f != cdHandle) {
//printf("BASETR %i %i\n", base, sector);
return cdimg_read_func_o(f, base, dest, sector);
}
// When sector exists in decoded ECM file buffer
else if (decoded_ecm_sectors && sector < decoded_ecm_sectors) {
//printf("ReadSector %i %i\n", sector, savedsectors);
return cdimg_read_func_o(decoded_ecm, base, dest, sector);
}
// To prevent invalid seek
/* else if (sector > len_ecm_savetable) {
SysPrintf("ECM: invalid sector requested\n");
return -1;
}*/
//printf("SeekSector %i %i %i %i\n", sector, pos->sector, prevsector, base);
if (sector <= len_ecm_savetable) {
// get sector from LUT which points to wanted sector or close to
// TODO: What would be optimal maximum to search near sector?
// Might cause slowdown if too small but too big also..
for (sectorcount = sector; ((sectorcount > 0) && ((sector-sectorcount) <= 50000)); sectorcount--) {
if (ecm_savetable[sectorcount].filepos >= ECM_HEADER_SIZE) {
pos = &(ecm_savetable[sectorcount]);
//printf("LUTSector %i %i %i %i\n", sector, pos->sector, prevsector, base);
break;
}
}
// if suitable sector was not found from LUT use last sector if less than wanted sector
if (pos->filepos <= ECM_HEADER_SIZE && sector > prevsector) pos=&(ecm_savetable[prevsector]);
}
writebytecount = pos->sector * CD_FRAMESIZE_RAW;
sectorcount = pos->sector;
if (decoded_ecm_sectors) fseek(decoded_ecm, writebytecount, SEEK_SET); // rewind to last pos
fseek(f, /*base+*/pos->filepos, SEEK_SET);
while(sector >= sectorcount) { // decode ecm file until we are past wanted sector
int c = fgetc(f);
int bits = 5;
if(c == EOF) { goto error_in; }
type = c & 3;
num = (c >> 2) & 0x1F;
//printf("ECM1 file; count %x\n", c);
while(c & 0x80) {
c = fgetc(f);
//printf("ECM2 file; count %x\n", c);
if(c == EOF) { goto error_in; }
if( (bits > 31) ||
((uint32_t)(c & 0x7F)) >= (((uint32_t)0x80000000LU) >> (bits-1))
) {
//SysMessage(_("Corrupt ECM file; invalid sector count\n"));
goto error;
}
num |= ((uint32_t)(c & 0x7F)) << bits;
bits += 7;
}
if(num == 0xFFFFFFFF) {
// End indicator
len_decoded_ecm_buffer = writebytecount;
len_ecm_savetable = len_decoded_ecm_buffer/CD_FRAMESIZE_RAW;
break;
}
num++;
while(num) {
if (!processsectors && sectorcount >= (sector-1)) { // ensure that we read the sector we are supposed to
processsectors = TRUE;
//printf("Saving at %i\n", sectorcount);
} else if (processsectors && sectorcount > sector) {
//printf("Terminating at %i\n", sectorcount);
break;
}
/*printf("Type %i Num %i SeekSector %i ProcessedSectors %i(%i) Bytecount %i Pos %li Write %u\n",
type, num, sector, sectorcount, pos->sector, writebytecount, ftell(f), processsectors);*/
switch(type) {
case 0: // META
b = num;
if(b > sizeof(sector_buffer)) { b = sizeof(sector_buffer); }
writebytecount += b;
if (!processsectors) { fseek(f, +b, SEEK_CUR); break; } // seek only
if(fread(sector_buffer, 1, b, f) != b) {
goto error_in;
}
//output_edc = edc_compute(output_edc, sector_buffer, b);
if(decoded_ecm_sectors && fwrite(sector_buffer, 1, b, decoded_ecm) != b) { // just seek or write also
goto error_out;
}
break;
case 1: //Mode 1
b=1;
writebytecount += ECM_SECTOR_SIZE[type];
if(fread(sector_buffer + 0x00C, 1, 0x003, f) != 0x003) { goto error_in; }
if(fread(sector_buffer + 0x010, 1, 0x800, f) != 0x800) { goto error_in; }
if (!processsectors) break; // seek only
reconstruct_sector(sector_buffer, type);
//output_edc = edc_compute(output_edc, sector_buffer, ECM_SECTOR_SIZE[type]);
if(decoded_ecm_sectors && fwrite(sector_buffer, 1, ECM_SECTOR_SIZE[type], decoded_ecm) != ECM_SECTOR_SIZE[type]) { goto error_out; }
break;
case 2: //Mode 2 (XA), form 1
b=1;
writebytecount += ECM_SECTOR_SIZE[type];
if (!processsectors) { fseek(f, +0x804, SEEK_CUR); break; } // seek only
if(fread(sector_buffer + 0x014, 1, 0x804, f) != 0x804) { goto error_in; }
reconstruct_sector(sector_buffer, type);
//output_edc = edc_compute(output_edc, sector_buffer + 0x10, ECM_SECTOR_SIZE[type]);
if(decoded_ecm_sectors && fwrite(sector_buffer + 0x10, 1, ECM_SECTOR_SIZE[type], decoded_ecm) != ECM_SECTOR_SIZE[type]) { goto error_out; }
break;
case 3: //Mode 2 (XA), form 2
b=1;
writebytecount += ECM_SECTOR_SIZE[type];
if (!processsectors) { fseek(f, +0x918, SEEK_CUR); break; } // seek only
if(fread(sector_buffer + 0x014, 1, 0x918, f) != 0x918) { goto error_in; }
reconstruct_sector(sector_buffer, type);
//output_edc = edc_compute(output_edc, sector_buffer + 0x10, ECM_SECTOR_SIZE[type]);
if(decoded_ecm_sectors && fwrite(sector_buffer + 0x10, 1, ECM_SECTOR_SIZE[type], decoded_ecm) != ECM_SECTOR_SIZE[type]) { goto error_out; }
break;
}
sectorcount=((writebytecount/CD_FRAMESIZE_RAW) - 0);
num -= b;
}
if (type && sectorcount > 0 && ecm_savetable[sectorcount].filepos <= ECM_HEADER_SIZE ) {
ecm_savetable[sectorcount].filepos = ftell(f)/*-base*/;
ecm_savetable[sectorcount].sector = sectorcount;
//printf("Marked %i at pos %i\n", ecm_savetable[sectorcount].sector, ecm_savetable[sectorcount].filepos);
}
}
if (decoded_ecm_sectors) {
fflush(decoded_ecm);
fseek(decoded_ecm, -1*CD_FRAMESIZE_RAW, SEEK_CUR);
num = fread(sector_buffer, 1, CD_FRAMESIZE_RAW, decoded_ecm);
decoded_ecm_sectors = MAX(decoded_ecm_sectors, sectorcount);
} else {
num = CD_FRAMESIZE_RAW;
}
memcpy(dest, sector_buffer, CD_FRAMESIZE_RAW);
prevsector = sectorcount;
//printf("OK: Frame decoded %i %i\n", sectorcount-1, writebytecount);
return num;
error_in:
error:
error_out:
//memset(dest, 0x0, CD_FRAMESIZE_RAW);
SysPrintf("Error decoding ECM image: WantedSector %i Type %i Base %i Sectors %i(%i) Pos %i(%li)\n",
sector, type, base, sectorcount, pos->sector, writebytecount, ftell(f));
return -1;
}
int handleecm(const char *isoname, FILE* cdh, s32* accurate_length) {
// Rewind to start and check ECM header and filename suffix validity
fseek(cdh, 0, SEEK_SET);
if(
(fgetc(cdh) == 'E') &&
(fgetc(cdh) == 'C') &&
(fgetc(cdh) == 'M') &&
(fgetc(cdh) == 0x00) &&
(strncmp((isoname+strlen(isoname)-5), ".ecm", 4))
) {
// Function used to read CD normally
// TODO: detect if 2048 and use it
cdimg_read_func_o = cdread_normal;
// Function used to decode ECM data
cdimg_read_func = cdread_ecm_decode;
// Last accessed sector
prevsector = 0;
// Already analyzed during this session, use cached results
if (ecm_file_detected) {
if (accurate_length) *accurate_length = len_ecm_savetable;
return 0;
}
SysPrintf(_("\nDetected ECM file with proper header and filename suffix.\n"));
// Init ECC/EDC tables
eccedc_init();
// Reserve maximum known sector ammount for LUT (80MIN CD)
len_ecm_savetable = 75*80*60; //2*(accurate_length/CD_FRAMESIZE_RAW);
// Index 0 always points to beginning of ECM data
ecm_savetable = calloc(len_ecm_savetable, sizeof(ECMFILELUT)); // calloc returns nulled data
ecm_savetable[0].filepos = ECM_HEADER_SIZE;
if (accurate_length || decoded_ecm_sectors) {
u8 tbuf1[CD_FRAMESIZE_RAW];
len_ecm_savetable = 0; // indicates to cdread_ecm_decode that no lut has been built yet
cdread_ecm_decode(cdh, 0U, tbuf1, INT_MAX); // builds LUT completely
if (accurate_length)*accurate_length = len_ecm_savetable;
}
// Full image decoded? Needs fmemopen()
#ifdef ENABLE_ECM_FULL
if (decoded_ecm_sectors) {
len_decoded_ecm_buffer = len_ecm_savetable*CD_FRAMESIZE_RAW;
decoded_ecm_buffer = malloc(len_decoded_ecm_buffer);
if (decoded_ecm_buffer) {
//printf("Memory ok1 %u %p\n", len_decoded_ecm_buffer, decoded_ecm_buffer);
decoded_ecm = fmemopen(decoded_ecm_buffer, len_decoded_ecm_buffer, "w+b");
decoded_ecm_sectors = 1;
} else {
SysMessage("Could not reserve memory for full ECM buffer. Only LUT will be used.");
decoded_ecm_sectors = 0;
}
}
#endif
ecm_file_detected = TRUE;
return 0;
}
return -1;
}
int (*cdimg_read_func_archive)(FILE *f, unsigned int base, void *dest, int sector) = NULL;
#ifdef HAVE_LIBARCHIVE
#include <archive.h>
#include <archive_entry.h>
struct archive *a = NULL;
u32 len_uncompressed_buffer = 0;
void *cdimage_buffer_mem = NULL;
FILE* cdimage_buffer = NULL; //cdHandle to store file
int aropen(FILE* fparchive, const char* _fn) {
s32 r;
u64 length = 0, length_peek;
boolean use_temp_file = FALSE; // TODO make a config param
static struct archive_entry *ae=NULL;
struct archive_entry *ae_peek;
if (a == NULL && cdimage_buffer == NULL) {
// We open file twice. First to peek sizes. This nastyness due used interface.
a = archive_read_new();
// r = archive_read_support_filter_all(a);
r = archive_read_support_format_all(a);
//r = archive_read_support_filter_all(a);
//r = archive_read_support_format_raw(a);
//r = archive_read_open_FILE(a, archive);
archive_read_open_filename(a, _fn, 75*CD_FRAMESIZE_RAW);
if (r != ARCHIVE_OK) {
SysPrintf("Archive open failed (%i).\n", r);
archive_read_free(a);
a = NULL;
return -1;
}
// Get the biggest file in archive
while ((r=archive_read_next_header(a, &ae_peek)) == ARCHIVE_OK) {
length_peek = archive_entry_size(ae_peek);
//printf("Entry canditate %s %i\n", archive_entry_pathname(ae_peek), length_peek);
length = MAX(length_peek, length);
ae = (ae == NULL ? ae_peek : ae);
}
archive_read_free(a);
if (ae == NULL) {
SysPrintf("Archive entry read failed (%i).\n", r);
a = NULL;
return -1;
}
//Now really open the file
a = archive_read_new();
// r = archive_read_support_compression_all(a);
r = archive_read_support_format_all(a);
archive_read_open_filename(a, _fn, 75*CD_FRAMESIZE_RAW);
while ((r=archive_read_next_header(a, &ae)) == ARCHIVE_OK) {
length_peek = archive_entry_size(ae);
if (length_peek == length) {
//ae = ae_peek;
SysPrintf(" -- Selected entry %s %i", archive_entry_pathname(ae), length);
break;
}
}
len_uncompressed_buffer = length?length:700*1024*1024;
}
if (use_temp_file && (cdimage_buffer == NULL || cdHandle != cdimage_buffer)) {
cdimage_buffer = fopen("/tmp/pcsxr.tmp.bin", "w+b");
}
else if (!use_temp_file && (cdimage_buffer == NULL || cdHandle != cdimage_buffer)) {
if (cdimage_buffer_mem == NULL && ((cdimage_buffer_mem = malloc(len_uncompressed_buffer)) == NULL)) {
SysMessage("Could not reserve enough memory for full image buffer.\n");
exit(3);
}
//printf("Memory ok2 %u %p\n", len_uncompressed_buffer, cdimage_buffer_mem);
cdimage_buffer = fmemopen(cdimage_buffer_mem, len_uncompressed_buffer, "w+b");
} else {
}
if (cdHandle != cdimage_buffer) {
fclose(cdHandle); // opened thru archive so this not needed anymore
cdHandle = cdimage_buffer;
}
return 0;
}
static int cdread_archive(FILE *f, unsigned int base, void *dest, int sector)
{
s32 r;
size_t size;
size_t readsize;
static off_t offset = 0; // w/o read always or static/ftell
const void *buff;
// If not pointing to archive file but CDDA file or some other track
if(f != cdHandle) {
return cdimg_read_func_archive(f, base, dest, sector);
}
// Jump if already completely read
if (a != NULL /*&& (ecm_file_detected || sector*CD_FRAMESIZE_RAW <= len_uncompressed_buffer)*/) {
readsize = (sector+1) * CD_FRAMESIZE_RAW;
for (fseek(cdimage_buffer, offset, SEEK_SET); offset < readsize;) {
r = archive_read_data_block(a, &buff, &size, &offset);
offset += size;
SysPrintf("ReadArchive seek:%u(%u) cur:%u(%u)\r", sector, readsize/1024, offset/CD_FRAMESIZE_RAW, offset/1024);
fwrite(buff, size, 1, cdimage_buffer);
if (r != ARCHIVE_OK) {
//SysPrintf("End of archive.\n");
archive_read_free(a);
a = NULL;
readsize = offset;
fflush(cdimage_buffer);
fseek(cdimage_buffer, 0, SEEK_SET);
}
}
} else {
//SysPrintf("ReadSectorArchSector: %u(%u)\n", sector, sector*CD_FRAMESIZE_RAW);
}
// TODO what causes req sector to be greater than CD size?
r = cdimg_read_func_archive(cdimage_buffer, base, dest, sector);
return r;
}
int handlearchive(const char *isoname, s32* accurate_length) {
u32 read_size = accurate_length?MSF2SECT(70,70,16) : MSF2SECT(0,0,16);
int ret = -1;
if ((ret=aropen(cdHandle, isoname)) == 0) {
cdimg_read_func = cdread_archive;
SysPrintf("[+archive]");
if (!ecm_file_detected) {
#ifndef ENABLE_ECM_FULL
//Detect ECM inside archive
cdimg_read_func_archive = cdread_normal;
cdread_archive(cdHandle, 0, cdbuffer, read_size);
if (handleecm("test.ecm", cdimage_buffer, accurate_length) != -1) {
cdimg_read_func_archive = cdread_ecm_decode;
cdimg_read_func = cdread_archive;
SysPrintf("[+ecm]");
}
#endif
} else {
SysPrintf("[+ecm]");
}
}
return ret;
}
#else
int aropen(FILE* fparchive, const char* _fn) {return -1;}
static int cdread_archive(FILE *f, unsigned int base, void *dest, int sector) {return -1;}
int handlearchive(const char *isoname, s32* accurate_length) {return -1;}
#endif
static unsigned char * CALLBACK ISOgetBuffer_compr(void) {
return compr_img->buff_raw[compr_img->sector_in_blk] + 12;
}
static unsigned char * CALLBACK ISOgetBuffer(void) {
return cdbuffer + 12;
}
static void PrintTracks(void) {
int i;
for (i = 1; i <= numtracks; i++) {
SysPrintf(_("Track %.2d (%s) - Start %.2d:%.2d:%.2d, Length %.2d:%.2d:%.2d\n"),
i, (ti[i].type == DATA ? "DATA" : ti[i].cddatype == CCDDA ? "CZDA" : "CDDA"),
ti[i].start[0], ti[i].start[1], ti[i].start[2],
ti[i].length[0], ti[i].length[1], ti[i].length[2]);
}
}
// This function is invoked by the front-end when opening an ISO
// file for playback
static long CALLBACK ISOopen(void) {
if (cdHandle != NULL) {
return 0; // it's already open
}
cdHandle = fopen(GetIsoFile(), "rb");
if (cdHandle == NULL) {
return -1;
}
SysPrintf(_("Loaded CD Image: %s"), GetIsoFile());
cddaBigEndian = FALSE;
subChanMixed = FALSE;
subChanRaw = FALSE;
pregapOffset = 0;
cdrIsoMultidiskCount = 1;
multifile = FALSE;
CDR_getBuffer = ISOgetBuffer;
cdimg_read_func = cdread_normal;
if (parsecue(GetIsoFile()) == 0) {
SysPrintf("[+cue]");
}
else if (parsetoc(GetIsoFile()) == 0) {
SysPrintf("[+toc]");
}
else if (parseccd(GetIsoFile()) == 0) {
SysPrintf("[+ccd]");
}
else if (parsemds(GetIsoFile()) == 0) {
SysPrintf("[+mds]");
}
// TODO Is it possible that cue/ccd+ecm? otherwise use else if below to supressn extra checks
if (handlepbp(GetIsoFile()) == 0) {
SysPrintf("[pbp]");
CDR_getBuffer = ISOgetBuffer_compr;
cdimg_read_func = cdread_compressed;
}
else if (handlecbin(GetIsoFile()) == 0) {
SysPrintf("[cbin]");
CDR_getBuffer = ISOgetBuffer_compr;
cdimg_read_func = cdread_compressed;
}
else if ((handleecm(GetIsoFile(), cdHandle, NULL) == 0)) {
SysPrintf("[+ecm]");
}
else if (handlearchive(GetIsoFile(), NULL) == 0) {
}
if (!subChanMixed && opensubfile(GetIsoFile()) == 0) {
SysPrintf("[+sub]");
}
if (opensbifile(GetIsoFile()) == 0) {
SysPrintf("[+sbi]");
}
if (!ecm_file_detected) {
// guess whether it is mode1/2048
fseek(cdHandle, 0, SEEK_END);
if (ftell(cdHandle) % 2048 == 0) {
unsigned int modeTest = 0;
fseek(cdHandle, 0, SEEK_SET);
fread(&modeTest, 4, 1, cdHandle);
if (SWAP32(modeTest) != 0xffffff00) {
SysPrintf("[2048]");
isMode1ISO = TRUE;
}
}
fseek(cdHandle, 0, SEEK_SET);
}
SysPrintf(".\n");
PrintTracks();
if (subChanMixed && (cdimg_read_func == cdread_normal))
cdimg_read_func = cdread_sub_mixed;
else if (isMode1ISO && (cdimg_read_func == cdread_normal))
cdimg_read_func = cdread_2048;
else if (isMode1ISO && (cdimg_read_func_archive == cdread_normal))
cdimg_read_func_archive = cdread_2048;
// make sure we have another handle open for cdda
if (numtracks > 1 && ti[1].handle == NULL) {
ti[1].handle = fopen(GetIsoFile(), "rb");
}
return 0;
}
static long CALLBACK ISOclose(void) {
int i;
if (cdHandle != NULL) {
fclose(cdHandle);
cdHandle = NULL;
//cdimage_buffer = NULL;
}
if (subHandle != NULL) {
fclose(subHandle);
subHandle = NULL;
}
if (compr_img != NULL) {
free(compr_img->index_table);
free(compr_img);
compr_img = NULL;
}
for (i = 1; i <= numtracks; i++) {
if (ti[i].handle != NULL) {
fclose(ti[i].handle);
ti[i].handle = NULL;
if (ti[i].decoded_buffer != NULL) {
free(ti[i].decoded_buffer);
}
ti[i].cddatype = NONE;
}
}
numtracks = 0;
ti[1].type = 0;
memset(cdbuffer, 0, sizeof(cdbuffer));
CDR_getBuffer = ISOgetBuffer;
return 0;
}
long CALLBACK ISOinit(void) {
assert(cdHandle == NULL);
assert(subHandle == NULL);
assert(ecm_file_detected == FALSE);
assert(decoded_ecm_buffer == NULL);
assert(decoded_ecm == NULL);
return 0; // do nothing
}
static long CALLBACK ISOshutdown(void) {
ISOclose();
// ECM LUT
free(ecm_savetable);
ecm_savetable = NULL;
if (decoded_ecm != NULL) {
fclose(decoded_ecm);
free(decoded_ecm_buffer);
decoded_ecm_buffer = NULL;
decoded_ecm = NULL;
}
ecm_file_detected = FALSE;
#ifdef HAVE_LIBARCHIVE
if (cdimage_buffer != NULL) {
//fclose(cdimage_buffer);
free(cdimage_buffer_mem);
cdimage_buffer_mem = NULL;
cdimage_buffer = NULL;
if (a) {
archive_read_free(a);
a = NULL;
}
}
#endif
return 0;
}
// return Starting and Ending Track
// buffer:
// byte 0 - start track
// byte 1 - end track
static long CALLBACK ISOgetTN(unsigned char *buffer) {
buffer[0] = 1;
if (numtracks > 0) {
buffer[1] = numtracks;
}
else {
buffer[1] = 1;
}
return 0;
}
// return Track Time
// buffer:
// byte 0 - frame
// byte 1 - second
// byte 2 - minute
static long CALLBACK ISOgetTD(unsigned char track, unsigned char *buffer) {
if (track == 0) {
unsigned int sect;
unsigned char time[3];
sect = msf2sec(ti[numtracks].start) + msf2sec(ti[numtracks].length);
sec2msf(sect, (char *)time);
buffer[2] = time[0];
buffer[1] = time[1];
buffer[0] = time[2];
}
else if (numtracks > 0 && track <= numtracks) {
buffer[2] = ti[track].start[0];
buffer[1] = ti[track].start[1];
buffer[0] = ti[track].start[2];
}
else {
buffer[2] = 0;
buffer[1] = 2;
buffer[0] = 0;
}
return 0;
}
// decode 'raw' subchannel data ripped by cdrdao
static void DecodeRawSubData(void) {
unsigned char subQData[12];
int i;
memset(subQData, 0, sizeof(subQData));
for (i = 0; i < 8 * 12; i++) {
if (subbuffer[i] & (1 << 6)) { // only subchannel Q is needed
subQData[i >> 3] |= (1 << (7 - (i & 7)));
}
}
memcpy(&subbuffer[12], subQData, 12);
}
// read track
// time: byte 0 - minute; byte 1 - second; byte 2 - frame
// uses bcd format
static long CALLBACK ISOreadTrack(unsigned char *time) {
int sector = MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2]));
long ret;
if (cdHandle == NULL) {
return -1;
}
if (pregapOffset) {
subChanMissing = FALSE;
if (sector >= pregapOffset) {
sector -= 2 * 75;
if (sector < pregapOffset)
subChanMissing = TRUE;
}
}
ret = cdimg_read_func(cdHandle, 0, cdbuffer, sector);
if (ret < 0)
return -1;
if (subHandle != NULL) {
fseek(subHandle, sector * SUB_FRAMESIZE, SEEK_SET);
fread(subbuffer, 1, SUB_FRAMESIZE, subHandle);
if (subChanRaw) DecodeRawSubData();
}
return 0;
}
// plays cdda audio
// sector: byte 0 - minute; byte 1 - second; byte 2 - frame
// does NOT uses bcd format
static long CALLBACK ISOplay(unsigned char *time) {
playing = TRUE;
return 0;
}
// stops cdda audio
static long CALLBACK ISOstop(void) {
playing = FALSE;
return 0;
}
// gets subchannel data
static unsigned char* CALLBACK ISOgetBufferSub(void) {
if ((subHandle != NULL || subChanMixed) && !subChanMissing) {
return subbuffer;
}
return NULL;
}
static long CALLBACK ISOgetStatus(struct CdrStat *stat) {
u32 sect;
CDR__getStatus(stat);
if (playing) {
stat->Type = 0x02;
stat->Status |= 0x80;
}
else {
// BIOS - boot ID (CD type)
stat->Type = ti[1].type;
}
// relative -> absolute time
sect = cddaCurPos;
sec2msf(sect, (u8 *)stat->Time);
return 0;
}
// read CDDA sector into buffer
long CALLBACK ISOreadCDDA(unsigned char m, unsigned char s, unsigned char f, unsigned char *buffer) {
unsigned char msf[3] = {m, s, f};
unsigned int file, track, track_start = 0;
int ret;
cddaCurPos = msf2sec(msf);
// find current track index
for (track = numtracks; ; track--) {
track_start = msf2sec(ti[track].start);
if (track_start <= cddaCurPos)
break;
if (track == 1)
break;
}
// data tracks play silent (or CDDA set to silent)
if (ti[track].type != CDDA || Config.Cdda == CDDA_DISABLED) {
memset(buffer, 0, CD_FRAMESIZE_RAW);
return 0;
}
file = 1;
if (multifile) {
// find the file that contains this track
for (file = track; file > 1; file--)
if (ti[file].handle != NULL)
break;
}
/* Need to decode audio track first if compressed still (lazy) */
if (ti[file].cddatype > BIN) {
do_decode_cdda(&(ti[file]), file);
}
ret = cdimg_read_func(ti[file].handle, ti[track].start_offset,
buffer, cddaCurPos - track_start);
if (ret != CD_FRAMESIZE_RAW) {
memset(buffer, 0, CD_FRAMESIZE_RAW);
return -1;
}
if (Config.Cdda == CDDA_ENABLED_BE || cddaBigEndian) {
int i;
unsigned char tmp;
for (i = 0; i < CD_FRAMESIZE_RAW / 2; i++) {
tmp = buffer[i * 2];
buffer[i * 2] = buffer[i * 2 + 1];
buffer[i * 2 + 1] = tmp;
}
}
return 0;
}
void cdrIsoInit(void) {
CDR_init = ISOinit;
CDR_shutdown = ISOshutdown;
CDR_open = ISOopen;
CDR_close = ISOclose;
CDR_getTN = ISOgetTN;
CDR_getTD = ISOgetTD;
CDR_readTrack = ISOreadTrack;
CDR_getBuffer = ISOgetBuffer;
CDR_play = ISOplay;
CDR_stop = ISOstop;
CDR_getBufferSub = ISOgetBufferSub;
CDR_getStatus = ISOgetStatus;
CDR_readCDDA = ISOreadCDDA;
CDR_getDriveLetter = CDR__getDriveLetter;
CDR_configure = CDR__configure;
CDR_test = CDR__test;
CDR_about = CDR__about;
CDR_setfilename = CDR__setfilename;
numtracks = 0;
}
int cdrIsoActive(void) {
return (cdHandle != NULL || ecm_savetable != NULL || decoded_ecm != NULL);
}