mirror of
https://github.com/MiSTer-devel/Main_MiSTer.git
synced 2026-04-12 03:04:02 +00:00
1768 lines
46 KiB
C++
1768 lines
46 KiB
C++
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <ios>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <sys/stat.h>
|
|
#include <cmath>
|
|
#include <libchdr/chd.h>
|
|
#include <byteswap.h>
|
|
#include "spi.h"
|
|
#include "user_io.h"
|
|
#include "file_io.h"
|
|
#include "hardware.h"
|
|
#include "cd.h"
|
|
#include "ide.h"
|
|
|
|
#if 0
|
|
#define dbg_printf printf
|
|
#define dbg_print_regs ide_print_regs
|
|
#define dbg_hexdump hexdump
|
|
#else
|
|
#define dbg_printf(...) void()
|
|
#define dbg_print_regs void
|
|
#define dbg_hexdump(...) void()
|
|
#endif
|
|
|
|
#define ide_send_data(databuf, size) ide_sendbuf(ide, 255, (size), (uint16_t*)(databuf))
|
|
#define ide_recv_data(databuf, size) ide_recvbuf(ide, 255, (size), (uint16_t*)(databuf))
|
|
#define ide_reset_buf() ide_reg_set(ide, 7, 0)
|
|
|
|
#define BYTES_PER_RAW_REDBOOK_FRAME 2352
|
|
#define BYTES_PER_COOKED_REDBOOK_FRAME 2048
|
|
#define REDBOOK_FRAMES_PER_SECOND 75
|
|
#define REDBOOK_FRAME_PADDING 150
|
|
|
|
#define CD_FPS 75
|
|
#define MSF_TO_FRAMES(M, S, F) ((M)*60*CD_FPS+(S)*CD_FPS+(F))
|
|
|
|
#define CD_ERR_NO_DISK 2
|
|
#define CD_ERR_ILLEGAL_REQUEST 5
|
|
#define CD_ERR_UNIT_ATTENTION 6
|
|
|
|
#define CD_ASC_CODE_COMMAND_SEQUENCE_ERR 0x2C
|
|
#define CD_ASC_CODE_ILLEGAL_OPCODE 0x20
|
|
#define CD_ASC_CODE_ILLEGAL_FIELD_CMD_PACKET 0x24
|
|
|
|
|
|
typedef struct
|
|
{
|
|
unsigned char min;
|
|
unsigned char sec;
|
|
unsigned char fr;
|
|
} TMSF;
|
|
|
|
static int check_magic(fileTYPE *file, int sectorSize, int mode2)
|
|
{
|
|
// Initialize our array in the event file->read() doesn't fully write it
|
|
static uint8_t pvd[BYTES_PER_COOKED_REDBOOK_FRAME];
|
|
memset(pvd, 0, sizeof(pvd));
|
|
|
|
uint32_t seek = 16 * sectorSize; // first vd is located at sector 16
|
|
if (sectorSize == BYTES_PER_RAW_REDBOOK_FRAME && !mode2) seek += 16;
|
|
if (mode2) seek += 24;
|
|
FileSeek(file, seek, SEEK_SET);
|
|
if (!FileReadAdv(file, pvd, BYTES_PER_COOKED_REDBOOK_FRAME)) return 0;
|
|
|
|
// pvd[0] = descriptor type, pvd[1..5] = standard identifier,
|
|
// pvd[6] = iso version (+8 for High Sierra)
|
|
return ((pvd[0] == 1 && !strncmp((char*)(&pvd[1]), "CD001", 5) && pvd[6] == 1) ||
|
|
(pvd[8] == 1 && !strncmp((char*)(&pvd[9]), "CDROM", 5) && pvd[14] == 1));
|
|
}
|
|
|
|
static int check_iso_file(fileTYPE *f, uint8_t *mode2, uint16_t *sectorSize)
|
|
{
|
|
if (check_magic(f, BYTES_PER_COOKED_REDBOOK_FRAME, false))
|
|
{
|
|
if (sectorSize) *sectorSize = BYTES_PER_COOKED_REDBOOK_FRAME;
|
|
if (mode2) *mode2 = 0;
|
|
return 1;
|
|
}
|
|
else if (check_magic(f, BYTES_PER_RAW_REDBOOK_FRAME, false))
|
|
{
|
|
if (sectorSize) *sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
|
|
if (mode2) *mode2 = 0;
|
|
return 1;
|
|
}
|
|
else if (check_magic(f, 2336, true))
|
|
{
|
|
if (sectorSize) *sectorSize = 2336;
|
|
if (mode2) *mode2 = 1;
|
|
return 1;
|
|
}
|
|
else if (check_magic(f, BYTES_PER_RAW_REDBOOK_FRAME, true))
|
|
{
|
|
if (sectorSize) *sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
|
|
if (mode2) *mode2 = 1;
|
|
return 1;
|
|
}
|
|
|
|
if (sectorSize) *sectorSize = 0;
|
|
if (mode2) *mode2 = 0;
|
|
return 0;
|
|
}
|
|
|
|
static const char * load_iso_file(drive_t *drv, const char* filename)
|
|
{
|
|
fileTYPE f;
|
|
memset(drv->track, 0, sizeof(drv->track));
|
|
drv->track_cnt = 0;
|
|
|
|
strcpy(drv->track[0].filename, filename);
|
|
if (!FileOpen(&f, filename))
|
|
{
|
|
printf("Cannot open ISO file!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!check_iso_file(&f, &drv->track[0].mode2, &drv->track[0].sectorSize))
|
|
{
|
|
printf("Fail to parse ISO!\n");
|
|
FileClose(&f);
|
|
return 0;
|
|
}
|
|
|
|
drv->track[0].attr = 0x40; //data track
|
|
drv->track[0].length = f.size / drv->track[0].sectorSize;
|
|
drv->track[0].number = 1;
|
|
|
|
FileClose(&f);
|
|
|
|
// lead-out track (track 2)
|
|
drv->track[1].start = drv->track[0].length;
|
|
drv->track[2].number = 2;
|
|
|
|
printf("ISO: mode2 = %d, sectorSize = %d, sectors = %d\n", drv->track[0].mode2, drv->track[0].sectorSize, drv->track[0].length);
|
|
drv->track_cnt = 2;
|
|
|
|
drv->data_num = 0;
|
|
if (!FileOpen(&drv->track[0].f, drv->track[0].filename))
|
|
{
|
|
printf("Cannot open ISO file! (First track)\n");
|
|
return 0;
|
|
}
|
|
return drv->track[0].filename;
|
|
}
|
|
|
|
static int get_word(std::string &keyword, std::istream &in)
|
|
{
|
|
in >> keyword;
|
|
for (uint32_t i = 0; i < keyword.size(); i++) keyword[i] = (char)toupper(keyword[i]);
|
|
return keyword.size();
|
|
}
|
|
|
|
static int get_timecode(uint32_t &frames, std::istream &in)
|
|
{
|
|
std::string msf;
|
|
in >> msf;
|
|
TMSF tmp = { 0, 0, 0 };
|
|
int success = sscanf(msf.c_str(), "%hhu:%hhu:%hhu", &tmp.min, &tmp.sec, &tmp.fr) == 3;
|
|
frames = (int)MSF_TO_FRAMES(tmp.min, tmp.sec, tmp.fr);
|
|
return success;
|
|
}
|
|
|
|
static track_t *get_track_from_lba(drive_t *drive, uint32_t lba, bool &index0)
|
|
{
|
|
track_t *ret = NULL;
|
|
index0 = false;
|
|
for (int i = 0; i < drive->track_cnt; i++)
|
|
{
|
|
uint32_t start_lba = i ? drive->track[i-1].start + drive->track[i-1].length : 0;
|
|
uint32_t end_lba = drive->track[i].start + drive->track[i].length;
|
|
|
|
if (lba >= start_lba && lba <= end_lba)
|
|
{
|
|
ret = &drive->track[i];
|
|
//In the "pregap" section
|
|
if (lba < drive->track[i].start) index0 = true;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int add_track(drive_t *drv, track_t *curr, uint32_t &shift, const int32_t prestart, uint32_t &totalPregap, uint32_t currPregap)
|
|
{
|
|
uint32_t skip = 0;
|
|
if (drv->track_cnt >= sizeof(drv->track) / sizeof(drv->track[0]))
|
|
{
|
|
printf("CDROM: too many tracks(%d)\n", drv->track_cnt);
|
|
return 0;
|
|
}
|
|
|
|
// frames between index 0(prestart) and 1(curr.start) must be skipped
|
|
if (prestart >= 0)
|
|
{
|
|
if (prestart > static_cast<int>(curr->start))
|
|
{
|
|
printf("CDROM: add_track => prestart %d cannot be > curr.start %u\n", prestart, curr->start);
|
|
return 0;
|
|
}
|
|
skip = static_cast<uint32_t>(static_cast<int>(curr->start) - prestart);
|
|
}
|
|
|
|
// Add the first track, if our vector is empty
|
|
if (!drv->track_cnt)
|
|
{
|
|
//assertm(curr.number == 1, "The first track must be labelled number 1 [BUG!]");
|
|
curr->skip = skip * curr->sectorSize;
|
|
curr->start += currPregap;
|
|
totalPregap = currPregap;
|
|
|
|
memcpy(&drv->track[drv->track_cnt], curr, sizeof(track_t));
|
|
FileOpenEx(&drv->track[drv->track_cnt].f, curr->filename, O_RDONLY);
|
|
drv->track_cnt++;
|
|
return 1;
|
|
}
|
|
|
|
// Guard against undefined behavior in subsequent tracks.back() call
|
|
//assert(!tracks.empty());
|
|
track_t *prev = &drv->track[drv->track_cnt - 1];
|
|
|
|
// current track consumes data from the same file as the previous
|
|
if (!strcmp(prev->filename, curr->filename))
|
|
{
|
|
curr->start += shift;
|
|
if (!prev->length)
|
|
{
|
|
prev->length = curr->start + totalPregap - prev->start - skip;
|
|
}
|
|
curr->skip += prev->skip + prev->length * prev->sectorSize + skip * curr->sectorSize;
|
|
totalPregap += currPregap;
|
|
curr->start += totalPregap;
|
|
// current track uses a different file as the previous track
|
|
}
|
|
else
|
|
{
|
|
uint32_t size = FileLoad(prev->filename, 0, 0);
|
|
const uint32_t tmp = size - prev->skip;
|
|
prev->length = tmp / prev->sectorSize;
|
|
|
|
if (tmp % prev->sectorSize != 0) prev->length++; // padding
|
|
|
|
curr->start += prev->start + prev->length + currPregap;
|
|
curr->skip = skip * curr->sectorSize;
|
|
shift += prev->start + prev->length;
|
|
totalPregap = currPregap;
|
|
}
|
|
|
|
// error checks
|
|
if (curr->number <= 1
|
|
|| prev->number + 1 != curr->number
|
|
|| curr->start < prev->start + prev->length) {
|
|
printf("add_track: failed consistency checks\n"
|
|
"\tcurr.number (%d) <= 1\n"
|
|
"\tprev.number (%d) + 1 != curr.number (%d)\n"
|
|
"\tcurr.start (%d) < prev.start (%d) + prev.length (%d)\n",
|
|
curr->number, prev->number, curr->number,
|
|
curr->start, prev->start, prev->length);
|
|
return 0;
|
|
}
|
|
|
|
memcpy(&drv->track[drv->track_cnt], curr, sizeof(track_t));
|
|
FileOpenEx(&drv->track[drv->track_cnt].f, drv->track[drv->track_cnt].filename, O_RDONLY);
|
|
drv->track_cnt++;
|
|
return 1;
|
|
}
|
|
|
|
static const char* load_chd_file(drive_t *drv, const char *chdfile)
|
|
{
|
|
|
|
//Borrow the cd.h "toc_t" and mister_chd* parse function. Then translate the toc_t to drive_t+track_t.
|
|
//TODO: abstract all the bin/cue+chd+iso parsing and reading into a shared class
|
|
//
|
|
|
|
const char *ext = chdfile + strlen(chdfile) - 4;
|
|
uint32_t total_sector_size = 0;
|
|
|
|
|
|
if (strncasecmp(".chd", ext, 4))
|
|
{
|
|
//Not a CHD
|
|
return 0;
|
|
}
|
|
toc_t tmpTOC = { };
|
|
memset(drv->track, 0, sizeof(drv->track));
|
|
drv->track_cnt = 0;
|
|
chd_error err = mister_load_chd(chdfile, &tmpTOC);
|
|
if (err != CHDERR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (drv->chd_hunkbuf)
|
|
{
|
|
free(drv->chd_hunkbuf);
|
|
}
|
|
|
|
drv->chd_hunkbuf = (uint8_t *)malloc(tmpTOC.chd_hunksize);
|
|
drv->chd_hunknum = -1;
|
|
drv->chd_f = tmpTOC.chd_f;
|
|
|
|
//don't use add_track, just do it ourselves...
|
|
for (int i = 0; i < tmpTOC.last; i++)
|
|
{
|
|
cd_track_t *chd_track = &tmpTOC.tracks[i];
|
|
track_t *trk = &drv->track[i];
|
|
trk->number = i + 1;
|
|
trk->sectorSize = chd_track->sector_size;
|
|
if (chd_track->type)
|
|
{
|
|
trk->attr = 0x40;
|
|
if (chd_track->type == 2)
|
|
{
|
|
trk->mode2 = true;
|
|
}
|
|
|
|
}
|
|
|
|
trk->chd_offset = chd_track->offset;
|
|
trk->start = chd_track->start;
|
|
trk->length = chd_track->end - chd_track->start;
|
|
drv->track_cnt++;
|
|
total_sector_size += trk->length * trk->sectorSize;
|
|
}
|
|
|
|
//Add the lead-out track
|
|
|
|
track_t *lead_out = &drv->track[drv->track_cnt];
|
|
lead_out->number = drv->track_cnt + 1;
|
|
lead_out->attr = 0;
|
|
lead_out->start = tmpTOC.tracks[tmpTOC.last - 1].end;
|
|
lead_out->length = 0;
|
|
drv->track_cnt++;
|
|
|
|
drv->total_sectors = total_sector_size / 512;
|
|
drv->chd_total_size = total_sector_size;
|
|
|
|
for (uint8_t i = 0; i < drv->track_cnt; i++)
|
|
{
|
|
if (drv->track[i].attr == 0x40)
|
|
{
|
|
drv->data_num = i;
|
|
}
|
|
}
|
|
|
|
return chdfile;
|
|
}
|
|
|
|
|
|
static const char* load_cue_file(drive_t *drv, const char *cuefile)
|
|
{
|
|
memset(drv->track, 0, sizeof(drv->track));
|
|
drv->track_cnt = 0;
|
|
|
|
track_t track = {};
|
|
uint32_t shift = 0;
|
|
uint32_t currPregap = 0;
|
|
uint32_t totalPregap = 0;
|
|
int32_t prestart = -1;
|
|
int track_number;
|
|
int success;
|
|
int canAddTrack = 0;
|
|
|
|
std::string pathname(cuefile);
|
|
std::size_t found = pathname.find_last_of('/');
|
|
if (found == std::string::npos) return 0; // no folder name?
|
|
pathname.resize(found + 1);
|
|
|
|
std::ifstream in;
|
|
in.open(cuefile, std::ios::in);
|
|
if (in.fail()) return 0;
|
|
|
|
while (!in.eof())
|
|
{
|
|
// get next line
|
|
std::string buf;
|
|
std::getline(in, buf);
|
|
|
|
if (in.fail() && !in.eof()) return 0; // probably a binary file
|
|
|
|
std::istringstream line(buf);
|
|
|
|
std::string command;
|
|
get_word(command, line);
|
|
|
|
//printf("command: %s\n", command.c_str());
|
|
|
|
if (command == "TRACK")
|
|
{
|
|
if (canAddTrack) success = add_track(drv, &track, shift, prestart, totalPregap, currPregap);
|
|
else success = 1;
|
|
|
|
track.start = 0;
|
|
track.skip = 0;
|
|
currPregap = 0;
|
|
prestart = -1;
|
|
|
|
line >> track_number; // (cin) read into a true int first
|
|
|
|
track.number = static_cast<uint8_t>(track_number);
|
|
|
|
std::string type;
|
|
get_word(type, line);
|
|
|
|
//printf(" type: %s\n", type.c_str());
|
|
|
|
if (type == "AUDIO")
|
|
{
|
|
track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
|
|
track.attr = 0;
|
|
track.mode2 = false;
|
|
}
|
|
else if (type == "MODE1/2048")
|
|
{
|
|
track.sectorSize = BYTES_PER_COOKED_REDBOOK_FRAME;
|
|
track.attr = 0x40;
|
|
track.mode2 = false;
|
|
}
|
|
else if (type == "MODE1/2352")
|
|
{
|
|
track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
|
|
track.attr = 0x40;
|
|
track.mode2 = false;
|
|
}
|
|
else if (type == "MODE2/2336")
|
|
{
|
|
track.sectorSize = 2336;
|
|
track.attr = 0x40;
|
|
track.mode2 = true;
|
|
}
|
|
else if (type == "MODE2/2352")
|
|
{
|
|
track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
|
|
track.attr = 0x40;
|
|
track.mode2 = true;
|
|
}
|
|
else success = 0;
|
|
|
|
canAddTrack = 1;
|
|
}
|
|
else if (command == "INDEX")
|
|
{
|
|
int index;
|
|
line >> index;
|
|
uint32_t frame;
|
|
success = get_timecode(frame, line);
|
|
|
|
if (index == 1) track.start = frame;
|
|
else if (index == 0) prestart = static_cast<int32_t>(frame);
|
|
// ignore other indices
|
|
}
|
|
else if (command == "FILE")
|
|
{
|
|
if (canAddTrack) success = add_track(drv, &track, shift, prestart, totalPregap, currPregap);
|
|
else success = 1;
|
|
canAddTrack = 0;
|
|
|
|
std::string filename;
|
|
std::getline(std::getline(line, filename, '"'), filename, '"');
|
|
|
|
strcpy(track.filename, pathname.c_str());
|
|
strcat(track.filename, filename.c_str());
|
|
|
|
printf("cue: got new file name: %s\n", track.filename);
|
|
}
|
|
else if (command == "PREGAP") success = get_timecode(currPregap, line);
|
|
// ignored commands
|
|
else if (command == "CATALOG" || command == "CDTEXTFILE" || command == "FLAGS" || command == "ISRC" ||
|
|
command == "PERFORMER" || command == "POSTGAP" || command == "REM" ||
|
|
command == "SONGWRITER" || command == "TITLE" || command.empty())
|
|
{
|
|
success = 1;
|
|
}
|
|
// failure
|
|
else
|
|
{
|
|
success = 0;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// add last track
|
|
if (!add_track(drv, &track, shift, prestart, totalPregap, currPregap))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// add lead-out track
|
|
track.number++;
|
|
track.filename[0] = 0;
|
|
track.attr = 0;//sync with load iso
|
|
track.start = drv->track[track.number - 1].start + drv->track[track.number - 1].length;
|
|
track.length = 0;
|
|
|
|
if (!add_track(drv, &track, shift, -1, totalPregap, 0))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < drv->track_cnt; i++)
|
|
{
|
|
if (drv->track[i].attr == 0x40)
|
|
{
|
|
drv->data_num = i;
|
|
return drv->track[i].filename;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline TMSF frames_to_msf(uint32_t frames)
|
|
{
|
|
TMSF msf = { 0, 0, 0 };
|
|
msf.fr = frames % REDBOOK_FRAMES_PER_SECOND;
|
|
frames /= REDBOOK_FRAMES_PER_SECOND;
|
|
msf.sec = frames % 60;
|
|
frames /= 60;
|
|
msf.min = static_cast<uint8_t>(frames);
|
|
return msf;
|
|
}
|
|
|
|
static int get_tracks(drive_t *drv, int& start_track_num, int& end_track_num, TMSF& lead_out_msf)
|
|
{
|
|
if (drv->track_cnt < 2 || !drv->track[0].length) return 0;
|
|
|
|
start_track_num = drv->track[0].number;
|
|
end_track_num = drv->track[drv->track_cnt - 2].number;
|
|
lead_out_msf = frames_to_msf(drv->track[drv->track_cnt - 1].start + REDBOOK_FRAME_PADDING);
|
|
return 1;
|
|
}
|
|
|
|
static int get_track_info(drive_t *drv, int requested_track_num, TMSF& start_msf, unsigned char& attr)
|
|
{
|
|
if (drv->track_cnt < 2 || requested_track_num < 1 || requested_track_num >= drv->track_cnt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
start_msf = frames_to_msf(drv->track[requested_track_num - 1].start + REDBOOK_FRAME_PADDING);
|
|
attr = drv->track[requested_track_num - 1].attr;
|
|
return 1;
|
|
}
|
|
|
|
static int read_toc(drive_t *drv, uint8_t *cmdbuf)
|
|
{
|
|
/* NTS: The SCSI MMC standards say we're allowed to indicate the return data
|
|
* is longer than it's allocation length. But here's the thing: some MS-DOS
|
|
* CD-ROM drivers will ask for the TOC but only provide enough room for one
|
|
* entry (OAKCDROM.SYS) and if we signal more data than it's buffer, it will
|
|
* reject our response and render the CD-ROM drive inaccessible. So to make
|
|
* this emulation work, we have to cut our response short to the driver's
|
|
* allocation length */
|
|
unsigned int AllocationLength = ((unsigned int)cmdbuf[7] << 8) + cmdbuf[8];
|
|
unsigned char Format = cmdbuf[2] & 0xF;
|
|
unsigned char Track = cmdbuf[6];
|
|
bool TIME = !!(cmdbuf[1] & 2);
|
|
unsigned char *write;
|
|
int first, last, track;
|
|
TMSF leadOut;
|
|
|
|
printf("read_toc in:\n");
|
|
hexdump(cmdbuf, 12);
|
|
|
|
memset(ide_buf, 0, 8);
|
|
|
|
if (!get_tracks(drv, first, last, leadOut))
|
|
{
|
|
printf("WARNING: ATAPI READ TOC failed to get track info\n");
|
|
return 8;
|
|
}
|
|
|
|
/* start 2 bytes out. we'll fill in the data length later */
|
|
write = ide_buf + 2;
|
|
|
|
if (Format == 1) /* Read multisession info */
|
|
{
|
|
unsigned char attr;
|
|
TMSF start;
|
|
|
|
*write++ = (unsigned char)1; /* @+2 first complete session */
|
|
*write++ = (unsigned char)1; /* @+3 last complete session */
|
|
|
|
if (!get_track_info(drv, first, start, attr))
|
|
{
|
|
printf("WARNING: ATAPI READ TOC unable to read track %u information\n", first);
|
|
attr = 0x41; /* ADR=1 CONTROL=4 */
|
|
start.min = 0;
|
|
start.sec = 0;
|
|
start.fr = 0;
|
|
}
|
|
|
|
printf("Track %u attr=0x%02x %02u:%02u:%02u\n", first, attr, start.min, start.sec, start.fr);
|
|
|
|
*write++ = 0x00; /* entry+0 RESERVED */
|
|
*write++ = (attr >> 4) | 0x10; /* entry+1 ADR=1 CONTROL=4 (DATA) */
|
|
*write++ = (unsigned char)first;/* entry+2 TRACK */
|
|
*write++ = 0x00; /* entry+3 RESERVED */
|
|
|
|
/* then, start address of first track in session */
|
|
if (TIME)
|
|
{
|
|
*write++ = 0x00;
|
|
*write++ = start.min;
|
|
*write++ = start.sec;
|
|
*write++ = start.fr;
|
|
}
|
|
else
|
|
{
|
|
uint32_t sec = (start.min * 60u * 75u) + (start.sec * 75u) + start.fr - 150u;
|
|
*write++ = (unsigned char)(sec >> 24u);
|
|
*write++ = (unsigned char)(sec >> 16u);
|
|
*write++ = (unsigned char)(sec >> 8u);
|
|
*write++ = (unsigned char)(sec >> 0u);
|
|
}
|
|
}
|
|
else if (Format == 0) /* Read table of contents */
|
|
{
|
|
*write++ = (unsigned char)first; /* @+2 */
|
|
*write++ = (unsigned char)last; /* @+3 */
|
|
|
|
for (track = first; track <= last; track++)
|
|
{
|
|
unsigned char attr;
|
|
TMSF start;
|
|
|
|
if (!get_track_info(drv, track, start, attr))
|
|
{
|
|
printf("WARNING: ATAPI READ TOC unable to read track %u information\n", track);
|
|
attr = 0x41; /* ADR=1 CONTROL=4 */
|
|
start.min = 0;
|
|
start.sec = 0;
|
|
start.fr = 0;
|
|
}
|
|
|
|
if (track < Track) continue;
|
|
if ((write + 8) > (ide_buf + AllocationLength)) break;
|
|
|
|
printf("Track %u attr=0x%02x %02u:%02u:%02u\n", track, attr, start.min, start.sec, start.fr);
|
|
|
|
*write++ = 0x00; /* entry+0 RESERVED */
|
|
*write++ = (attr >> 4) | 0x10; /* entry+1 ADR=1 CONTROL=4 (DATA) */
|
|
*write++ = (unsigned char)track;/* entry+2 TRACK */
|
|
*write++ = 0x00; /* entry+3 RESERVED */
|
|
if (TIME)
|
|
{
|
|
*write++ = 0x00;
|
|
*write++ = start.min;
|
|
*write++ = start.sec;
|
|
*write++ = start.fr;
|
|
}
|
|
else
|
|
{
|
|
uint32_t sec = (start.min * 60u * 75u) + (start.sec * 75u) + start.fr - 150u;
|
|
*write++ = (unsigned char)(sec >> 24u);
|
|
*write++ = (unsigned char)(sec >> 16u);
|
|
*write++ = (unsigned char)(sec >> 8u);
|
|
*write++ = (unsigned char)(sec >> 0u);
|
|
}
|
|
}
|
|
|
|
if ((write + 8) <= (ide_buf + AllocationLength))
|
|
{
|
|
*write++ = 0x00;
|
|
*write++ = 0x14;
|
|
*write++ = 0xAA;/*TRACK*/
|
|
*write++ = 0x00;
|
|
if (TIME)
|
|
{
|
|
*write++ = 0x00;
|
|
*write++ = leadOut.min;
|
|
*write++ = leadOut.sec;
|
|
*write++ = leadOut.fr;
|
|
}
|
|
else
|
|
{
|
|
uint32_t sec = (leadOut.min * 60u * 75u) + (leadOut.sec * 75u) + leadOut.fr - 150u;
|
|
*write++ = (unsigned char)(sec >> 24u);
|
|
*write++ = (unsigned char)(sec >> 16u);
|
|
*write++ = (unsigned char)(sec >> 8u);
|
|
*write++ = (unsigned char)(sec >> 0u);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("WARNING: ATAPI READ TOC Format=%u not supported\n", Format);
|
|
return 8;
|
|
}
|
|
|
|
/* update the TOC data length field */
|
|
unsigned int x = (unsigned int)(write - ide_buf) - 2;
|
|
ide_buf[0] = x >> 8;
|
|
ide_buf[1] = x & 0xFF;
|
|
|
|
printf("read_toc result:\n");
|
|
hexdump(ide_buf, write - ide_buf);
|
|
|
|
return write - ide_buf;
|
|
}
|
|
|
|
void cdrom_mode_select(ide_config *ide)
|
|
{
|
|
|
|
uint8_t *mode_page = &ide_buf[8];
|
|
drive_t *drv = &ide->drive[ide->regs.drv];
|
|
uint8_t page_code = mode_page[0] & 0x3F;
|
|
|
|
switch (page_code) {
|
|
case 0x0E:
|
|
{
|
|
uint8_t p0vol = mode_page[9];
|
|
uint8_t p1vol = mode_page[11];
|
|
//in gain factor
|
|
drv->volume_l = (p0vol + 1) / 256.0f;
|
|
drv->volume_r = (p1vol + 1) / 256.0f;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint16_t mode_sense(drive_t *drv, int page)
|
|
{
|
|
uint8_t *write = ide_buf;
|
|
uint8_t *plen;
|
|
|
|
uint32_t x;
|
|
|
|
int valid = 0;
|
|
|
|
printf("mode_sense page: %X\n", page);
|
|
|
|
/* Mode Parameter List MMC-3 Table 340 */
|
|
/* - Mode parameter header */
|
|
/* - Page(s) */
|
|
|
|
/* Mode Parameter Header (response for 10-byte MODE SENSE) SPC-2 Table 148 */
|
|
*write++ = 0x00; /* MODE DATA LENGTH (MSB) */
|
|
*write++ = 0x00; /* (LSB) */
|
|
*write++ = 0x00; /* MEDIUM TYPE */
|
|
*write++ = 0x00; /* DEVICE-SPECIFIC PARAMETER */
|
|
*write++ = 0x00; /* Reserved */
|
|
*write++ = 0x00; /* Reserved */
|
|
*write++ = 0x00; /* BLOCK DESCRIPTOR LENGTH (MSB) */
|
|
*write++ = 0x00; /* (LSB) */
|
|
/* NTS: MMC-3 Table 342 says that BLOCK DESCRIPTOR LENGTH is zero, where it would be 8 for legacy units */
|
|
|
|
/* Mode Page Format MMC-3 Table 341 */
|
|
if (page == 0x01 || page == 0x3F)
|
|
{
|
|
valid = 1;
|
|
*write++ = 1; /* PS|reserved|Page Code */
|
|
plen = write;
|
|
*write++ = 0x00; /* Page Length (n - 1) ... Length in bytes of the mode parameters that follow */
|
|
|
|
*write++ = 0x00; /* +2 Error recovery Parameter AWRE|ARRE|TB|RC|Reserved|PER|DTE|DCR */
|
|
*write++ = 3; /* +3 Read Retry Count */
|
|
*write++ = 0x00; /* +4 Reserved */
|
|
*write++ = 0x00; /* +5 Reserved */
|
|
*write++ = 0x00; /* +6 Reserved */
|
|
*write++ = 0x00; /* +7 Reserved */
|
|
|
|
*plen = write - plen - 1;
|
|
}
|
|
|
|
/* CD-ROM audio control MMC-3 Section 6.3.7 table 354 */
|
|
/* also MMC-1 Section 5.2.3.1 table 97 */
|
|
if (page == 0x0E || page == 0x3F)
|
|
{
|
|
valid = 1;
|
|
*write++ = 0x0E; /* PS|reserved|Page Code */
|
|
plen = write;
|
|
*write++ = 0x00; /* Page Length (n - 1) ... Length in bytes of the mode parameters that follow */
|
|
|
|
*write++ = 0x04; /* +2 Reserved|IMMED=1|SOTC=0|Reserved */
|
|
*write++ = 0x00; /* +3 Reserved */
|
|
*write++ = 0x00; /* +4 Reserved */
|
|
*write++ = 0x00; /* +5 Reserved */
|
|
*write++ = 0x00; /* +6 Obsolete (75) */
|
|
*write++ = 75; /* +7 Obsolete (75) */
|
|
*write++ = 0x01; /* +8 output port 0 selection (0001b = channel 0) */
|
|
*write++ = (uint8_t)((drv->volume_l * 256) - 1); /* +9 output port 0 volume (0xFF = 0dB atten.) */
|
|
*write++ = 0x02; /* +10 output port 1 selection (0010b = channel 1) */
|
|
*write++ = (uint8_t)((drv->volume_l * 256) - 1); /* +11 output port 1 volume (0xFF = 0dB atten.) */
|
|
*write++ = 0x00; /* +12 output port 2 selection (none) */
|
|
*write++ = 0x00; /* +13 output port 2 volume (0x00 = mute) */
|
|
*write++ = 0x00; /* +14 output port 3 selection (none) */
|
|
*write++ = 0x00; /* +15 output port 3 volume (0x00 = mute) */
|
|
|
|
*plen = write - plen - 1;
|
|
}
|
|
|
|
/* CD-ROM mechanical status MMC-3 Section 6.3.11 table 361 */
|
|
if (page == 0x2A || page == 0x3F)
|
|
{
|
|
valid = 1;
|
|
*write++ = 0x2A; /* PS|reserved|Page Code */
|
|
plen = write;
|
|
*write++ = 0x00; /* Page Length (n - 1) ... Length in bytes of the mode parameters that follow */
|
|
|
|
/* MSB | | | | | | | LSB */
|
|
*write++ = 0x07; /* +2 Reserved |Reserved |DVD-RAM read |DVD-R read |DVD-ROM read | Method 2 | CD-RW read | CD-R read */
|
|
*write++ = 0x00; /* +3 Reserved |Reserved |DVD-RAM write|DVD-R write | Reserved | Test Write | CD-RW write | CD-R write */
|
|
*write++ = 0x71; /* +4 Buffer Underrun|Multisession |Mode 2 form 2|Mode 2 form 1|Digital Port 2|Digital Port 1 | Composite | Audio play */
|
|
*write++ = 0xFF; /* +5 Read code bar |UPC |ISRC |C2 Pointers |R-W deintcorr | R-W supported |CDDA accurate |CDDA support */
|
|
*write++ = 0x2F; /* +6 Loading mechanism type |Reserved |Eject |Prevent Jumper |Lock state |Lock */
|
|
/* 0 (0x00) = Caddy
|
|
* 1 (0x20) = Tray
|
|
* 2 (0x40) = Popup
|
|
* 3 (0x60) = Reserved
|
|
* 4 (0x80) = Changer with indivually changeable discs
|
|
* 5 (0xA0) = Changer using a magazine mechanism
|
|
* 6 (0xC0) = Reserved
|
|
* 6 (0xE0) = Reserved */
|
|
*write++ = 0x03; /* +7 Reserved |Reserved |R-W in leadin|Side chg cap |S/W slot sel |Changer disc pr|Sep. ch. mute |Sep. volume levels */
|
|
|
|
x = 176 * 8; /* +8 maximum speed supported in kB: 8X (obsolete in MMC-3) */
|
|
*write++ = x >> 8;
|
|
*write++ = x;
|
|
|
|
x = 256; /* +10 Number of volume levels supported */
|
|
*write++ = x >> 8;
|
|
*write++ = x;
|
|
|
|
x = 6 * 256; /* +12 buffer size supported by drive in kB */
|
|
*write++ = x >> 8;
|
|
*write++ = x;
|
|
|
|
x = 176 * 8; /* +14 current read speed selected in kB: 8X (obsolete in MMC-3) */
|
|
*write++ = x >> 8;
|
|
*write++ = x;
|
|
|
|
*plen = write - plen - 1;
|
|
}
|
|
|
|
if (!valid)
|
|
{
|
|
*write++ = page; /* PS|reserved|Page Code */
|
|
*write++ = 0x06; /* Page Length (n - 1) ... Length in bytes of the mode parameters that follow */
|
|
|
|
memset(write, 0, 6); write += 6;
|
|
printf("WARNING: MODE SENSE on page 0x%02x not supported\n", page);
|
|
}
|
|
|
|
/* mode param header, data length */
|
|
x = (uint32_t)(write - ide_buf) - 2;
|
|
ide_buf[0] = (unsigned char)(x >> 8u);
|
|
ide_buf[1] = (unsigned char)x;
|
|
|
|
hexdump(ide_buf, x + 2);
|
|
return x + 2;
|
|
}
|
|
|
|
static int get_subchan(drive_t *drv, unsigned char& attr, unsigned char& track_num, unsigned char& index, TMSF& relative_msf, TMSF& absolute_msf)
|
|
{
|
|
attr = 0;
|
|
track_num = 1;
|
|
index = 1;
|
|
relative_msf.min = relative_msf.fr = 0; relative_msf.sec = 2;
|
|
absolute_msf.min = absolute_msf.fr = 0; absolute_msf.sec = 2;
|
|
|
|
if (drv->play_start_lba == 0xFFFFFFFF) return 0;
|
|
|
|
//TODO: use current play position when audio playback will be implemented
|
|
uint32_t cur_pos = drv->play_start_lba;
|
|
bool is_index0;
|
|
track_t *cur_track = NULL;
|
|
cur_track = get_track_from_lba(drv, cur_pos, is_index0);
|
|
|
|
if (cur_track)
|
|
{
|
|
track_num = cur_track->number;
|
|
attr = cur_track->attr;
|
|
absolute_msf = frames_to_msf(cur_pos + REDBOOK_FRAME_PADDING);
|
|
int relative_diff = cur_pos - cur_track->start;
|
|
|
|
relative_msf = frames_to_msf(abs(relative_diff));
|
|
index = is_index0 ? 0 : 1;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_subchannel(drive_t *drv, uint8_t* cmdbuf)
|
|
{
|
|
unsigned char paramList = cmdbuf[3];
|
|
unsigned char attr, track, index;
|
|
bool SUBQ = !!(cmdbuf[2] & 0x40);
|
|
bool TIME = !!(cmdbuf[1] & 2);
|
|
unsigned char *write;
|
|
TMSF rel, abs;
|
|
|
|
if (paramList == 0 || paramList > 3)
|
|
{
|
|
printf("ATAPI READ SUBCHANNEL unknown param list\n");
|
|
memset(ide_buf, 0, 8);
|
|
return 8;
|
|
}
|
|
else if (paramList == 2)
|
|
{
|
|
printf("ATAPI READ SUBCHANNEL Media Catalog Number not supported\n");
|
|
memset(ide_buf, 0, 8);
|
|
return 8;
|
|
}
|
|
else if (paramList == 3) {
|
|
printf("ATAPI READ SUBCHANNEL ISRC not supported\n");
|
|
memset(ide_buf, 0, 8);
|
|
return 8;
|
|
}
|
|
|
|
/* get current subchannel position */
|
|
if (!get_subchan(drv, attr, track, index, rel, abs))
|
|
{
|
|
printf("ATAPI READ SUBCHANNEL unable to read current pos\n");
|
|
memset(ide_buf, 0, 8);
|
|
return 8;
|
|
}
|
|
|
|
memset(ide_buf, 0, 8);
|
|
write = ide_buf;
|
|
*write++ = 0x00;
|
|
*write++ = (!drv->playing) ? 0x13 : drv->paused ? 0x12 : 0x11; /* AUDIO STATUS */
|
|
*write++ = 0x00; /* SUBCHANNEL DATA LENGTH */
|
|
*write++ = 0x00;
|
|
|
|
if (SUBQ)
|
|
{
|
|
*write++ = 0x01; /* subchannel data format code */
|
|
*write++ = (attr >> 4) | 0x10; /* ADR/CONTROL */
|
|
*write++ = track;
|
|
*write++ = index;
|
|
if (TIME)
|
|
{
|
|
*write++ = 0x00;
|
|
*write++ = abs.min;
|
|
*write++ = abs.sec;
|
|
*write++ = abs.fr;
|
|
*write++ = 0x00;
|
|
*write++ = rel.min;
|
|
*write++ = rel.sec;
|
|
*write++ = rel.fr;
|
|
}
|
|
else
|
|
{
|
|
uint32_t sec;
|
|
|
|
sec = (abs.min * 60u * 75u) + (abs.sec * 75u) + abs.fr - 150u;
|
|
*write++ = (unsigned char)(sec >> 24u);
|
|
*write++ = (unsigned char)(sec >> 16u);
|
|
*write++ = (unsigned char)(sec >> 8u);
|
|
*write++ = (unsigned char)(sec >> 0u);
|
|
|
|
sec = (rel.min * 60u * 75u) + (rel.sec * 75u) + rel.fr - 150u;
|
|
*write++ = (unsigned char)(sec >> 24u);
|
|
*write++ = (unsigned char)(sec >> 16u);
|
|
*write++ = (unsigned char)(sec >> 8u);
|
|
*write++ = (unsigned char)(sec >> 0u);
|
|
}
|
|
}
|
|
|
|
unsigned int x = (unsigned int)(write - ide_buf) - 4;
|
|
ide_buf[2] = x >> 8;
|
|
ide_buf[3] = x;
|
|
|
|
dbg_hexdump(ide_buf, write - ide_buf);
|
|
return write - ide_buf;
|
|
}
|
|
|
|
static void pkt_send(ide_config *ide, void *data, uint16_t size)
|
|
{
|
|
ide->regs.pkt_io_size = (size + 1) / 2;
|
|
ide_send_data(data, ide->regs.pkt_io_size);
|
|
|
|
ide->regs.cylinder = size;
|
|
ide->regs.sector_count = 2;
|
|
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_DRQ | ATA_STATUS_IRQ;
|
|
ide_set_regs(ide);
|
|
ide->state = IDE_STATE_WAIT_PKT_RD;
|
|
}
|
|
|
|
static void read_cd_sectors(ide_config *ide, track_t *track, int cnt)
|
|
{
|
|
drive_t *drv = &ide->drive[ide->regs.drv];
|
|
uint32_t sz = drv->track[drv->data_num].sectorSize;
|
|
|
|
if (sz == 2048)
|
|
{
|
|
if (!ide->null) ide->null = (FileReadAdv(&track->f, ide_buf, cnt * sz, -1) <= 0);
|
|
if (ide->null) memset(ide_buf, 0, cnt * sz);
|
|
return;
|
|
}
|
|
|
|
uint32_t pre = drv->track[drv->data_num].mode2 ? 24 : 16;
|
|
uint32_t post = sz - pre - 2048;
|
|
uint32_t off = 0;
|
|
|
|
while (cnt--)
|
|
{
|
|
if (!ide->null) ide->null = !FileSeek(&track->f, pre, SEEK_CUR);
|
|
if (!ide->null) ide->null = (FileReadAdv(&track->f, ide_buf + off, 2048, -1) <= 0);
|
|
if (ide->null) memset(ide_buf + off, 0, 2048);
|
|
if (!ide->null) ide->null = !FileSeek(&track->f, post, SEEK_CUR);
|
|
off += 2048;
|
|
}
|
|
}
|
|
|
|
void cdrom_read(ide_config *ide)
|
|
{
|
|
bool is_index0 = false;
|
|
uint32_t cnt = ide->regs.pkt_cnt;
|
|
drive_t *drive = &ide->drive[ide->regs.drv];
|
|
|
|
if ((cnt * 4) > ide_io_max_size) cnt = ide_io_max_size / 4;
|
|
|
|
while ((cnt * 2048) > ide->regs.pkt_size_limit)
|
|
{
|
|
if (cnt <= 1) break;
|
|
cnt--;
|
|
}
|
|
|
|
|
|
if (cnt != ide->regs.pkt_cnt)
|
|
{
|
|
dbg_printf("** partial CD read\n");
|
|
}
|
|
|
|
|
|
track_t *track = get_track_from_lba(drive, ide->regs.pkt_lba, is_index0);
|
|
|
|
if (ide->state == IDE_STATE_INIT_RW && !drive->chd_f && track)
|
|
{
|
|
uint32_t pos = track->skip + (ide->regs.pkt_lba - track->start) * track->sectorSize;
|
|
ide->null = (FileSeek(&track->f, pos, SEEK_SET) < 0);
|
|
}
|
|
|
|
|
|
if (drive->chd_f) {
|
|
|
|
uint32_t hdr = drive->track[drive->data_num].mode2 ? 24 : 16;
|
|
if (drive->track[drive->data_num].sectorSize == 2048)
|
|
{
|
|
hdr = 0;
|
|
}
|
|
uint32_t d_offset = 0;
|
|
|
|
if (ide->state == IDE_STATE_INIT_RW)
|
|
{
|
|
drive->chd_last_partial_lba = ide->regs.pkt_lba;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < cnt; i++)
|
|
{
|
|
|
|
if (mister_chd_read_sector(drive->chd_f, drive->chd_last_partial_lba + drive->track[drive->data_num].chd_offset, d_offset, hdr, 2048, ide_buf, drive->chd_hunkbuf, &drive->chd_hunknum) != CHDERR_NONE)
|
|
{
|
|
//I don't think anything else uses this, but set it just in case.
|
|
ide->null = 1;
|
|
memset(ide_buf + d_offset, 0, 2048);
|
|
}
|
|
else
|
|
{
|
|
ide->null = 0;
|
|
}
|
|
d_offset += 2048;
|
|
drive->chd_last_partial_lba++;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
read_cd_sectors(ide, track, cnt);
|
|
}
|
|
|
|
dbg_printf("\nsector:\n");
|
|
dbg_hexdump(ide_buf, 512, 0);
|
|
|
|
ide->regs.pkt_cnt -= cnt;
|
|
pkt_send(ide, ide_buf, cnt * 2048);
|
|
}
|
|
|
|
static int disc_info(drive_t *drv, uint16_t maxlen)
|
|
{
|
|
if (!maxlen) return 0;
|
|
if (maxlen > 34) maxlen = 34;
|
|
|
|
memset(ide_buf, 0, 34);
|
|
ide_buf[1] = 32; /* 0-1: Data Length excluding itself */
|
|
ide_buf[2] = 0xe; /* Complete Status, Complete Session */
|
|
ide_buf[3] = 1; /* Number of first track on disc */
|
|
ide_buf[4] = 1; /* Number of Sessions */
|
|
ide_buf[5] = 1; /* First Track Number in Last Session */
|
|
ide_buf[6] = drv->track_cnt;/* Last Track Number in Last Session */
|
|
ide_buf[7] = 0x20; /* Disc defined for unrestricted use */
|
|
ide_buf[8] = 0x00; /* CD-Rom Disk */
|
|
|
|
|
|
memset(ide_buf+16, 0xFF, 4); /* Lead-in Start Time for Last Session, all 0xFF if disc is complete*/
|
|
memset(ide_buf+20, 0xFF, 4); /* Last Possible Start Time for Start Time of Lead-out, all 0xFF if disc is complete*/
|
|
|
|
dbg_hexdump(ide_buf, maxlen, 0);
|
|
return maxlen;
|
|
}
|
|
|
|
static int track_info(drive_t *drv, uint8_t track_number, uint16_t maxlen)
|
|
{
|
|
if (!maxlen) return 0;
|
|
if (maxlen > 24) maxlen = 24;
|
|
|
|
memset(ide_buf, 0, 24);
|
|
ide_buf[1] = 20; /* 0-1: Data Length excluding itself */
|
|
ide_buf[2] = track_number; /* Track Number*/
|
|
ide_buf[3] = 1; /* Session Nuber (hardcoded to 1) */
|
|
ide_buf[5] = 0x01 & 0x0F; /* 4 bit Subcode-Q Mode 1 Identifier */
|
|
ide_buf[6] = (drv->track[track_number].mode2 ? 0x02 : 0x01) & 0x0F; /* RT = Packet = FP = 0, 4 bit Data Mode */
|
|
ide_buf[8] = bswap_32(drv->track[track_number].start); /* 4 Byte Track Start Address */
|
|
|
|
dbg_hexdump(ide_buf, maxlen, 0);
|
|
return maxlen;
|
|
}
|
|
|
|
static int cd_inquiry(uint8_t maxlen)
|
|
{
|
|
static const char vendor[] = "MiSTer ";
|
|
static const char product[] = "CDROM ";
|
|
|
|
memset(ide_buf, 0, 47);
|
|
ide_buf[0] = (0 << 5) | 5; /* Peripheral qualifier=0 device type=5 (CDROM) */
|
|
ide_buf[1] = 0x80; /* RMB=1 removable media */
|
|
ide_buf[2] = 0x00; /* ANSI version */
|
|
ide_buf[3] = 0x21;
|
|
ide_buf[4] = maxlen - 5; /* additional length */
|
|
|
|
for (int i = 0; i < 8; i++) ide_buf[i + 8] = (unsigned char)vendor[i];
|
|
for (int i = 0; i < 16; i++) ide_buf[i + 16] = (unsigned char)product[i];
|
|
for (int i = 0; i < 4; i++) ide_buf[i + 32] = ' ';
|
|
for (int i = 0; i < 11; i++) ide_buf[i + 36] = ' ';
|
|
hexdump(ide_buf, maxlen);
|
|
return maxlen;
|
|
}
|
|
|
|
static void get_conf(ide_config *ide, uint8_t* cmdbuf, uint16_t maxlen) {
|
|
if (cmdbuf[2] == 0 && cmdbuf[3] == 0) {
|
|
if (maxlen == 0) {
|
|
cdrom_reply(ide,0);
|
|
return;
|
|
}
|
|
if (maxlen > 16) {
|
|
maxlen = 16;
|
|
}
|
|
|
|
memset(ide_buf, 0, 16);
|
|
ide_buf[3] = 0x0F; // Length LSB (Word 0-3)
|
|
// Word 4 Reserved
|
|
// Word 5 Reserved
|
|
ide_buf[7] = 0x08; // Current Profile (CD-ROM) LSB (Word 6-7)
|
|
// Feature 0000h // Feature Code: Profile List (Word 8-9)
|
|
ide_buf[10] = 0x02 | 0x01; // Persistent, Current (Word 10)
|
|
ide_buf[11] = 0x04; // Additional Length: profile descriptors * 4 (Word 11)
|
|
ide_buf[13] = 0x08; // CD-ROM Profile Descriptor (Word 12-13)
|
|
ide_buf[14] = 0x01; // Current (Word 14)
|
|
// Word 15 reserved
|
|
|
|
dbg_hexdump(ide_buf, maxlen);
|
|
pkt_send(ide, ide_buf, maxlen);
|
|
}
|
|
else
|
|
{
|
|
printf("(!) Error in packet command %02X\n", cmdbuf[0]);
|
|
hexdump(cmdbuf, 12, 0);
|
|
cdrom_reply(ide, CD_ERR_ILLEGAL_REQUEST, CD_ASC_CODE_ILLEGAL_FIELD_CMD_PACKET);
|
|
}
|
|
}
|
|
|
|
static void set_sense(uint8_t SK, uint8_t ASC = 0, uint8_t ASCQ = 0)
|
|
{
|
|
int len = 18;
|
|
memset(ide_buf, 0, len);
|
|
|
|
ide_buf[0] = 0x70; /* RESPONSE CODE */
|
|
ide_buf[2] = SK & 0xF; /* SENSE KEY */
|
|
ide_buf[7] = len - 18; /* additional sense length */
|
|
ide_buf[12] = ASC;
|
|
ide_buf[13] = ASCQ;
|
|
}
|
|
|
|
static int get_sense(drive_t *drv)
|
|
{
|
|
switch (drv->load_state)
|
|
{
|
|
case 3:
|
|
set_sense(2, 0x3A);
|
|
break;
|
|
|
|
case 2:
|
|
set_sense(2, 4, 1);
|
|
drv->load_state--;
|
|
break;
|
|
|
|
case 1:
|
|
set_sense(2, 28, 0);
|
|
drv->load_state--;
|
|
break;
|
|
|
|
default:
|
|
set_sense(drv->atapi_sense_key, drv->atapi_asc_code, drv->atapi_ascq_code);
|
|
break;
|
|
}
|
|
|
|
dbg_hexdump(ide_buf, 18);
|
|
return 18;
|
|
}
|
|
|
|
static bool pause_resume(drive_t *drv, uint8_t *cmdbuf)
|
|
{
|
|
bool resume = !!(cmdbuf[8] & 1);
|
|
if (drv->playing)
|
|
{
|
|
drv->paused = !resume;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void play_audio_msf(drive_t *drv, uint8_t *cmdbuf)
|
|
{
|
|
if (cmdbuf[3] == 0xFF && cmdbuf[4] == 0xFF && cmdbuf[5] == 0xFF)
|
|
{
|
|
drv->play_start_lba = 0xFFFFFFFF;
|
|
}
|
|
else
|
|
{
|
|
drv->play_start_lba = (cmdbuf[3] * 60u * 75u) + (cmdbuf[4] * 75u) + cmdbuf[5];
|
|
|
|
if (drv->play_start_lba >= 150u) drv->play_start_lba -= 150u; /* LBA sector 0 == M:S:F sector 0:2:0 */
|
|
else drv->play_start_lba = 0;
|
|
}
|
|
|
|
if (cmdbuf[6] == 0xFF && cmdbuf[7] == 0xFF && cmdbuf[8] == 0xFF)
|
|
{
|
|
drv->play_end_lba = 0xFFFFFFFF;
|
|
}
|
|
else
|
|
{
|
|
drv->play_end_lba = (cmdbuf[6] * 60u * 75u) + (cmdbuf[7] * 75u) + cmdbuf[8];
|
|
if (drv->play_end_lba >= 150u) drv->play_end_lba -= 150u; /* LBA sector 0 == M:S:F sector 0:2:0 */
|
|
else drv->play_end_lba = 0;
|
|
}
|
|
|
|
if (drv->play_start_lba == drv->play_end_lba)
|
|
{
|
|
drv->playing = 0;
|
|
drv->paused = 0;
|
|
return;
|
|
}
|
|
|
|
/* LBA 0xFFFFFFFF means start playing wherever the optics of the CD sit */
|
|
if (drv->play_start_lba != 0xFFFFFFFF)
|
|
{
|
|
drv->playing = 1;
|
|
drv->paused = 0;
|
|
}
|
|
else
|
|
{
|
|
drv->playing = 0;
|
|
drv->paused = 1;
|
|
}
|
|
}
|
|
|
|
void play_audio10(drive_t *drv, uint8_t *cmdbuf)
|
|
{
|
|
uint16_t play_length;
|
|
|
|
drv->play_start_lba = ((uint32_t)cmdbuf[2] << 24) + ((uint32_t)cmdbuf[3] << 16) + ((uint32_t)cmdbuf[4] << 8) + ((uint32_t)cmdbuf[5] << 0);
|
|
play_length = ((uint16_t)cmdbuf[7] << 8) + ((uint16_t)cmdbuf[8] << 0);
|
|
drv->play_end_lba = drv->play_start_lba + play_length;
|
|
|
|
if (play_length == 0)
|
|
{
|
|
drv->playing = 0;
|
|
drv->paused = 0;
|
|
return;
|
|
}
|
|
|
|
/* LBA 0xFFFFFFFF means start playing wherever the optics of the CD sit */
|
|
if (drv->play_start_lba != 0xFFFFFFFF)
|
|
{
|
|
drv->playing = 1;
|
|
drv->paused = 0;
|
|
}
|
|
else
|
|
{
|
|
drv->playing = 0;
|
|
drv->paused = 1;
|
|
}
|
|
}
|
|
|
|
static void cdrom_nodisk(ide_config *ide)
|
|
{
|
|
drive_t *drv = &ide->drive[ide->regs.drv];
|
|
drv->last_load_state = drv->load_state;
|
|
if (drv->load_state && drv->load_state < 3) drv->load_state--;
|
|
|
|
cdrom_reply(ide, CD_ERR_NO_DISK);
|
|
}
|
|
|
|
void cdrom_handle_pkt(ide_config *ide)
|
|
{
|
|
drive_t *drv = &ide->drive[ide->regs.drv];
|
|
uint8_t cmdbuf[16];
|
|
|
|
ide_recv_data(cmdbuf, 6);
|
|
ide_reset_buf();
|
|
dbg_hexdump(cmdbuf, 12, 0);
|
|
|
|
ide->regs.pkt_cnt = 0;
|
|
int err = 0;
|
|
|
|
//See MMC-5 section 4.1.6.1
|
|
//If the no disk/load state isn't "done", most commands need to return CHECK CONDITION+sense data.
|
|
//The only commands that ignore this are the ones listed below.
|
|
//GET CONFIG ,GET EVENT STATUS NOTIFICATION, INQUIRY, REQUEST SENSE
|
|
//0x46, 0x4A, 0x12, 0x3h
|
|
if (drv->load_state || drv->mcr_flag)
|
|
{
|
|
if ((cmdbuf[0] != 0x46) && (cmdbuf[0] != 0x4A) && (cmdbuf[0] != 0x12) && (cmdbuf[0] != 0x3))
|
|
{
|
|
cdrom_nodisk(ide);
|
|
return;
|
|
}
|
|
}
|
|
switch (cmdbuf[0])
|
|
{
|
|
case 0xA8: // read(12) sectors
|
|
case 0x28: // read(10) sectors
|
|
dbg_printf("** Read Sectors\n");
|
|
//hexdump(cmdbuf, 12, 0);
|
|
ide->regs.pkt_cnt = (cmdbuf[0] == 0x28) ? ((cmdbuf[7] << 8) | cmdbuf[8]) : ((cmdbuf[6] << 24) | (cmdbuf[7] << 16) | (cmdbuf[8] << 8) | cmdbuf[9]);
|
|
ide->regs.pkt_lba = ((cmdbuf[2] << 24) | (cmdbuf[3] << 16) | (cmdbuf[4] << 8) | cmdbuf[5]);
|
|
if (!ide->regs.pkt_cnt)
|
|
{
|
|
dbg_printf(" length 0 is not a error.\n");
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
}
|
|
|
|
dbg_printf("** par: lba = %d, cnt = %d, load_state = %d\n", ide->regs.pkt_lba, ide->regs.pkt_cnt, drv->load_state);
|
|
ide->state = IDE_STATE_INIT_RW;
|
|
if (!drv->load_state) cdrom_read(ide);
|
|
else cdrom_nodisk(ide);
|
|
break;
|
|
|
|
case 0x25: // read capacity
|
|
dbg_printf("** Read Capacity\n");
|
|
if (!drv->load_state)
|
|
{
|
|
uint32_t tmp = 0;
|
|
|
|
tmp = drv->track[drv->track_cnt-1].start;
|
|
|
|
ide_buf[0] = tmp >> 24;
|
|
ide_buf[1] = tmp >> 16;
|
|
ide_buf[2] = tmp >> 8;
|
|
ide_buf[3] = tmp;
|
|
|
|
tmp = 2048;
|
|
ide_buf[4] = tmp >> 24;
|
|
ide_buf[5] = tmp >> 16;
|
|
ide_buf[6] = tmp >> 8;
|
|
ide_buf[7] = tmp;
|
|
//hexdump(buf, 8, 0);
|
|
pkt_send(ide, ide_buf, 8);
|
|
}
|
|
else cdrom_nodisk(ide);
|
|
break;
|
|
|
|
case 0x2B: // seek
|
|
dbg_printf("** Seek\n");
|
|
drv->playing = 0;
|
|
drv->paused = 0;
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
|
|
case 0x1B: //START STOP UNIT
|
|
dbg_printf("** Start Stop Unit\n");
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
|
|
case 0x1E: // lock the cd door - doing nothing.
|
|
dbg_printf("** Lock Door\n");
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
|
|
case 0x5A: // mode sense
|
|
dbg_printf("** Mode Sense\n");
|
|
pkt_send(ide, ide_buf, mode_sense(drv, cmdbuf[2]));
|
|
break;
|
|
|
|
case 0x42: // read sub
|
|
dbg_printf("** read sub:\n");
|
|
if (!drv->load_state)
|
|
{
|
|
pkt_send(ide, ide_buf, read_subchannel(drv, cmdbuf));
|
|
}
|
|
else cdrom_nodisk(ide);
|
|
break;
|
|
|
|
case 0x43: // read TOC
|
|
dbg_printf("** Read TOC\n");
|
|
if (!drv->load_state)
|
|
{
|
|
pkt_send(ide, ide_buf, read_toc(drv, cmdbuf));
|
|
}
|
|
else cdrom_nodisk(ide);
|
|
break;
|
|
|
|
case 0x4E: // stop play/scan
|
|
dbg_printf("** Stop Play/Scan\n");
|
|
drv->playing = 0;
|
|
drv->paused = 0;
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
|
|
case 0x12: // inquiry
|
|
dbg_printf("** Inquiry\n");
|
|
pkt_send(ide, ide_buf, cd_inquiry(cmdbuf[4]));
|
|
break;
|
|
|
|
case 0x35: // synchronize cache
|
|
dbg_printf("** synchronize cache\n");
|
|
dbg_hexdump(cmdbuf, 10, 0);
|
|
cdrom_reply(ide,0);
|
|
break;
|
|
|
|
case 0x46: // get configuration
|
|
dbg_printf("** get configuration\n");
|
|
get_conf(ide, cmdbuf, cmdbuf[7] << 8 | cmdbuf[8]);
|
|
break;
|
|
|
|
case 0x51: // read disc information
|
|
dbg_printf("** read disc information\n");
|
|
dbg_hexdump(cmdbuf, 12, 0);
|
|
if ((cmdbuf[1] & 7) == 0)
|
|
{
|
|
pkt_send(ide, ide_buf, disc_info(drv, cmdbuf[7] << 8 | cmdbuf[8]));
|
|
}
|
|
else err = 1;
|
|
break;
|
|
|
|
case 0x52: // read track information
|
|
dbg_printf("** read track information\n");
|
|
dbg_hexdump(cmdbuf, 12, 0);
|
|
if (cmdbuf[1]==1)
|
|
{
|
|
if (cmdbuf[5] > drv->track_cnt || cmdbuf[5] == 0xFF)
|
|
{
|
|
err = 1;
|
|
break;
|
|
}
|
|
pkt_send(ide, ide_buf, track_info(drv, cmdbuf[5], cmdbuf[7] << 8 | cmdbuf[8]));
|
|
}
|
|
else err = 1;
|
|
break;
|
|
|
|
case 0x03: // mode sense
|
|
dbg_printf("** get sense:\n");
|
|
pkt_send(ide, ide_buf, get_sense(drv));
|
|
break;
|
|
|
|
|
|
case 0x55: // mode select
|
|
dbg_printf("** mode select\n");
|
|
ide->regs.cylinder = (cmdbuf[7] << 8) | cmdbuf[8];
|
|
if (ide->regs.cylinder > 512) ide->regs.cylinder = 512;
|
|
ide->regs.pkt_io_size = (ide->regs.cylinder + 1) / 2;
|
|
ide->regs.sector_count = 0;
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_DRQ | ATA_STATUS_IRQ;
|
|
ide_set_regs(ide);
|
|
ide->state = IDE_STATE_WAIT_PKT_MODE;
|
|
break;
|
|
|
|
case 0x00: // test unit ready
|
|
dbg_printf("** Test Unit Ready\n");
|
|
if (!drv->load_state) cdrom_reply(ide, 0, false);
|
|
else cdrom_nodisk(ide);
|
|
break;
|
|
|
|
case 0x45: // play lba
|
|
dbg_printf("** CD PLAY AUDIO(10)\n");
|
|
play_audio10(drv, cmdbuf);
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
|
|
case 0x47: // play msf
|
|
dbg_printf("** CD PLAY AUDIO MSF\n");
|
|
play_audio_msf(drv, cmdbuf);
|
|
cdrom_reply(ide, 0);
|
|
break;
|
|
|
|
case 0x4B: // pause/resume
|
|
dbg_printf("** CD PAUSE/RESUME\n");
|
|
cdrom_reply(ide, pause_resume(drv, cmdbuf) ? 0 : CD_ERR_ILLEGAL_REQUEST, CD_ASC_CODE_COMMAND_SEQUENCE_ERR);
|
|
break;
|
|
|
|
default:
|
|
err = 1;
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
{
|
|
printf("(!) Error in packet command %02X\n", cmdbuf[0]);
|
|
hexdump(cmdbuf, 12, 0);
|
|
cdrom_reply(ide, CD_ERR_ILLEGAL_REQUEST, CD_ASC_CODE_ILLEGAL_OPCODE);
|
|
}
|
|
}
|
|
|
|
int cdrom_handle_cmd(ide_config *ide)
|
|
{
|
|
uint8_t drv;
|
|
|
|
switch (ide->regs.cmd)
|
|
{
|
|
case 0xA1: // identify packet
|
|
//print_regs(&ide->regs);
|
|
dbg_printf("identify packet\n");
|
|
ide_send_data(ide->drive[ide->regs.drv].id, 256);
|
|
drv = ide->regs.drv;
|
|
memset(&ide->regs, 0, sizeof(ide->regs));
|
|
ide->regs.drv = drv;
|
|
ide->regs.pkt_io_size = 256;
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_DRQ | ATA_STATUS_IRQ | ATA_STATUS_END;
|
|
ide_set_regs(ide);
|
|
ide->state = IDE_STATE_IDLE;
|
|
break;
|
|
|
|
case 0xEC: // identify (fail)
|
|
dbg_printf("identify (CD)\n");
|
|
ide->regs.sector = 1;
|
|
ide->regs.sector_count = 1;
|
|
ide->regs.cylinder = 0xEB14;
|
|
ide->regs.head = 0;
|
|
ide->regs.io_size = 0;
|
|
return 1;
|
|
|
|
case 0xA0: // packet
|
|
dbg_printf("cmd A0: %02X\n", ide->regs.features);
|
|
if (ide->regs.features & 1)
|
|
{
|
|
dbg_printf("Cancel A0 DMA transfer\n");
|
|
return 1;
|
|
}
|
|
ide->regs.pkt_size_limit = ide->regs.cylinder;
|
|
if (!ide->regs.pkt_size_limit) ide->regs.pkt_size_limit = ide_io_max_size * 512;
|
|
ide->regs.pkt_io_size = 6;
|
|
ide->regs.sector_count = 1;
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_DRQ; // | ATA_STATUS_IRQ;
|
|
ide_set_regs(ide);
|
|
ide->state = IDE_STATE_WAIT_PKT_CMD;
|
|
break;
|
|
|
|
case 0x08: // reset
|
|
dbg_printf("cmd 08\n");
|
|
ide->drive[ide->regs.drv].playing = 0;
|
|
ide->drive[ide->regs.drv].paused = 0;
|
|
ide->regs.sector = 1;
|
|
ide->regs.sector_count = 1;
|
|
ide->regs.cylinder = 0xEB14;
|
|
ide->regs.head = 0;
|
|
ide->regs.io_size = 0;
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_DSC;
|
|
ide_set_regs(ide);
|
|
break;
|
|
|
|
case 0xEF: // set features
|
|
switch(ide->regs.features)
|
|
{
|
|
case 0x03:
|
|
dbg_printf("Ignoring Set Features Transfer Mode: %02x\n", ide->regs.features);
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_IRQ;
|
|
ide_set_regs(ide);
|
|
break;
|
|
|
|
default:
|
|
dbg_printf("Unsupported feature %02x", ide->regs.features);
|
|
dbg_print_regs(&ide->regs);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case 0x00: // nop
|
|
dbg_printf("cmd 00\n");
|
|
return 1; // must always fail
|
|
|
|
default:
|
|
printf("(!) Unsupported command\n");
|
|
ide_print_regs(&ide->regs);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//error is the atapi sense_key
|
|
void cdrom_reply(ide_config *ide, uint8_t error, uint8_t asc_code, uint8_t ascq_code, bool unit_attention)
|
|
{
|
|
ide->state = IDE_STATE_IDLE;
|
|
ide->regs.sector_count = 3;
|
|
if (ide->drive[ide->regs.drv].mcr_flag && unit_attention) {
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_IRQ | ATA_STATUS_ERR;
|
|
ide->regs.error = (CD_ERR_UNIT_ATTENTION << 4) | ATA_ERR_MC;
|
|
ide->drive[ide->regs.drv].mcr_flag = false;
|
|
}
|
|
else
|
|
{
|
|
ide->regs.status = ATA_STATUS_RDY | ATA_STATUS_IRQ | (error ? ATA_STATUS_ERR : 0);
|
|
ide->regs.error = error << 4;
|
|
ide->drive[ide->regs.drv].atapi_sense_key = error;
|
|
ide->drive[ide->regs.drv].atapi_asc_code = asc_code;
|
|
ide->drive[ide->regs.drv].atapi_ascq_code = ascq_code;
|
|
}
|
|
|
|
ide_set_regs(ide);
|
|
}
|
|
|
|
|
|
void cdrom_close_chd(drive_t *drv)
|
|
{
|
|
|
|
if (drv->chd_f)
|
|
{
|
|
chd_close(drv->chd_f);
|
|
drv->chd_f = NULL;
|
|
}
|
|
|
|
if (drv->chd_hunkbuf)
|
|
{
|
|
free(drv->chd_hunkbuf);
|
|
drv->chd_hunkbuf = NULL;
|
|
}
|
|
drv->chd_hunknum = -1;
|
|
}
|
|
|
|
const char* cdrom_parse(uint32_t num, const char *filename)
|
|
{
|
|
const char *res = 0;
|
|
|
|
int drv = num & 1;
|
|
num >>= 1;
|
|
|
|
//always close files and reset state. empty filename == unmounted cd from OSD
|
|
cdrom_close_chd(&ide_inst[num].drive[drv]);
|
|
for (uint8_t i = 0; i < sizeof(ide_inst[num].drive[drv].track) / sizeof(track_t); i++)
|
|
{
|
|
if (ide_inst[num].drive[drv].track[i].f.opened())
|
|
{
|
|
FileClose(&ide_inst[num].drive[drv].track[i].f);
|
|
}
|
|
}
|
|
ide_inst[num].drive[drv].mcr_flag = true;
|
|
ide_inst[num].drive[drv].playing = 0;
|
|
ide_inst[num].drive[drv].paused = 0;
|
|
ide_inst[num].drive[drv].play_start_lba = 0;
|
|
ide_inst[num].drive[drv].play_end_lba = 0;
|
|
if (strlen(filename))
|
|
{
|
|
const char *path = getFullPath(filename);
|
|
res = load_chd_file(&ide_inst[num].drive[drv], path);
|
|
if (!res) res = load_cue_file(&ide_inst[num].drive[drv], path);
|
|
if (!res) res = load_iso_file(&ide_inst[num].drive[drv], path);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void ide_cdda_send_sector()
|
|
{
|
|
bool is_index0 = false;
|
|
static uint8_t cdda_buf[BYTES_PER_RAW_REDBOOK_FRAME];
|
|
drive_t *drv = NULL;
|
|
ide_config *ide = NULL;
|
|
int ide_idx = -1;
|
|
for (ide_idx = 0; ide_idx < 2; ide_idx++)
|
|
{
|
|
for (int drv_idx = 0; drv_idx < 2; drv_idx++)
|
|
{
|
|
if (ide_inst[ide_idx].drive[drv_idx].playing == 1 && ide_inst[ide_idx].drive[drv_idx].paused == 0)
|
|
{
|
|
drv = &ide_inst[ide_idx].drive[drv_idx];
|
|
ide = &ide_inst[ide_idx];
|
|
break;
|
|
}
|
|
}
|
|
if (drv) break;
|
|
}
|
|
|
|
if (!drv || !ide) return;
|
|
|
|
bool needs_swap = false;
|
|
track_t *track = get_track_from_lba(drv, drv->play_start_lba, is_index0);
|
|
|
|
if (!track->attr)
|
|
{
|
|
if (drv->chd_f)
|
|
{
|
|
mister_chd_read_sector(drv->chd_f, drv->play_start_lba + drv->track[drv->data_num].chd_offset, 0, 0, BYTES_PER_RAW_REDBOOK_FRAME, cdda_buf, drv->chd_hunkbuf, &drv->chd_hunknum);
|
|
needs_swap = true;
|
|
}
|
|
else
|
|
{
|
|
//If we're in the index0 area "audio pregap", that data is actually in the
|
|
//previous track. Use that file object, seek and return the data from there.
|
|
//If the seek fails just return zero data.
|
|
//It may be a 'PREGAP' which indicates no stored data
|
|
|
|
track_t *read_track = track;
|
|
if (is_index0 && track->number > 1)
|
|
{
|
|
//track number is 1-based, track array is zero.
|
|
read_track = &drv->track[track->number-2];
|
|
|
|
}
|
|
uint32_t pos = read_track->skip + (drv->play_start_lba - read_track->start) * read_track->sectorSize;
|
|
if (FileSeek(&read_track->f, pos, SEEK_SET))
|
|
{
|
|
FileReadAdv(&read_track->f, cdda_buf, sizeof(cdda_buf), -1);
|
|
} else {
|
|
memset(cdda_buf, 0, sizeof(cdda_buf));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memset(cdda_buf, 0, sizeof(cdda_buf));
|
|
}
|
|
|
|
int16_t *cdda_buf16 = (int16_t *)cdda_buf;
|
|
const int buf_wsize = sizeof(cdda_buf) / 2;
|
|
|
|
for (int sidx = 0; sidx < buf_wsize; sidx++)
|
|
{
|
|
if (needs_swap) cdda_buf16[sidx] = bswap_16(cdda_buf16[sidx]);
|
|
double tmps = (double)cdda_buf16[sidx];
|
|
cdda_buf16[sidx] = (int16_t)(tmps*((sidx & 1) ? drv->volume_l : drv->volume_r));
|
|
}
|
|
|
|
ide_sendbuf(ide, 0x200, buf_wsize, (uint16_t *)cdda_buf);
|
|
|
|
drv->play_start_lba++;
|
|
if (drv->play_start_lba >= drv->play_end_lba)
|
|
{
|
|
drv->playing = 0;
|
|
drv->paused = 0;
|
|
}
|
|
}
|