diff --git a/Makefile.common b/Makefile.common index f5cd21c..28b86a6 100644 --- a/Makefile.common +++ b/Makefile.common @@ -5,6 +5,7 @@ DEPS_DIR := $(CORE_DIR)/deps LIBRETRO_DIR := $(CORE_DIR)/libretro-common MEDNAFEN_DIR := $(CORE_DIR)/mednafen CORE_EMU_DIR := $(MEDNAFEN_DIR)/ss +CDROM_DIR := $(MEDNAFEN_DIR)/cdrom INCFLAGS := -I$(CORE_DIR) -I$(MEDNAFEN_DIR) -I$(MEDNAFEN_DIR)/include -I$(MEDNAFEN_DIR)/intl -I$(MEDNAFEN_DIR)/hw_sound -I$(MEDNAFEN_DIR)/hw_cpu -I$(MEDNAFEN_DIR)/hw_misc -I$(LIBRETRO_DIR)/include -I$(DEPS_DIR)/zlib @@ -109,22 +110,21 @@ ifeq ($(NEED_TREMOR), 1) SOURCES_C += $(wildcard $(MEDNAFEN_DIR)/tremor/*.c) endif -ifeq ($(NEED_CD), 1) -SOURCES_CXX += $(MEDNAFEN_DIR)/cdrom/CDAccess.cpp \ - $(MEDNAFEN_DIR)/cdrom/CDAccess_Image.cpp \ - $(MEDNAFEN_DIR)/cdrom/CDAccess_CCD.cpp \ - $(MEDNAFEN_DIR)/cdrom/audioreader.cpp \ - $(MEDNAFEN_DIR)/cdrom/misc.cpp \ - $(MEDNAFEN_DIR)/cdrom/cdromif.cpp +ifneq ($(HAVE_GRIFFIN),1) +SOURCES_CXX += $(CDROM_DIR)/CDAccess.cpp \ + $(CDROM_DIR)/CDAccess_Image.cpp \ + $(CDROM_DIR)/CDAccess_CCD.cpp \ + $(CDROM_DIR)/CDAFReader.cpp \ + $(CDROM_DIR)/CDAFReader_Vorbis.cpp \ + $(CDROM_DIR)/cdromif.cpp \ + $(CDROM_DIR)/CDUtility.cpp \ + $(CDROM_DIR)/lec.cpp \ + $(CDROM_DIR)/galois.cpp \ + $(CDROM_DIR)/recover-raw.cpp \ + $(CDROM_DIR)/l-ec.cpp \ + $(CDROM_DIR)/edc_crc32.cpp endif - -SOURCES_C += \ - $(MEDNAFEN_DIR)/cdrom/CDUtility.c \ - $(MEDNAFEN_DIR)/cdrom/galois.c \ - $(MEDNAFEN_DIR)/cdrom/l-ec.c \ - $(MEDNAFEN_DIR)/cdrom/lec.c \ - $(MEDNAFEN_DIR)/cdrom/recover-raw.c \ - $(MEDNAFEN_DIR)/cdrom/edc_crc32.c + FLAGS += -DNEED_CD SOURCES_CXX += \ $(MEDNAFEN_DIR)/error.cpp \ diff --git a/libretro.cpp b/libretro.cpp index a610520..d3cdac9 100755 --- a/libretro.cpp +++ b/libretro.cpp @@ -1371,7 +1371,6 @@ static bool Load(MDFNFILE* fp) unsigned i; if(MDFN_GetSettingS("ss.dbg_exe_cdpath") != "") { - bool success; RMD_Drive dr; dr.Name = std::string("Virtual CD Drive"); @@ -1387,8 +1386,7 @@ static bool Load(MDFNFILE* fp) static std::vector CDInterfaces; CDInterfaces.clear(); - CDInterfaces.push_back(CDIF_Open(&success, MDFN_GetSettingS("ss.dbg_exe_cdpath").c_str(), false, - false)); + CDInterfaces.push_back(CDIF_Open(MDFN_GetSettingS("ss.dbg_exe_cdpath").c_str(), false)); cdifs = &CDInterfaces; } @@ -2250,14 +2248,14 @@ static MDFNGI *MDFNI_LoadCD(const char *devicename) for(unsigned i = 0; i < file_list.size(); i++) { bool success = true; - CDIF *image = CDIF_Open(&success, file_list[i].c_str(), false, old_cdimagecache); + CDIF *image = CDIF_Open(file_list[i].c_str(), false); CDInterfaces.push_back(image); } } else { bool success = true; - CDIF *image = CDIF_Open(&success, devicename, false, old_cdimagecache); + CDIF *image = CDIF_Open(devicename, false); log_cb(RETRO_LOG_INFO, "Pushing CD image onto stack: %s.\n", devicename); CDInterfaces.push_back(image); } @@ -2272,7 +2270,6 @@ static MDFNGI *MDFNI_LoadCD(const char *devicename) for(unsigned i = 0; i < CDInterfaces.size(); i++) { TOC toc; - TOC_Clear(&toc); CDInterfaces[i]->ReadTOC(&toc); @@ -2296,9 +2293,8 @@ static MDFNGI *MDFNI_LoadCD(const char *devicename) for(unsigned i = 0; i < CDInterfaces.size(); i++) { - CD_TOC toc; + TOC toc; - TOC_Clear(&toc); CDInterfaces[i]->ReadTOC(&toc); layout_md5.update_u32_as_lsb(toc.first_track); diff --git a/mednafen/cdrom/CDAFReader.cpp b/mednafen/cdrom/CDAFReader.cpp new file mode 100644 index 0000000..c07612a --- /dev/null +++ b/mednafen/cdrom/CDAFReader.cpp @@ -0,0 +1,60 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAFReader.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** 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. +*/ + +// CDAFR_Open(), and CDAFReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access +// to it for as long as the CDAFReader object exists. + +// Don't allow exceptions to propagate into the vorbis/musepack/etc. libraries, as it could easily leave the state of the library's decoder "object" in an +// inconsistent state, which would cause all sorts of unfun when we try to destroy it while handling the exception farther up. + +#include +#include "CDAFReader.h" +#include "CDAFReader_Vorbis.h" +#include "CDAFReader_MPC.h" + +CDAFReader::CDAFReader() : LastReadPos(0) +{ + +} + +CDAFReader::~CDAFReader() +{ + +} + +CDAFReader *CDAFR_Open(Stream *fp) +{ + static CDAFReader* (* const OpenFuncs[])(Stream* fp) = + { +#ifdef HAVE_MPC + CDAFR_MPC_Open, +#endif + CDAFR_Vorbis_Open + }; + + for(auto const& f : OpenFuncs) + { + return f(fp); + } + + return(NULL); +} + diff --git a/mednafen/cdrom/CDAFReader.h b/mednafen/cdrom/CDAFReader.h new file mode 100644 index 0000000..850a1a9 --- /dev/null +++ b/mednafen/cdrom/CDAFReader.h @@ -0,0 +1,62 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAFReader.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** 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. +*/ + +#ifndef __MDFN_CDAFREADER_H +#define __MDFN_CDAFREADER_H + +#include + +class CDAFReader +{ + public: + CDAFReader(); + virtual ~CDAFReader(); + + virtual uint64_t FrameCount(void) = 0; + INLINE uint64_t Read(uint64_t frame_offset, int16 *buffer, uint64_t frames) + { + uint64_t ret; + + if(LastReadPos != frame_offset) + { + //puts("SEEK"); + if(!Seek_(frame_offset)) + return(0); + LastReadPos = frame_offset; + } + + ret = Read_(buffer, frames); + LastReadPos += ret; + return(ret); + } + + private: + virtual uint64_t Read_(int16 *buffer, uint64_t frames) = 0; + virtual bool Seek_(uint64_t frame_offset) = 0; + + uint64_t LastReadPos; +}; + +// AR_Open(), and CDAFReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access +// to it for as long as the CDAFReader object exists. +CDAFReader *CDAFR_Open(Stream *fp); + +#endif diff --git a/mednafen/cdrom/CDAFReader_MPC.cpp b/mednafen/cdrom/CDAFReader_MPC.cpp new file mode 100644 index 0000000..bf03a22 --- /dev/null +++ b/mednafen/cdrom/CDAFReader_MPC.cpp @@ -0,0 +1,203 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAFReader_MPC.cpp: +** Copyright (C) 2006-2016 Mednafen Team +** +** 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 +#include "CDAFReader.h" +#include "CDAFReader_MPC.h" + +#include + +class CDAFReader_MPC final : public CDAFReader +{ + public: + CDAFReader_MPC(Stream *fp); + ~CDAFReader_MPC(); + + uint64_t Read_(int16 *buffer, uint64_t frames) override; + bool Seek_(uint64_t frame_offset) override; + uint64_t FrameCount(void) override; + + private: + mpc_reader reader; + mpc_demux *demux; + mpc_streaminfo si; + + MPC_SAMPLE_FORMAT MPCBuffer[MPC_DECODER_BUFFER_LENGTH]; + + uint32_t MPCBufferIn; + uint32_t MPCBufferOffs; + Stream *fw; +}; + + +/// Reads size bytes of data into buffer at ptr. +static mpc_int32_t_t impc_read(mpc_reader *p_reader, void *ptr, mpc_int32_t_t size) +{ + Stream *fw = (Stream*)(p_reader->data); + + return fw->read(ptr, size, false); +} + +/// Seeks to byte position offset. +static mpc_bool_t impc_seek(mpc_reader *p_reader, mpc_int32_t_t offset) +{ + Stream *fw = (Stream*)(p_reader->data); + + fw->seek(offset, SEEK_SET); + return(MPC_TRUE); +} + +/// Returns the current byte offset in the stream. +static mpc_int32_t_t impc_tell(mpc_reader *p_reader) +{ + Stream *fw = (Stream*)(p_reader->data); + return fw->tell(); +} + +/// Returns the total length of the source stream, in bytes. +static mpc_int32_t_t impc_get_size(mpc_reader *p_reader) +{ + Stream *fw = (Stream*)(p_reader->data); + return fw->size(); +} + +/// True if the stream is a seekable stream. +static mpc_bool_t impc_canseek(mpc_reader *p_reader) +{ + return(MPC_TRUE); +} + +CDAFReader_MPC::CDAFReader_MPC(Stream *fp) : fw(fp) +{ + demux = NULL; + memset(&si, 0, sizeof(si)); + memset(MPCBuffer, 0, sizeof(MPCBuffer)); + MPCBufferOffs = 0; + MPCBufferIn = 0; + + memset(&reader, 0, sizeof(reader)); + reader.read = impc_read; + reader.seek = impc_seek; + reader.tell = impc_tell; + reader.get_size = impc_get_size; + reader.canseek = impc_canseek; + reader.data = (void*)fp; + + if(!(demux = mpc_demux_init(&reader))) + { + throw(0); + } + mpc_demux_get_info(demux, &si); + + if(si.channels != 2) + { + mpc_demux_exit(demux); + demux = NULL; + throw MDFN_Error(0, _("MusePack stream has wrong number of channels(%u); the correct number is 2."), si.channels); + } + + if(si.sample_freq != 44100) + { + mpc_demux_exit(demux); + demux = NULL; + throw MDFN_Error(0, _("MusePack stream has wrong samplerate(%u Hz); the correct samplerate is 44100 Hz."), si.sample_freq); + } +} + +CDAFReader_MPC::~CDAFReader_MPC() +{ + if(demux) + { + mpc_demux_exit(demux); + demux = NULL; + } +} + +uint64_t CDAFReader_MPC::Read_(int16 *buffer, uint64_t frames) +{ + mpc_status err; + int16 *cowbuf = (int16 *)buffer; + int32_t toread = frames * 2; + + while(toread > 0) + { + int32_t tmplen; + + if(!MPCBufferIn) + { + mpc_frame_info fi; + memset(&fi, 0, sizeof(fi)); + + fi.buffer = MPCBuffer; + if((err = mpc_demux_decode(demux, &fi)) < 0 || fi.bits == -1) + return(frames - toread / 2); + + MPCBufferIn = fi.samples * 2; + MPCBufferOffs = 0; + } + + tmplen = MPCBufferIn; + + if(tmplen >= toread) + tmplen = toread; + + for(int x = 0; x < tmplen; x++) + { + int32_t samp = MPCBuffer[MPCBufferOffs + x] >> MPC_FIXED_POINT_FRACTPART; + if(samp < -32768) + samp = -32768; + + if(samp > 32767) + samp = 32767; + + *cowbuf = (int16)samp; + cowbuf++; + } + + MPCBufferOffs += tmplen; + toread -= tmplen; + MPCBufferIn -= tmplen; + } + + return(frames - toread / 2); +} + +bool CDAFReader_MPC::Seek_(uint64_t frame_offset) +{ + MPCBufferOffs = 0; + MPCBufferIn = 0; + + if(mpc_demux_seek_sample(demux, frame_offset) < 0) + return(false); + + return(true); +} + +uint64_t CDAFReader_MPC::FrameCount(void) +{ + return(mpc_streaminfo_get_length_samples(&si)); +} + + +CDAFReader* CDAFR_MPC_Open(Stream* fp) +{ + return new CDAFReader_MPC(fp); +} diff --git a/mednafen/cdrom/CDAFReader_MPC.h b/mednafen/cdrom/CDAFReader_MPC.h new file mode 100644 index 0000000..0cf960b --- /dev/null +++ b/mednafen/cdrom/CDAFReader_MPC.h @@ -0,0 +1,27 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAFReader_MPC.h: +** Copyright (C) 2015-2016 Mednafen Team +** +** 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. +*/ + +#ifndef __MDFN_CDAFREADER_MPC_H +#define __MDFN_CDAFREADER_MPC_H + +CDAFReader* CDAFR_MPC_Open(Stream* fp); + +#endif diff --git a/mednafen/cdrom/CDAFReader_Vorbis.cpp b/mednafen/cdrom/CDAFReader_Vorbis.cpp new file mode 100644 index 0000000..72b0599 --- /dev/null +++ b/mednafen/cdrom/CDAFReader_Vorbis.cpp @@ -0,0 +1,129 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAFReader_Vorbis.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** 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 +#include "CDAFReader.h" +#include "CDAFReader_Vorbis.h" + +#include + +class CDAFReader_Vorbis final : public CDAFReader +{ + public: + CDAFReader_Vorbis(Stream *fp); + ~CDAFReader_Vorbis(); + + uint64_t Read_(int16_t *buffer, uint64_t frames) override; + bool Seek_(uint64_t frame_offset) override; + uint64_t FrameCount(void) override; + + private: + OggVorbis_File ovfile; + Stream *fw; +}; + + +static size_t iov_read_func(void *ptr, size_t size, size_t nmemb, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + if(!size) + return(0); + + return fw->read(ptr, size * nmemb, false) / size; +} + +static int iov_seek_func(void *user_data, int64_t offset, int whence) +{ + Stream *fw = (Stream*)user_data; + + fw->seek(offset, whence); + return(0); +} + +static int iov_close_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + fw->close(); + return(0); +} + +static long iov_tell_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + return fw->tell(); +} + +CDAFReader_Vorbis::CDAFReader_Vorbis(Stream *fp) : fw(fp) +{ + ov_callbacks cb; + + memset(&cb, 0, sizeof(cb)); + cb.read_func = iov_read_func; + cb.seek_func = iov_seek_func; + cb.close_func = iov_close_func; + cb.tell_func = iov_tell_func; + + if(ov_open_callbacks(fp, &ovfile, NULL, 0, cb)) + throw(0); +} + +CDAFReader_Vorbis::~CDAFReader_Vorbis() +{ + ov_clear(&ovfile); +} + +uint64_t CDAFReader_Vorbis::Read_(int16_t *buffer, uint64_t frames) +{ + uint8 *tw_buf = (uint8 *)buffer; + int cursection = 0; + long toread = frames * sizeof(int16_t) * 2; + + while(toread > 0) + { + long didread = ov_read(&ovfile, (char*)tw_buf, toread, &cursection); + + if(didread == 0) + break; + + tw_buf = (uint8 *)tw_buf + didread; + toread -= didread; + } + + return(frames - toread / sizeof(int16_t) / 2); +} + +bool CDAFReader_Vorbis::Seek_(uint64_t frame_offset) +{ + ov_pcm_seek(&ovfile, frame_offset); + return(true); +} + +uint64_t CDAFReader_Vorbis::FrameCount(void) +{ + return(ov_pcm_total(&ovfile, -1)); +} + +CDAFReader* CDAFR_Vorbis_Open(Stream* fp) +{ + return new CDAFReader_Vorbis(fp); +} diff --git a/mednafen/cdrom/CDAFReader_Vorbis.h b/mednafen/cdrom/CDAFReader_Vorbis.h new file mode 100644 index 0000000..4d2e64c --- /dev/null +++ b/mednafen/cdrom/CDAFReader_Vorbis.h @@ -0,0 +1,28 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAFReader_Vorbis.h: +** Copyright (C) 2015-2016 Mednafen Team +** +** 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. +*/ + +#ifndef __MDFN_CDAFREADER_VORBIS_H +#define __MDFN_CDAFREADER_VORBIS_H + +CDAFReader* CDAFR_Vorbis_Open(Stream* fp); + +#endif + diff --git a/mednafen/cdrom/CDAccess.cpp b/mednafen/cdrom/CDAccess.cpp index 3c9d8b6..0157369 100644 --- a/mednafen/cdrom/CDAccess.cpp +++ b/mednafen/cdrom/CDAccess.cpp @@ -15,16 +15,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#include -#ifdef _WIN32 -#include -#else -#include -#endif - #include "../mednafen.h" - #include "CDAccess.h" #include "CDAccess_Image.h" #include "CDAccess_CCD.h" @@ -39,9 +30,15 @@ CDAccess::~CDAccess() } -CDAccess *cdaccess_open_image(bool *success, const char *path, bool image_memcache) +CDAccess* CDAccess_Open(const std::string& path, bool image_memcache) { - if(strlen(path) >= 4 && !strcasecmp(path + strlen(path) - 4, ".ccd")) - return new CDAccess_CCD(success, path, image_memcache); - return new CDAccess_Image(success, path, image_memcache); + CDAccess *ret = NULL; + + if(path.size() >= 4 && !strcasecmp(path.c_str() + path.size() - 4, ".ccd")) + ret = new CDAccess_CCD(path, image_memcache); + else + ret = new CDAccess_Image(path, image_memcache); + + return ret; } + diff --git a/mednafen/cdrom/CDAccess.h b/mednafen/cdrom/CDAccess.h index 8e54213..999b3b6 100644 --- a/mednafen/cdrom/CDAccess.h +++ b/mednafen/cdrom/CDAccess.h @@ -2,12 +2,8 @@ #define __MDFN_CDROMFILE_H #include -#include - -#include #include "CDUtility.h" -#include "misc.h" class CDAccess { @@ -16,17 +12,21 @@ class CDAccess CDAccess(); virtual ~CDAccess(); - virtual bool Read_Raw_Sector(uint8_t *buf, int32_t lba) = 0; + virtual void Read_Raw_Sector(uint8_t *buf, int32_t lba) = 0; - virtual bool Read_TOC(TOC *toc) = 0; + // Returns false if the read wouldn't be "fast"(i.e. reading from a disk), + // or if the read can't be done in a thread-safe re-entrant manner. + // + // Writes 96 bytes into pwbuf, and returns 'true' otherwise. + virtual bool Fast_Read_Raw_PW_TSRE(uint8_t* pwbuf, int32_t lba) const noexcept = 0; - virtual void Eject(bool eject_status) = 0; // Eject a disc if it's physical, otherwise NOP. Returns true on success(or NOP), false on error + virtual void Read_TOC(TOC *toc) = 0; private: CDAccess(const CDAccess&); // No copy constructor. CDAccess& operator=(const CDAccess&); // No assignment operator. }; -CDAccess *cdaccess_open_image(bool *success, const char *path, bool image_memcache); +CDAccess* CDAccess_Open(const std::string& path, bool image_memcache); #endif diff --git a/mednafen/cdrom/CDAccess_CCD.cpp b/mednafen/cdrom/CDAccess_CCD.cpp index fbafc89..36f0059 100644 --- a/mednafen/cdrom/CDAccess_CCD.cpp +++ b/mednafen/cdrom/CDAccess_CCD.cpp @@ -1,50 +1,63 @@ -/* Mednafen - Multi-system Emulator - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAccess_CCD.cpp: +** Copyright (C) 2013-2016 Mednafen Team +** +** 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 +#include -#include "../mednafen.h" -#include "../general.h" -#include #include "CDAccess_CCD.h" -#include "CDUtility.h" #include #include #include +static void MDFN_strtoupper(std::string &str) +{ + const size_t len = str.length(); + + for(size_t x = 0; x < len; x++) + { + if(str[x] >= 'a' && str[x] <= 'z') + str[x] = str[x] - 'a' + 'A'; + } +} + typedef std::map CCD_Section; -template + template static T CCD_ReadInt(CCD_Section &s, const std::string &propname, const bool have_defval = false, const int defval = 0) { - const char *vp; - char *ep = NULL; - int scan_base = 10; - size_t scan_offset = 0; - long ret = 0; CCD_Section::iterator zit = s.find(propname); if(zit == s.end()) { if(have_defval) return defval; - throw MDFN_Error(0, _("Missing property: %s"), propname.c_str()); + else + throw MDFN_Error(0, _("Missing property: %s"), propname.c_str()); } const std::string &v = zit->second; + int scan_base = 10; + size_t scan_offset = 0; + long ret = 0; if(v.length() >= 3 && v[0] == '0' && v[1] == 'x') { @@ -52,7 +65,8 @@ static T CCD_ReadInt(CCD_Section &s, const std::string &propname, const bool hav scan_offset = 2; } - vp = v.c_str() + scan_offset; + const char *vp = v.c_str() + scan_offset; + char *ep = NULL; if(std::numeric_limits::is_signed) ret = strtol(vp, &ep, scan_base); @@ -60,22 +74,22 @@ static T CCD_ReadInt(CCD_Section &s, const std::string &propname, const bool hav ret = strtoul(vp, &ep, scan_base); if(!vp[0] || ep[0]) + { throw MDFN_Error(0, _("Property %s: Malformed integer: %s"), propname.c_str(), v.c_str()); + } return ret; } -CDAccess_CCD::CDAccess_CCD(bool *success, const char *path, bool image_memcache) : img_stream(NULL), sub_stream(NULL), img_numsectors(0) +CDAccess_CCD::CDAccess_CCD(const std::string& path, bool image_memcache) : img_numsectors(0) { - TOC_Clear(&tocd); - if (!Load(path, image_memcache)) - *success = false; + Load(path, image_memcache); } -bool CDAccess_CCD::Load(const char *path, bool image_memcache) +bool CDAccess_CCD::Load(const std::string& path, bool image_memcache) { - FileStream cf(path, MODE_READ); + FileStream cf(path.c_str(), MODE_READ); std::map Sections; std::string linebuf; std::string cur_section_name; @@ -87,11 +101,9 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) if(file_ext.length() == 4 && file_ext[0] == '.') { - int i; - signed char av = -1; signed char extupt[3] = { -1, -1, -1 }; - for(i = 1; i < 4; i++) + for(int i = 1; i < 4; i++) { if(file_ext[i] >= 'A' && file_ext[i] <= 'Z') extupt[i - 1] = 'A' - 'a'; @@ -99,7 +111,8 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) extupt[i - 1] = 0; } - for(i = 0; i < 3; i++) + signed char av = -1; + for(int i = 0; i < 3; i++) { if(extupt[i] != -1) av = extupt[i]; @@ -110,13 +123,13 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) if(av == -1) av = 0; - for(i = 0; i < 3; i++) + for(int i = 0; i < 3; i++) { if(extupt[i] == -1) extupt[i] = av; } - for(i = 0; i < 3; i++) + for(int i = 0; i < 3; i++) { img_extsd[i] += extupt[i]; sub_extsd[i] += extupt[i]; @@ -129,7 +142,8 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) while(cf.get_line(linebuf) >= 0) { - MDFN_trim(linebuf); + MDFN_rtrim(linebuf); + MDFN_ltrim(linebuf); if(linebuf.length() == 0) // Skip blank lines. continue; @@ -138,7 +152,7 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) { if(linebuf.length() < 3 || linebuf[linebuf.length() - 1] != ']') { - MDFN_Error(0, _("Malformed section specifier: %s"), linebuf.c_str()); + log_cb(RETRO_LOG_ERROR, "Malformed section specifier: %s", linebuf.c_str()); return false; } @@ -147,21 +161,24 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) } else { - std::string k, v; const size_t feqpos = linebuf.find('='); const size_t leqpos = linebuf.rfind('='); + std::string k, v; if(feqpos == std::string::npos || feqpos != leqpos) { - MDFN_Error(0, _("Malformed value pair specifier: %s"), linebuf.c_str()); + log_cb(RETRO_LOG_ERROR, "Malformed value pair specifier: %s\n", linebuf.c_str()); return false; } k = linebuf.substr(0, feqpos); v = linebuf.substr(feqpos + 1); - MDFN_trim(k); - MDFN_trim(v); + MDFN_rtrim(k); + MDFN_ltrim(k); + + MDFN_rtrim(v); + MDFN_ltrim(v); MDFN_strtoupper(k); @@ -170,117 +187,116 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) } { - unsigned te; - CCD_Section &ds = Sections["DISC"]; - unsigned toc_entries = CCD_ReadInt(ds, "TOCENTRIES"); - unsigned num_sessions = CCD_ReadInt(ds, "SESSIONS"); + CCD_Section& ds = Sections["DISC"]; + unsigned toc_entries = CCD_ReadInt(ds, "TOCENTRIES"); + unsigned num_sessions = CCD_ReadInt(ds, "SESSIONS"); bool data_tracks_scrambled = CCD_ReadInt(ds, "DATATRACKSSCRAMBLED"); if(num_sessions != 1) { - MDFN_Error(0, _("Unsupported number of sessions: %u"), num_sessions); + log_cb(RETRO_LOG_ERROR, "Unsupported number of sessions: %u\n", num_sessions); return false; } if(data_tracks_scrambled) { - MDFN_Error(0, _("Scrambled CCD data tracks currently not supported.")); + log_cb(RETRO_LOG_ERROR, "Scrambled CCD data tracks currently not supported.\n"); return false; } //printf("MOO: %d\n", toc_entries); - for(te = 0; te < toc_entries; te++) + for(unsigned te = 0; te < toc_entries; te++) { char tmpbuf[64]; snprintf(tmpbuf, sizeof(tmpbuf), "ENTRY %u", te); - CCD_Section & ts = Sections[std::string(tmpbuf)]; + CCD_Section& ts = Sections[std::string(tmpbuf)]; unsigned session = CCD_ReadInt(ts, "SESSION"); - uint8_t point = CCD_ReadInt(ts, "POINT"); - uint8_t adr = CCD_ReadInt(ts, "ADR"); - uint8_t control = CCD_ReadInt(ts, "CONTROL"); - uint8_t pmin = CCD_ReadInt(ts, "PMIN"); - uint8_t psec = CCD_ReadInt(ts, "PSEC"); - uint8_t pframe = CCD_ReadInt(ts, "PFRAME"); - signed plba = CCD_ReadInt(ts, "PLBA"); + uint8_t point = CCD_ReadInt(ts, "POINT"); + uint8_t adr = CCD_ReadInt(ts, "ADR"); + uint8_t control = CCD_ReadInt(ts, "CONTROL"); + uint8_t pmin = CCD_ReadInt(ts, "PMIN"); + uint8_t psec = CCD_ReadInt(ts, "PSEC"); + //uint8_t pframe = CCD_ReadInt(ts, "PFRAME"); + signed plba = CCD_ReadInt(ts, "PLBA"); if(session != 1) { - MDFN_Error(0, "Unsupported TOC entry Session value: %u", session); + log_cb(RETRO_LOG_ERROR, "Unsupported TOC entry Session value: %u\n", session); return false; } // Reference: ECMA-394, page 5-14 - if (point >= 1 && point <= 99) + if(point >= 1 && point <= 99) { tocd.tracks[point].adr = adr; tocd.tracks[point].control = control; tocd.tracks[point].lba = plba; + tocd.tracks[point].valid = true; } - else - switch(point) - { - default: - MDFN_Error(0, "Unsupported TOC entry Point value: %u", point); - return false; - case 0xA0: - tocd.first_track = pmin; - tocd.disc_type = psec; - break; + else switch(point) + { + default: + log_cb(RETRO_LOG_ERROR, "Unsupported TOC entry Point value: %u\n", point); + return false; + case 0xA0: + tocd.first_track = pmin; + tocd.disc_type = psec; + break; - case 0xA1: - tocd.last_track = pmin; - break; + case 0xA1: + tocd.last_track = pmin; + break; - case 0xA2: - tocd.tracks[100].adr = adr; - tocd.tracks[100].control = control; - tocd.tracks[100].lba = plba; - break; - } + case 0xA2: + tocd.tracks[100].adr = adr; + tocd.tracks[100].control = control; + tocd.tracks[100].lba = plba; + tocd.tracks[100].valid = true; + break; + } } } - /* Convenience leadout track duplication. */ - if(tocd.last_track < 99) - tocd.tracks[tocd.last_track + 1] = tocd.tracks[100]; - - /* Open image stream. */ + // Open image stream. { std::string image_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(img_extsd), true); - FileStream *str = new FileStream(image_path.c_str(), MODE_READ); if(image_memcache) - img_stream = new MemoryStream(str); + img_stream = new MemoryStream(new FileStream(image_path.c_str(), MODE_READ)); else - img_stream = str; + img_stream = new FileStream(image_path.c_str(), MODE_READ); - int64 ss = img_stream->size(); + uint64 ss = img_stream->size(); if(ss % 2352) { - MDFN_Error(0, _("CCD image size is not evenly divisible by 2352.")); + log_cb(RETRO_LOG_ERROR, "CCD image size is not evenly divisible by 2352.\n"); + return false; + } + + if(ss > 0x7FFFFFFF) + { + log_cb(RETRO_LOG_ERROR, "CCD image is too large.\n"); return false; } img_numsectors = ss / 2352; } + // Open subchannel stream { - /* Open subchannel stream */ std::string sub_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(sub_extsd), true); - FileStream *str = new FileStream(sub_path.c_str(), MODE_READ); + FileStream sub_stream(sub_path.c_str(), MODE_READ); - if(image_memcache) - sub_stream = new MemoryStream(str); - else - sub_stream = str; - - if(sub_stream->size() != (int64)img_numsectors * 96) + if(sub_stream.size() != (uint64)img_numsectors * 96) { - MDFN_Error(0, _("CCD SUB file size mismatch.")); + log_cb(RETRO_LOG_ERROR, "CCD SUB file size mismatch.\n"); return false; } + + sub_data = new uint8_t[(uint64)img_numsectors * 96]; + sub_stream.read(sub_data, (uint64)img_numsectors * 96); } CheckSubQSanity(); @@ -298,128 +314,133 @@ bool CDAccess_CCD::Load(const char *path, bool image_memcache) // bool CDAccess_CCD::CheckSubQSanity(void) { - size_t s; size_t checksum_pass_counter = 0; - int prev_lba = INT_MAX; - uint8_t prev_track = 0; + int prev_lba = INT_MAX; + uint8_t prev_track = 0; - // Silence GCC warning - (void)prev_lba; - - for(s = 0; s < img_numsectors; s++) + for(size_t s = 0; s < img_numsectors; s++) { - uint8_t adr; union { - uint8 full[96]; + uint8_t full[96]; struct { - uint8 pbuf[12]; - uint8 qbuf[12]; + uint8_t pbuf[12]; + uint8_t qbuf[12]; }; } buf; - sub_stream->seek(s * 96, SEEK_SET); - sub_stream->read(buf.full, 96); + memcpy(buf.full, &sub_data[s * 96], 96); - if(!subq_check_checksum(buf.qbuf)) - continue; - - adr = buf.qbuf[0] & 0xF; - - if(adr == 0x01) + if(subq_check_checksum(buf.qbuf)) { - int lba; - uint8_t track; - uint8_t track_bcd = buf.qbuf[1]; - uint8_t index_bcd = buf.qbuf[2]; - uint8_t rm_bcd = buf.qbuf[3]; - uint8_t rs_bcd = buf.qbuf[4]; - uint8_t rf_bcd = buf.qbuf[5]; - uint8_t am_bcd = buf.qbuf[7]; - uint8_t as_bcd = buf.qbuf[8]; - uint8_t af_bcd = buf.qbuf[9]; + uint8_t adr = buf.qbuf[0] & 0xF; - //printf("%2x %2x %2x\n", am_bcd, as_bcd, af_bcd); - - if(!BCD_is_valid(track_bcd) || !BCD_is_valid(index_bcd) || !BCD_is_valid(rm_bcd) || !BCD_is_valid(rs_bcd) || !BCD_is_valid(rf_bcd) || - !BCD_is_valid(am_bcd) || !BCD_is_valid(as_bcd) || !BCD_is_valid(af_bcd) || - rs_bcd > 0x59 || rf_bcd > 0x74 || as_bcd > 0x59 || af_bcd > 0x74) + if(adr == 0x01) { - MDFN_Error(0, _("Garbage subchannel Q data detected(bad BCD/out of range): %02x:%02x:%02x %02x:%02x:%02x"), rm_bcd, rs_bcd, rf_bcd, am_bcd, as_bcd, af_bcd); - return false; + uint8_t track_bcd = buf.qbuf[1]; + uint8_t index_bcd = buf.qbuf[2]; + uint8_t rm_bcd = buf.qbuf[3]; + uint8_t rs_bcd = buf.qbuf[4]; + uint8_t rf_bcd = buf.qbuf[5]; + uint8_t am_bcd = buf.qbuf[7]; + uint8_t as_bcd = buf.qbuf[8]; + uint8_t af_bcd = buf.qbuf[9]; + + //printf("%2x %2x %2x\n", am_bcd, as_bcd, af_bcd); + + if(!BCD_is_valid(track_bcd) || !BCD_is_valid(index_bcd) || !BCD_is_valid(rm_bcd) || !BCD_is_valid(rs_bcd) || !BCD_is_valid(rf_bcd) || + !BCD_is_valid(am_bcd) || !BCD_is_valid(as_bcd) || !BCD_is_valid(af_bcd) || + rs_bcd > 0x59 || rf_bcd > 0x74 || as_bcd > 0x59 || af_bcd > 0x74) + { + log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(bad BCD/out of range): %02x:%02x:%02x %02x:%02x:%02x\n", rm_bcd, rs_bcd, rf_bcd, am_bcd, as_bcd, af_bcd); + return false; + } + else + { + int lba = ((BCD_to_U8(am_bcd) * 60 + BCD_to_U8(as_bcd)) * 75 + BCD_to_U8(af_bcd)) - 150; + uint8_t track = BCD_to_U8(track_bcd); + + if(prev_lba != INT_MAX && abs(lba - prev_lba) > 100) + { + log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(excessively large jump in AMSF)\n"); + return false; + } + + if(abs((int)(lba - s)) > 100) + { + log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(AMSF value is out of tolerance)\n"); + return false; + } + + prev_lba = lba; + + if(track < prev_track) + { + log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(bad track number)\n"); + return false; + } + + prev_track = track; + } + checksum_pass_counter++; } - - lba = ((BCD_to_U8(am_bcd) * 60 + BCD_to_U8(as_bcd)) * 75 + BCD_to_U8(af_bcd)) - 150; - track = BCD_to_U8(track_bcd); - - prev_lba = lba; - - if(track < prev_track) - { - MDFN_Error(0, _("Garbage subchannel Q data detected(bad track number)")); - return false; - } - - prev_track = track; - checksum_pass_counter++; } } //printf("%u/%u\n", checksum_pass_counter, img_numsectors); - return true; } -void CDAccess_CCD::Cleanup(void) -{ - if(img_stream) - { - delete img_stream; - img_stream = NULL; - } - - if(sub_stream) - { - delete sub_stream; - sub_stream = NULL; - } -} - CDAccess_CCD::~CDAccess_CCD() { - Cleanup(); + if (img_stream) + delete[] img_stream; + if (sub_data) + delete[] sub_data; } -bool CDAccess_CCD::Read_Raw_Sector(uint8 *buf, int32 lba) +void CDAccess_CCD::Read_Raw_Sector(uint8_t *buf, int32_t lba) { - uint8_t sub_buf[96]; - - if(lba < 0 || (size_t)lba >= img_numsectors) + if(lba < 0) { - MDFN_Error(0, _("LBA out of range.")); - return false; + synth_udapp_sector_lba(0xFF, tocd, lba, 0, buf); + return; + } + + if((size_t)lba >= img_numsectors) + { + synth_leadout_sector_lba(0xFF, tocd, lba, buf); + return; } img_stream->seek(lba * 2352, SEEK_SET); img_stream->read(buf, 2352); - sub_stream->seek(lba * 96, SEEK_SET); - sub_stream->read(sub_buf, 96); + subpw_interleave(&sub_data[lba * 96], buf + 2352); +} - subpw_interleave(sub_buf, buf + 2352); +bool CDAccess_CCD::Fast_Read_Raw_PW_TSRE(uint8_t* pwbuf, int32_t lba) const noexcept +{ + if(lba < 0) + { + subpw_synth_udapp_lba(tocd, lba, 0, pwbuf); + return true; + } + + if((size_t)lba >= img_numsectors) + { + subpw_synth_leadout_lba(tocd, lba, pwbuf); + return true; + } + + subpw_interleave(&sub_data[lba * 96], pwbuf); return true; } - -bool CDAccess_CCD::Read_TOC(TOC *toc) +void CDAccess_CCD::Read_TOC(TOC *toc) { *toc = tocd; - return true; } -void CDAccess_CCD::Eject(bool eject_status) -{ - -} diff --git a/mednafen/cdrom/CDAccess_CCD.h b/mednafen/cdrom/CDAccess_CCD.h index 21dc147..199bc1d 100644 --- a/mednafen/cdrom/CDAccess_CCD.h +++ b/mednafen/cdrom/CDAccess_CCD.h @@ -1,53 +1,52 @@ -/* Mednafen - Multi-system Emulator - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* CDAccess_CCD.h: +** Copyright (C) 2013-2016 Mednafen Team +** +** 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. +*/ -#ifndef _CDROM_CDACCESS_CCD_H_ -#define _CDROM_CDACCESS_CCD_H_ +#include +#include -#include "../FileStream.h" -#include "../MemoryStream.h" #include "CDAccess.h" -#include - class CDAccess_CCD : public CDAccess { public: - CDAccess_CCD(bool *success, const char *path, bool image_memcache); + CDAccess_CCD(const std::string& path, bool image_memcache); virtual ~CDAccess_CCD(); - virtual bool Read_Raw_Sector(uint8_t *buf, int32_t lba); + virtual void Read_Raw_Sector(uint8 *buf, int32 lba); - virtual bool Read_TOC(TOC *toc); + virtual bool Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept; - virtual void Eject(bool eject_status); + virtual void Read_TOC(TOC *toc); private: - bool Load(const char *path, bool image_memcache); + bool Load(const std::string& path, bool image_memcache); void Cleanup(void); bool CheckSubQSanity(void); - Stream* img_stream; - Stream* sub_stream; + Stream *img_stream; + uint8_t *sub_data; + size_t img_numsectors; TOC tocd; }; - -#endif diff --git a/mednafen/cdrom/CDAccess_Image.cpp b/mednafen/cdrom/CDAccess_Image.cpp index b212ad2..fc4c584 100644 --- a/mednafen/cdrom/CDAccess_Image.cpp +++ b/mednafen/cdrom/CDAccess_Image.cpp @@ -26,13 +26,8 @@ A PREGAP statement in the first track definition in a CUE sheet may not work properly(depends on what is proper); it will be added onto the implicit default 00:02:00 of pregap. - - Trying to read sectors at an LBA of less than 0 is not supported. TODO: support it(at least up to -150). */ -#include -#include - #include "../mednafen.h" #include @@ -40,28 +35,25 @@ #include #include #include +#include #include "../general.h" +#include "../mednafen-endian.h" #include "../FileStream.h" #include "../MemoryStream.h" #include "CDAccess.h" #include "CDAccess_Image.h" -#include "CDUtility.h" -#include "audioreader.h" - -#include "../../libretro.h" - -extern retro_log_printf_t log_cb; +#include "CDAFReader.h" #include enum { CDRF_SUBM_NONE = 0, - CDRF_SUBM_RW, - CDRF_SUBM_RW_RAW + CDRF_SUBM_RW = 1, + CDRF_SUBM_RW_RAW = 2 }; // Disk-image(rip) track/sector formats @@ -74,10 +66,11 @@ enum DI_FORMAT_MODE2_FORM1 = 0x04, DI_FORMAT_MODE2_FORM2 = 0x05, DI_FORMAT_MODE2_RAW = 0x06, + DI_FORMAT_CDI_RAW = 0x07, _DI_FORMAT_COUNT }; -static const int32 DI_Size_Table[7] = +static const int32_t DI_Size_Table[8] = { 2352, // Audio 2048, // MODE1 @@ -85,10 +78,11 @@ static const int32 DI_Size_Table[7] = 2336, // MODE2 2048, // MODE2 Form 1 2324, // Mode 2 Form 2 - 2352 + 2352, // MODE2 RAW + 2352 // CD-I RAW }; -static const char *DI_CDRDAO_Strings[7] = +static const char *DI_CDRDAO_Strings[8] = { "AUDIO", "MODE1", @@ -96,26 +90,25 @@ static const char *DI_CDRDAO_Strings[7] = "MODE2", "MODE2_FORM1", "MODE2_FORM2", - "MODE2_RAW" + "MODE2_RAW", + "CDI_RAW" }; -static const char *DI_CUE_Strings[7] = +static const char *DI_CUE_Strings[8] = { "AUDIO", "MODE1/2048", "MODE1/2352", - - // FIXME: These are just guesses: - "MODE2/2336", - "MODE2/2048", - "MODE2/2324", - "MODE2/2352" + "MODE2/2336", // FIXME: A guess + "MODE2/2048", // FIXME: A guess + "MODE2/2324", // FIXME: A guess + "MODE2/2352", // FIXME: A guess + "CDI/2352" }; // Should return an offset to the start of the next argument(past any whitespace), or if there isn't a next argument, // it'll return the length of the src string. -static size_t UnQuotify(const std::string &src, size_t source_offset, - std::string &dest, bool parse_quotes = true) +static size_t UnQuotify(const std::string &src, size_t source_offset, std::string &dest, bool parse_quotes = true) { const size_t source_len = src.length(); bool in_quote = 0; @@ -174,39 +167,40 @@ static size_t UnQuotify(const std::string &src, size_t source_offset, return source_offset; } -uint32 CDAccess_Image::GetSectorCount(CDRFILE_TRACK_INFO *track) +uint32_t CDAccess_Image::GetSectorCount(CDRFILE_TRACK_INFO *track) { - int64 size; - if(track->DIFormat == DI_FORMAT_AUDIO) { if(track->AReader) return(((track->AReader->FrameCount() * 4) - track->FileOffset) / 2352); + else + { + const int64_t size = track->fp->size(); - size = track->fp->size(); + //printf("%d %d %d\n", (int)stat_buf.st_size, (int)track->FileOffset, (int)stat_buf.st_size - (int)track->FileOffset); + if(track->SubchannelMode) + return((size - track->FileOffset) / (2352 + 96)); + return((size - track->FileOffset) / 2352); + } + } + else + { + const int64_t size = track->fp->size(); - if(track->SubchannelMode) - return((size - track->FileOffset) / (2352 + 96)); - return((size - track->FileOffset) / 2352); + return((size - track->FileOffset) / DI_Size_Table[track->DIFormat]); } - size = track->fp->size(); - - return((size - track->FileOffset) / DI_Size_Table[track->DIFormat]); + return(0); } -bool CDAccess_Image::ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int tracknum, - const std::string &filename, const char *binoffset, const char *msfoffset, - const char *length, bool image_memcache, std::map &toc_streamcache) +bool CDAccess_Image::ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int tracknum, const std::string &filename, const char *binoffset, const char *msfoffset, const char *length, bool image_memcache, std::map &toc_streamcache) { long offset = 0; // In bytes! long tmp_long; int m, s, f; - uint32 sector_mult; + uint32_t sector_mult; long sectors; - std::map::iterator ribbit; - - ribbit = toc_streamcache.find(filename); + std::map::iterator ribbit = toc_streamcache.find(filename); if(ribbit != toc_streamcache.end()) { @@ -232,11 +226,11 @@ bool CDAccess_Image::ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int t if(filename.length() >= 4 && !strcasecmp(filename.c_str() + filename.length() - 4, ".wav")) { - track->AReader = AR_Open(track->fp); + track->AReader = CDAFR_Open(track->fp); if(!track->AReader) { - MDFN_Error(0, "TODO ERROR"); + log_cb(RETRO_LOG_ERROR, "TODO ERROR\n"); return false; } } @@ -284,64 +278,114 @@ bool CDAccess_Image::ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int t if(tmp_long > sectors) { - MDFN_Error(0, _("Length specified in TOC file for track %d is too large by %ld sectors!\n"), tracknum, (long)(tmp_long - sectors)); + log_cb(RETRO_LOG_ERROR, "Length specified in TOC file for track %d is too large by %ld sectors!\n", tracknum, (long)(tmp_long - sectors)); return false; } sectors = tmp_long; } track->sectors = sectors; + return true; } -int CDAccess_Image::LoadSBI(const char* sbi_path) +static void MDFN_strtoupper(std::string &str) { - /* Loading SBI file */ - uint8 header[4]; - uint8 ed[4 + 10]; - uint8 tmpq[12]; - FileStream sbis(sbi_path, MODE_READ); + size_t x; + const size_t len = str.length(); + + for(x = 0; x < len; x++) + { + if(str[x] >= 'a' && str[x] <= 'z') + str[x] = str[x] - 'a' + 'A'; + } +} + +bool CDAccess_Image::LoadSBI(const std::string& sbi_path) +{ + uint8_t header[4]; + uint8_t ed[4 + 10]; + uint8_t tmpq[12]; + + log_cb(RETRO_LOG_INFO, "Loading SBI file \"%s\"...\n", sbi_path.c_str()); + + if (!path_is_valid(sbi_path.c_str())) + { + /* SBI file not available, but don't error out. */ + return true; + } + + FileStream sbis(sbi_path.c_str(), MODE_READ); sbis.read(header, 4); if(memcmp(header, "SBI\0", 4)) - return -1; + { + log_cb(RETRO_LOG_ERROR, "Not recognized a valid SBI file."); + return false; + } while(sbis.read(ed, sizeof(ed), false) == sizeof(ed)) { - /* Bad BCD MSF offset in SBI file. */ if(!BCD_is_valid(ed[0]) || !BCD_is_valid(ed[1]) || !BCD_is_valid(ed[2])) - return -1; + { + log_cb(RETRO_LOG_ERROR, "Bad BCD MSF offset in SBI file: %02x:%02x:%02x\n", ed[0], ed[1], ed[2]); + return false; + } - /* Unrecognized boogly oogly in SBI file */ if(ed[3] != 0x01) - return -1; + { + log_cb(RETRO_LOG_ERROR, "Unrecognized boogly oogly in SBI file: %02x\n", ed[3]); + return false; + } memcpy(tmpq, &ed[4], 10); + // subq_generate_checksum(tmpq); tmpq[10] ^= 0xFF; tmpq[11] ^= 0xFF; + // - uint32 aba = AMSF_to_ABA(BCD_to_U8(ed[0]), BCD_to_U8(ed[1]), BCD_to_U8(ed[2])); + //printf("%02x:%02x:%02x --- ", ed[0], ed[1], ed[2]); + //for(unsigned i = 0; i < 12; i++) + // printf("%02x ", tmpq[i]); + //printf("\n"); - memcpy(SubQReplaceMap[aba].data, tmpq, 12); + uint32_t aba = AMSF_to_ABA(BCD_to_U8(ed[0]), BCD_to_U8(ed[1]), BCD_to_U8(ed[2])); + + memcpy(SubQReplaceMap[aba].data(), tmpq, 12); } - - //MDFN_printf(_("Loaded Q subchannel replacements for %zu sectors.\n"), SubQReplaceMap.size()); - - return 0; + log_cb(RETRO_LOG_INFO, "Loaded Q subchannel replacements for %zu sectors.\n", SubQReplaceMap.size()); + return true; } -bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) +static bool StringToMSF(const char* str, unsigned* m, unsigned* s, unsigned* f) { - MemoryStream fp(new FileStream(path, MODE_READ)); + if(sscanf(str, "%u:%u:%u", m, s, f) != 3) + { + log_cb(RETRO_LOG_ERROR, "M:S:F time \"%s\" is malformed.\n", str); + return false; + } + + if(*m > 99 || *s > 59 || *f > 74) + { + log_cb(RETRO_LOG_ERROR, "M:S:F time \"%s\" contains component(s) out of range.\n", str); + return false; + } + + return true; +} + +bool CDAccess_Image::ImageOpen(const std::string& path, bool image_memcache) +{ + MemoryStream fp(new FileStream(path.c_str(), MODE_READ)); static const unsigned max_args = 4; std::string linebuf; std::string cmdbuf, args[max_args]; bool IsTOC = false; - int32 active_track = -1; - int32 AutoTrackInc = 1; // For TOC + int32_t active_track = -1; + int32_t AutoTrackInc = 1; // For TOC CDRFILE_TRACK_INFO TmpTrack; std::string file_base, file_ext; std::map toc_streamcache; @@ -360,11 +404,12 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) // Check for annoying UTF-8 BOM. if(!IsTOC) { - uint8 bom_tmp[3]; + uint8_t bom_tmp[3]; if(fp.read(bom_tmp, 3, false) == 3 && bom_tmp[0] == 0xEF && bom_tmp[1] == 0xBB && bom_tmp[2] == 0xBF) { - log_cb(RETRO_LOG_ERROR, "UTF-8 BOM detected at start of CUE sheet.\n"); + // Print an annoying error message, but don't actually error out. + log_cb(RETRO_LOG_WARN, "UTF-8 BOM detected at start of CUE sheet.\n"); } else fp.seek(0, SEEK_SET); @@ -390,7 +435,8 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) } // Call trim AFTER we handle TOC-style comments, so we'll be sure to remove trailing whitespace in lines like: MONKEY // BABIES - MDFN_trim(linebuf); + MDFN_rtrim(linebuf); + MDFN_ltrim(linebuf); if(linebuf.length() == 0) // Skip blank lines. continue; @@ -423,9 +469,12 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) active_track = -1; } + for(int32_t i = 2; i < 100; i++) + TmpTrack.index[i] = -1; + if(AutoTrackInc > 99) { - MDFN_Error(0, _("Invalid track number: %d"), AutoTrackInc); + log_cb(RETRO_LOG_ERROR, "Invalid track number: %d", AutoTrackInc); return false; } @@ -447,7 +496,7 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) if(format_lookup == _DI_FORMAT_COUNT) { - MDFN_Error(0, _("Invalid track format: %s"), args[0].c_str()); + log_cb(RETRO_LOG_ERROR, "Invalid track format: %s", args[0].c_str()); return false; } @@ -457,8 +506,7 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) if(!strcasecmp(args[1].c_str(), "RW")) { TmpTrack.SubchannelMode = CDRF_SUBM_RW; - MDFN_Error(0, _("\"RW\" format subchannel data not supported, only \"RW_RAW\" is!")); - return false; + log_cb(RETRO_LOG_ERROR, "\"RW\" format subchannel data not supported, only \"RW_RAW\" is!"); } else if(!strcasecmp(args[1].c_str(), "RW_RAW")) TmpTrack.SubchannelMode = CDRF_SUBM_RW_RAW; @@ -466,15 +514,21 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) } // end to TRACK else if(cmdbuf == "SILENCE") { - //throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); +#if 0 + log_cb(RETRO_LOG_INFO, "Unsupported directive: %s\n", cmdbuf.c_str()); + return false; +#endif } else if(cmdbuf == "ZERO") { - //throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); +#if 0 + log_cb(RETRO_LOG_INFO, "Unsupported directive: %s\n", cmdbuf.c_str()); + return false; +#endif } else if(cmdbuf == "FIFO") { - MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); + log_cb(RETRO_LOG_INFO, "Unsupported directive: %s\n", cmdbuf.c_str()); return false; } else if(cmdbuf == "FILE" || cmdbuf == "AUDIOFILE") @@ -495,7 +549,8 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) length = args[2].c_str(); } //printf("%s, %s, %s, %s\n", args[0].c_str(), binoffset, msfoffset, length); - ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, msfoffset, length, image_memcache, toc_streamcache); + if (!ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, msfoffset, length, image_memcache, toc_streamcache)) + return false; } else if(cmdbuf == "DATAFILE") { @@ -510,32 +565,43 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) else length = args[1].c_str(); - ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, NULL, length, image_memcache, toc_streamcache); + if (!ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, NULL, length, image_memcache, toc_streamcache)) + return false; } else if(cmdbuf == "INDEX") { - + // FIXME + log_cb(RETRO_LOG_ERROR, "Unsupported directive: %s", cmdbuf.c_str()); + return false; } else if(cmdbuf == "PREGAP") { if(active_track < 0) { - MDFN_Error(0, _("Command %s is outside of a TRACK definition!\n"), cmdbuf.c_str()); + log_cb(RETRO_LOG_ERROR, "Command %s is outside of a TRACK definition!\n", cmdbuf.c_str()); return false; } - int m,s,f; - sscanf(args[0].c_str(), "%d:%d:%d", &m, &s, &f); + + unsigned int m,s,f; + + if (!StringToMSF(args[0].c_str(), &m, &s, &f)) + return false; + TmpTrack.pregap = (m * 60 + s) * 75 + f; } // end to PREGAP else if(cmdbuf == "START") { if(active_track < 0) { - MDFN_Error(0, _("Command %s is outside of a TRACK definition!\n"), cmdbuf.c_str()); + log_cb(RETRO_LOG_ERROR, "Command %s is outside of a TRACK definition!\n", cmdbuf.c_str()); return false; } - int m,s,f; - sscanf(args[0].c_str(), "%d:%d:%d", &m, &s, &f); + + unsigned int m,s,f; + + if (!StringToMSF(args[0].c_str(), &m, &s, &f)) + return false; + TmpTrack.pregap = (m * 60 + s) * 75 + f; } else if(cmdbuf == "TWO_CHANNEL_AUDIO") @@ -560,7 +626,7 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) } else { - MDFN_Error(0, _("Unsupported argument to \"NO\" directive: %s"), args[0].c_str()); + log_cb(RETRO_LOG_ERROR, "Unsupported argument to \"NO\" directive: %s", args[0].c_str()); return false; } } @@ -581,7 +647,10 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) disc_type = DISC_TYPE_CD_XA; else { - //throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); +#if 0 + log_cb(RETRO_LOG_ERROR, "Unsupported directive: %s", cmdbuf.c_str()); + return false; +#endif } // TODO: CATALOG @@ -597,12 +666,6 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) active_track = -1; } - if(!MDFN_IsFIROPSafe(args[0])) - { - MDFN_Error(0, _("Referenced path \"%s\" is potentially unsafe. See \"filesys.untrusted_fip_check\" setting.\n"), args[0].c_str()); - return false; - } - std::string efn = MDFN_EvalFIP(base_dir, args[0]); TmpTrack.fp = new FileStream(efn.c_str(), MODE_READ); TmpTrack.FirstFileInstance = 1; @@ -620,17 +683,16 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) else if(!strcasecmp(args[1].c_str(), "OGG") || !strcasecmp(args[1].c_str(), "VORBIS") || !strcasecmp(args[1].c_str(), "WAVE") || !strcasecmp(args[1].c_str(), "WAV") || !strcasecmp(args[1].c_str(), "PCM") || !strcasecmp(args[1].c_str(), "MPC") || !strcasecmp(args[1].c_str(), "MP+")) { - TmpTrack.AReader = AR_Open(TmpTrack.fp); - + TmpTrack.AReader = CDAFR_Open(TmpTrack.fp); if(!TmpTrack.AReader) { - MDFN_Error(0, _("Unsupported audio track file format: %s\n"), args[0].c_str()); + log_cb(RETRO_LOG_ERROR, "Unsupported audio track file format: %s\n", args[0].c_str()); return false; } } else { - MDFN_Error(0, _("Unsupported track format: %s\n"), args[1].c_str()); + log_cb(RETRO_LOG_ERROR, "Unsupported track format: %s\n", args[1].c_str()); return false; } } @@ -646,8 +708,18 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) TmpTrack.index[0] = -1; TmpTrack.index[1] = 0; } + + for(int32_t i = 2; i < 100; i++) + TmpTrack.index[i] = -1; + active_track = atoi(args[0].c_str()); + if(active_track < 1 || active_track > 99) + { + log_cb(RETRO_LOG_ERROR, "Invalid track number: %d\n", active_track); + return false; + } + if(active_track < FirstTrack) FirstTrack = active_track; if(active_track > LastTrack) @@ -665,13 +737,7 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) if(format_lookup == _DI_FORMAT_COUNT) { - MDFN_Error(0, _("Invalid track format: %s\n"), args[1].c_str()); - return false; - } - - if(active_track < 0 || active_track > 99) - { - MDFN_Error(0, _("Invalid track number: %d\n"), active_track); + log_cb(RETRO_LOG_ERROR, "Invalid track format: %s\n", args[1].c_str()); return false; } } @@ -679,18 +745,19 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) { if(active_track >= 0) { + unsigned wi; unsigned int m,s,f; - if(sscanf(args[1].c_str(), "%u:%u:%u", &m, &s, &f) != 3) + if (!StringToMSF(args[1].c_str(), &m, &s, &f)) + return false; + + if(sscanf(args[0].c_str(), "%u", &wi) == 1 && wi < 100) + TmpTrack.index[wi] = (m * 60 + s) * 75 + f; + else { - MDFN_Error(0, _("Malformed m:s:f time in \"%s\" directive: %s"), cmdbuf.c_str(), args[0].c_str()); + log_cb(RETRO_LOG_ERROR, "Malformed \"INDEX\" directive: %s\n", cmdbuf.c_str()); return false; } - - if(!strcasecmp(args[0].c_str(), "01") || !strcasecmp(args[0].c_str(), "1")) - TmpTrack.index[1] = (m * 60 + s) * 75 + f; - else if(!strcasecmp(args[0].c_str(), "00") || !strcasecmp(args[0].c_str(), "0")) - TmpTrack.index[0] = (m * 60 + s) * 75 + f; } } else if(cmdbuf == "PREGAP") @@ -699,11 +766,8 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) { unsigned int m,s,f; - if(sscanf(args[0].c_str(), "%u:%u:%u", &m, &s, &f) != 3) - { - MDFN_Error(0, _("Malformed m:s:f time in \"%s\" directive: %s"), cmdbuf.c_str(), args[0].c_str()); + if (!StringToMSF(args[0].c_str(), &m, &s, &f)) return false; - } TmpTrack.pregap = (m * 60 + s) * 75 + f; } @@ -714,11 +778,8 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) { unsigned int m,s,f; - if(sscanf(args[0].c_str(), "%u:%u:%u", &m, &s, &f) != 3) - { - MDFN_Error(0, _("Malformed m:s:f time in \"%s\" directive: %s"), cmdbuf.c_str(), args[0].c_str()); + if (!StringToMSF(args[0].c_str(), &m, &s, &f)) return false; - } TmpTrack.postgap = (m * 60 + s) * 75 + f; } @@ -751,17 +812,19 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) } else { - MDFN_Error(0, _("Unknown CUE sheet \"FLAGS\" directive flag \"%s\".\n"), args[i].c_str()); + log_cb(RETRO_LOG_ERROR, "Unknown CUE sheet \"FLAGS\" directive flag \"%s\".\n", args[i].c_str()); return false; } } } else if(cmdbuf == "CDTEXTFILE" || cmdbuf == "CATALOG" || cmdbuf == "ISRC" || cmdbuf == "TITLE" || cmdbuf == "PERFORMER" || cmdbuf == "SONGWRITER") - log_cb(RETRO_LOG_ERROR, "Unsupported CUE sheet directive: \"%s\".\n", cmdbuf.c_str()); + { + log_cb(RETRO_LOG_INFO, "Unsupported CUE sheet directive: \"%s\".\n", cmdbuf.c_str()); /* FIXME, generic logger passed by pointer to constructor */ + } else { - MDFN_Error(0, _("Unknown CUE sheet directive \"%s\".\n"), cmdbuf.c_str()); + log_cb(RETRO_LOG_ERROR, "Unknown CUE sheet directive \"%s\".\n", cmdbuf.c_str()); return false; } } // end of CUE sheet handling @@ -772,21 +835,28 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) if(FirstTrack > LastTrack) { - MDFN_Error(0, _("No tracks found!\n")); + log_cb(RETRO_LOG_ERROR, "No tracks found!\n"); return false; } - NumTracks = 1 + LastTrack - FirstTrack; + FirstTrack = FirstTrack; + NumTracks = 1 + LastTrack - FirstTrack; - int32 RunningLBA = 0; - int32 LastIndex = 0; + int32_t RunningLBA = 0; + int32_t LastIndex = 0; long FileOffset = 0; - // Silence GCC warning - (void)LastIndex; + RunningLBA -= 150; + Tracks[FirstTrack].pregap += 150; for(int x = FirstTrack; x < (FirstTrack + NumTracks); x++) { + if(!Tracks[x].fp && !Tracks[x].AReader) + { + log_cb(RETRO_LOG_ERROR, "Missing track %u.\n", x); + return false; + } + if(Tracks[x].DIFormat == DI_FORMAT_AUDIO) Tracks[x].subq_control &= ~SUBQ_CTRLF_DATA; else @@ -794,16 +864,23 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) if(!IsTOC) // TOC-format disc_type calculation is handled differently. { - switch(Tracks[x].DIFormat) + if(disc_type != DISC_TYPE_CD_I) { - case DI_FORMAT_MODE2: - case DI_FORMAT_MODE2_FORM1: - case DI_FORMAT_MODE2_FORM2: - case DI_FORMAT_MODE2_RAW: - disc_type = DISC_TYPE_CD_XA; - break; - default: - break; + switch(Tracks[x].DIFormat) + { + default: break; + + case DI_FORMAT_MODE2: + case DI_FORMAT_MODE2_FORM1: + case DI_FORMAT_MODE2_FORM2: + case DI_FORMAT_MODE2_RAW: + disc_type = DISC_TYPE_CD_XA; + break; + + case DI_FORMAT_CDI_RAW: + disc_type = DISC_TYPE_CD_I; + break; + } } } @@ -864,38 +941,52 @@ bool CDAccess_Image::ImageOpen(const char *path, bool image_memcache) total_sectors = RunningLBA; + // + // Adjust indexes for MakeSubPQ() + // + for(int x = FirstTrack; x < (FirstTrack + NumTracks); x++) + { + const int32_t base = Tracks[x].index[1]; + + for(int32_t i = 0; i < 100; i++) + { + if(i == 0 || Tracks[x].index[i] == -1) + Tracks[x].index[i] = INT32_MAX; + else + Tracks[x].index[i] = Tracks[x].LBA + (Tracks[x].index[i] - base); + + assert(Tracks[x].index[i] >= 0); + } + } + // // Load SBI file, if present // if(!IsTOC) { - std::string sbi_path; char sbi_ext[4] = { 's', 'b', 'i', 0 }; if(file_ext.length() == 4 && file_ext[0] == '.') { - unsigned i; - for(i = 0; i < 3; i++) + for(unsigned i = 0; i < 3; i++) { if(file_ext[1 + i] >= 'A' && file_ext[1 + i] <= 'Z') sbi_ext[i] += 'A' - 'a'; } } - sbi_path = MDFN_EvalFIP(base_dir, file_base + std::string(".") + std::string(sbi_ext), true); - - if (path_is_valid(sbi_path.c_str())) - LoadSBI(sbi_path.c_str()); + if (!LoadSBI(MDFN_EvalFIP(base_dir, file_base + std::string(".") + std::string(sbi_ext), true).c_str())) + return false; } + GenerateTOC(); + return true; } void CDAccess_Image::Cleanup(void) { - int32_t track; - - for(track = 0; track < 100; track++) + for(int32_t track = 0; track < 100; track++) { CDRFILE_TRACK_INFO *this_track = &Tracks[track]; @@ -916,13 +1007,11 @@ void CDAccess_Image::Cleanup(void) } } -CDAccess_Image::CDAccess_Image(bool *success, const char *path, bool image_memcache) : - NumTracks(0), FirstTrack(0), LastTrack(0), total_sectors(0) +CDAccess_Image::CDAccess_Image(const std::string& path, bool image_memcache) : NumTracks(0), FirstTrack(0), LastTrack(0), total_sectors(0) { memset(Tracks, 0, sizeof(Tracks)); - if (!ImageOpen(path, image_memcache)) - *success = false; + ImageOpen(path, image_memcache); } CDAccess_Image::~CDAccess_Image() @@ -930,154 +1019,196 @@ CDAccess_Image::~CDAccess_Image() Cleanup(); } -bool CDAccess_Image::Read_Raw_Sector(uint8 *buf, int32 lba) +void CDAccess_Image::Read_Raw_Sector(uint8_t *buf, int32_t lba) { - int32_t track; uint8_t SimuQ[0xC]; - bool TrackFound = false; + int32_t track; + CDRFILE_TRACK_INFO *ct; + + // + // Leadout synthesis + // + if(lba >= total_sectors) + { + uint8_t data_synth_mode = (disc_type == DISC_TYPE_CD_XA ? 0x02 : 0x01); + + switch(Tracks[LastTrack].DIFormat) + { + case DI_FORMAT_AUDIO: + break; + + case DI_FORMAT_MODE1_RAW: + case DI_FORMAT_MODE1: + data_synth_mode = 0x01; + break; + + case DI_FORMAT_MODE2_RAW: + case DI_FORMAT_MODE2_FORM1: + case DI_FORMAT_MODE2_FORM2: + case DI_FORMAT_MODE2: + case DI_FORMAT_CDI_RAW: + data_synth_mode = 0x02; + break; + } + + synth_leadout_sector_lba(data_synth_mode, toc, lba, buf); + return; + } + // + // + // memset(buf + 2352, 0, 96); - - MakeSubPQ(lba, buf + 2352); - + track = MakeSubPQ(lba, buf + 2352); subq_deinterleave(buf + 2352, SimuQ); - for(track = FirstTrack; track < (FirstTrack + NumTracks); track++) - { - CDRFILE_TRACK_INFO *ct = &Tracks[track]; + ct = &Tracks[track]; - if(lba >= (ct->LBA - ct->pregap_dv - ct->pregap) && lba < (ct->LBA + ct->sectors + ct->postgap)) + // + // Handle pregap and postgap reading + // + if(lba < (ct->LBA - ct->pregap_dv) || lba >= (ct->LBA + ct->sectors)) + { + int32_t pg_offset = lba - ct->LBA; + CDRFILE_TRACK_INFO* et = ct; + + if(pg_offset < -150) { - TrackFound = true; - - // Handle pregap and postgap reading - if(lba < (ct->LBA - ct->pregap_dv) || lba >= (ct->LBA + ct->sectors)) - { - //printf("Pre/post-gap read, LBA=%d(LBA-track_start_LBA=%d)\n", lba, lba - ct->LBA); - memset(buf, 0, 2352); // Null sector data, per spec - } - else - { - if(ct->AReader) - { - int16 AudioBuf[588 * 2]; - int frames_read = ct->AReader->Read((ct->FileOffset / 4) + (lba - ct->LBA) * 588, AudioBuf, 588); - - ct->LastSamplePos += frames_read; - - if(frames_read < 0 || frames_read > 588) // This shouldn't happen. - { - printf("Error: frames_read out of range: %d\n", frames_read); - frames_read = 0; - } - - if(frames_read < 588) - memset((uint8 *)AudioBuf + frames_read * 2 * sizeof(int16), 0, (588 - frames_read) * 2 * sizeof(int16)); - - for(int i = 0; i < 588 * 2; i++) - MDFN_en16lsb(buf + i * 2, AudioBuf[i]); - } - else // Binary, woo. - { - long SeekPos = ct->FileOffset; - long LBARelPos = lba - ct->LBA; - - SeekPos += LBARelPos * DI_Size_Table[ct->DIFormat]; - - if(ct->SubchannelMode) - SeekPos += 96 * (lba - ct->LBA); - - ct->fp->seek(SeekPos, SEEK_SET); - - switch(ct->DIFormat) - { - case DI_FORMAT_AUDIO: - ct->fp->read(buf, 2352); - - if(ct->RawAudioMSBFirst) - Endian_A16_Swap(buf, 588 * 2); - break; - - case DI_FORMAT_MODE1: - ct->fp->read(buf + 12 + 3 + 1, 2048); - encode_mode1_sector(lba + 150, buf); - break; - - case DI_FORMAT_MODE1_RAW: - case DI_FORMAT_MODE2_RAW: - ct->fp->read(buf, 2352); - break; - - case DI_FORMAT_MODE2: - ct->fp->read(buf + 16, 2336); - encode_mode2_sector(lba + 150, buf); - break; - - - // FIXME: M2F1, M2F2, does sub-header come before or after user data(standards say before, but I wonder - // about cdrdao...). - case DI_FORMAT_MODE2_FORM1: - ct->fp->read(buf + 24, 2048); - //encode_mode2_form1_sector(lba + 150, buf); - break; - - case DI_FORMAT_MODE2_FORM2: - ct->fp->read(buf + 24, 2324); - //encode_mode2_form2_sector(lba + 150, buf); - break; - - } - - if(ct->SubchannelMode) - ct->fp->read(buf + 2352, 96); - } - } // end if audible part of audio track read. - break; - } // End if LBA is in range - } // end track search loop - - if(!TrackFound) - { - MDFN_Error(0, _("Could not find track for sector %u!"), lba); - return false; - } - -#if 0 - if(qbuf[0] & 0x40) - { - uint8 dummy_buf[2352 + 96]; - bool any_mismatch = false; - - memcpy(dummy_buf + 16, buf + 16, 2048); - memset(dummy_buf + 2352, 0, 96); - - MakeSubPQ(lba, dummy_buf + 2352); - encode_mode1_sector(lba + 150, dummy_buf); - - for(int i = 0; i < 2352 + 96; i++) - { - if(dummy_buf[i] != buf[i]) - { - printf("Mismatch at %d, %d: %02x:%02x; ", lba, i, dummy_buf[i], buf[i]); - any_mismatch = true; - } + if((Tracks[track].subq_control & SUBQ_CTRLF_DATA) && (FirstTrack < track) && !(Tracks[track - 1].subq_control & SUBQ_CTRLF_DATA)) + et = &Tracks[track - 1]; } - if(any_mismatch) - puts("\n"); - } -#endif - //subq_deinterleave(buf + 2352, qbuf); - //printf("%02x\n", qbuf[0]); - //printf("%02x\n", buf[12 + 3]); - return true; + memset(buf, 0, 2352); + switch(et->DIFormat) + { + case DI_FORMAT_AUDIO: + break; + + case DI_FORMAT_MODE1_RAW: + case DI_FORMAT_MODE1: + encode_mode1_sector(lba + 150, buf); + break; + + case DI_FORMAT_MODE2_RAW: + case DI_FORMAT_MODE2_FORM1: + case DI_FORMAT_MODE2_FORM2: + case DI_FORMAT_MODE2: + case DI_FORMAT_CDI_RAW: + buf[12 + 6] = 0x20; + buf[12 + 10] = 0x20; + encode_mode2_form2_sector(lba + 150, buf); + // TODO: Zero out optional(?) checksum bytes? + break; + } + //printf("Pre/post-gap read, LBA=%d(LBA-track_start_LBA=%d)\n", lba, lba - ct->LBA); + } + else + { + if(ct->AReader) + { + int16_t AudioBuf[588 * 2]; + uint64_t frames_read = ct->AReader->Read((ct->FileOffset / 4) + (lba - ct->LBA) * 588, AudioBuf, 588); + + ct->LastSamplePos += frames_read; + + if(frames_read > 588) // This shouldn't happen. + { + printf("Error: frames_read out of range: %llu\n", (unsigned long long)frames_read); + frames_read = 0; + } + + if(frames_read < 588) + memset((uint8_t *)AudioBuf + frames_read * 2 * sizeof(int16_t), 0, (588 - frames_read) * 2 * sizeof(int16_t)); + + for(int i = 0; i < 588 * 2; i++) + MDFN_en16lsb(buf + i * 2, AudioBuf[i]); + } + else // Binary, woo. + { + long SeekPos = ct->FileOffset; + long LBARelPos = lba - ct->LBA; + + SeekPos += LBARelPos * DI_Size_Table[ct->DIFormat]; + + if(ct->SubchannelMode) + SeekPos += 96 * (lba - ct->LBA); + + ct->fp->seek(SeekPos, SEEK_SET); + + switch(ct->DIFormat) + { + case DI_FORMAT_AUDIO: + ct->fp->read(buf, 2352); + + if(ct->RawAudioMSBFirst) + Endian_A16_Swap(buf, 588 * 2); + break; + + case DI_FORMAT_MODE1: + ct->fp->read(buf + 12 + 3 + 1, 2048); + encode_mode1_sector(lba + 150, buf); + break; + + case DI_FORMAT_MODE1_RAW: + case DI_FORMAT_MODE2_RAW: + case DI_FORMAT_CDI_RAW: + ct->fp->read(buf, 2352); + break; + + case DI_FORMAT_MODE2: + ct->fp->read(buf + 16, 2336); + encode_mode2_sector(lba + 150, buf); + break; + + + // FIXME: M2F1, M2F2, does sub-header come before or after user data(standards say before, but I wonder + // about cdrdao...). + case DI_FORMAT_MODE2_FORM1: + ct->fp->read(buf + 24, 2048); + //encode_mode2_form1_sector(lba + 150, buf); + break; + + case DI_FORMAT_MODE2_FORM2: + ct->fp->read(buf + 24, 2324); + //encode_mode2_form2_sector(lba + 150, buf); + break; + + } + + if(ct->SubchannelMode) + ct->fp->read(buf + 2352, 96); + } + } // end if audible part of audio track read. } -// Note: this function makes use of the current contents(as in |=) in SubPWBuf. -void CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) +bool CDAccess_Image::Fast_Read_Raw_PW_TSRE(uint8_t* pwbuf, int32_t lba) const noexcept { - unsigned i; - uint8_t buf[0xC], adr, control; + int32_t track; + + if(lba >= total_sectors) + { + subpw_synth_leadout_lba(toc, lba, pwbuf); + return(true); + } + + memset(pwbuf, 0, 96); + track = MakeSubPQ(lba, pwbuf); + + // + // If TOC+BIN has embedded subchannel data, we can't fast-read(synthesize) it... + // + if(Tracks[track].SubchannelMode && lba >= (Tracks[track].LBA - Tracks[track].pregap_dv) && (lba < Tracks[track].LBA + Tracks[track].sectors)) + return(false); + + return(true); +} + +// +// Note: this function makes use of the current contents(as in |=) in SubPWBuf. +// +int32_t CDAccess_Image::MakeSubPQ(int32_t lba, uint8_t *SubPWBuf) const +{ + uint8_t buf[0xC]; int32_t track; uint32_t lba_relative; uint32_t ma, sa, fa; @@ -1087,34 +1218,31 @@ void CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) for(track = FirstTrack; track < (FirstTrack + NumTracks); track++) { - if(lba >= (Tracks[track].LBA - Tracks[track].pregap_dv - Tracks[track].pregap) - && lba < (Tracks[track].LBA + Tracks[track].sectors + Tracks[track].postgap)) + if(lba >= (Tracks[track].LBA - Tracks[track].pregap_dv - Tracks[track].pregap) && lba < (Tracks[track].LBA + Tracks[track].sectors + Tracks[track].postgap)) { track_found = true; break; } } - //printf("%d %d\n", Tracks[1].LBA, Tracks[1].sectors); - if(!track_found) - { - printf("MakeSubPQ error for sector %u!", lba); - track = FirstTrack; - } + throw(MDFN_Error(0, _("Could not find track for sector %u!"), lba)); - lba_relative = abs((int32)lba - Tracks[track].LBA); + if(lba < Tracks[track].LBA) + lba_relative = Tracks[track].LBA - 1 - lba; + else + lba_relative = lba - Tracks[track].LBA; - f = (lba_relative % 75); - s = ((lba_relative / 75) % 60); - m = (lba_relative / 75 / 60); + f = (lba_relative % 75); + s = ((lba_relative / 75) % 60); + m = (lba_relative / 75 / 60); - fa = (lba + 150) % 75; - sa = ((lba + 150) / 75) % 60; - ma = ((lba + 150) / 75 / 60); + fa = (lba + 150) % 75; + sa = ((lba + 150) / 75) % 60; + ma = ((lba + 150) / 75 / 60); - adr = 0x1; // Q channel data encodes position - control = Tracks[track].subq_control; + uint8_t adr = 0x1; // Q channel data encodes position + uint8_t control = Tracks[track].subq_control; // Handle pause(D7 of interleaved subchannel byte) bit, should be set to 1 when in pregap or postgap. if((lba < Tracks[track].LBA) || (lba >= Tracks[track].LBA + Tracks[track].sectors)) @@ -1125,7 +1253,7 @@ void CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) // Handle pregap between audio->data track { - int32_t pg_offset = (int32)lba - Tracks[track].LBA; + int32_t pg_offset = (int32_t)lba - Tracks[track].LBA; // If we're more than 2 seconds(150 sectors) from the real "start" of the track/INDEX 01, and the track is a data track, // and the preceding track is an audio track, encode it as audio(by taking the SubQ control field from the preceding track). @@ -1142,21 +1270,35 @@ void CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) } } + memset(buf, 0, 0xC); buf[0] = (adr << 0) | (control << 4); buf[1] = U8_to_BCD(track); - if(lba < Tracks[track].LBA) // Index is 00 in pregap - buf[2] = U8_to_BCD(0x00); - else - buf[2] = U8_to_BCD(0x01); + // Index + //if(lba < Tracks[track].LBA) // Index is 00 in pregap + // buf[2] = U8_to_BCD(0x00); + //else + // buf[2] = U8_to_BCD(0x01); + { + int index = 0; - /* Track relative MSF address */ + for(int32_t i = 0; i < 100; i++) + { + if(lba >= Tracks[track].index[i]) + index = i; + } + buf[2] = U8_to_BCD(index); + } + + // Track relative MSF address buf[3] = U8_to_BCD(m); buf[4] = U8_to_BCD(s); buf[5] = U8_to_BCD(f); - buf[6] = 0; - /* Absolute MSF address */ + + buf[6] = 0; // Zerroooo + + // Absolute MSF address buf[7] = U8_to_BCD(ma); buf[8] = U8_to_BCD(sa); buf[9] = U8_to_BCD(fa); @@ -1166,48 +1308,52 @@ void CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) if(!SubQReplaceMap.empty()) { //printf("%d\n", lba); - std::map::const_iterator it = SubQReplaceMap.find(LBA_to_ABA(lba)); + auto it = SubQReplaceMap.find(LBA_to_ABA(lba)); if(it != SubQReplaceMap.end()) { //printf("Replace: %d\n", lba); - memcpy(buf, it->second.data, 12); + memcpy(buf, it->second.data(), 12); } } - for (i = 0; i < 96; i++) + for(int i = 0; i < 96; i++) SubPWBuf[i] |= (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | pause_or; + + return track; } -bool CDAccess_Image::Read_TOC(TOC *toc) +void CDAccess_Image::Read_TOC(TOC *rtoc) { - unsigned i; + *rtoc = toc; +} - TOC_Clear(toc); +void CDAccess_Image::GenerateTOC(void) +{ + toc.Clear(); - toc->first_track = FirstTrack; - toc->last_track = FirstTrack + NumTracks - 1; - toc->disc_type = disc_type; + toc.first_track = FirstTrack; + toc.last_track = FirstTrack + NumTracks - 1; + toc.disc_type = disc_type; - for(i = toc->first_track; i <= toc->last_track; i++) + for(int i = FirstTrack; i < FirstTrack + NumTracks; i++) { - toc->tracks[i].lba = Tracks[i].LBA; - toc->tracks[i].adr = ADR_CURPOS; - toc->tracks[i].control = Tracks[i].subq_control; + if(Tracks[i].DIFormat == DI_FORMAT_CDI_RAW) + { + toc.first_track = std::min(99, i + 1); + toc.last_track = std::max(toc.first_track, toc.last_track); + } + + toc.tracks[i].lba = Tracks[i].LBA; + toc.tracks[i].adr = ADR_CURPOS; + toc.tracks[i].control = Tracks[i].subq_control; + toc.tracks[i].valid = true; } - toc->tracks[100].lba = total_sectors; - toc->tracks[100].adr = ADR_CURPOS; - toc->tracks[100].control = toc->tracks[toc->last_track].control & 0x4; - - // Convenience leadout track duplication. - if(toc->last_track < 99) - toc->tracks[toc->last_track + 1] = toc->tracks[100]; - - return true; + toc.tracks[100].lba = total_sectors; + toc.tracks[100].adr = ADR_CURPOS; + toc.tracks[100].control = Tracks[FirstTrack + NumTracks - 1].subq_control; + toc.tracks[100].valid = true; } -void CDAccess_Image::Eject(bool eject_status) -{ -} diff --git a/mednafen/cdrom/CDAccess_Image.h b/mednafen/cdrom/CDAccess_Image.h index 27e7b1c..4830282 100644 --- a/mednafen/cdrom/CDAccess_Image.h +++ b/mednafen/cdrom/CDAccess_Image.h @@ -2,9 +2,12 @@ #define __MDFN_CDACCESS_IMAGE_H #include +#include + +#include "CDUtility.h" class Stream; -class AudioReader; +class CDAFReader; struct CDRFILE_TRACK_INFO { @@ -18,7 +21,7 @@ struct CDRFILE_TRACK_INFO int32_t postgap; - int32_t index[2]; + int32_t index[100]; int32_t sectors; // Not including pregap sectors! Stream *fp; @@ -29,21 +32,22 @@ struct CDRFILE_TRACK_INFO uint32_t LastSamplePos; - AudioReader *AReader; + CDAFReader *AReader; }; class CDAccess_Image : public CDAccess { public: - CDAccess_Image(bool *success, const char *path, bool image_memcache); + CDAccess_Image(const std::string& path, bool image_memcache); virtual ~CDAccess_Image(); - virtual bool Read_Raw_Sector(uint8_t *buf, int32_t lba); + virtual void Read_Raw_Sector(uint8_t *buf, int32_t lba); - virtual bool Read_TOC(TOC *toc); + virtual bool Fast_Read_Raw_PW_TSRE(uint8_t* pwbuf, int32_t lba) const noexcept; + + virtual void Read_TOC(TOC *toc); - virtual void Eject(bool eject_status); private: int32_t NumTracks; @@ -52,26 +56,21 @@ class CDAccess_Image : public CDAccess int32_t total_sectors; uint8_t disc_type; CDRFILE_TRACK_INFO Tracks[100]; // Track #0(HMM?) through 99 + TOC toc; - struct cpp11_array_doodad - { - uint8 data[12]; - }; - - std::map SubQReplaceMap; + std::map> SubQReplaceMap; std::string base_dir; - bool ImageOpen(const char *path, bool image_memcache); - int LoadSBI(const char* sbi_path); + bool ImageOpen(const std::string& path, bool image_memcache); + bool LoadSBI(const std::string& sbi_path); + void GenerateTOC(void); void Cleanup(void); // MakeSubPQ will OR the simulated P and Q subchannel data into SubPWBuf. - void MakeSubPQ(int32_t lba, uint8_t *SubPWBuf); + int32_t MakeSubPQ(int32_t lba, uint8_t *SubPWBuf) const; - bool ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int tracknum, - const std::string &filename, const char *binoffset, const char *msfoffset, - const char *length, bool image_memcache, std::map &toc_streamcache); + bool ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int tracknum, const std::string &filename, const char *binoffset, const char *msfoffset, const char *length, bool image_memcache, std::map &toc_streamcache); uint32_t GetSectorCount(CDRFILE_TRACK_INFO *track); }; diff --git a/mednafen/cdrom/CDUtility.c b/mednafen/cdrom/CDUtility.cpp similarity index 64% rename from mednafen/cdrom/CDUtility.c rename to mednafen/cdrom/CDUtility.cpp index fcf115b..10989b7 100644 --- a/mednafen/cdrom/CDUtility.c +++ b/mednafen/cdrom/CDUtility.cpp @@ -17,16 +17,14 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "../mednafen.h" #include "CDUtility.h" -#include "edc_crc32.h" -#include "galois.h" -#include "l-ec.h" -#include "recover-raw.h" +#include "dvdisaster.h" #include "lec.h" #include -/* lookup table for crc calculation */ +// lookup table for crc calculation static uint16_t subq_crctab[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, @@ -61,17 +59,18 @@ static uint16_t subq_crctab[256] = }; -static uint8_t cdutil_scramble_table[2352 - 12]; +static uint8_t scramble_table[2352 - 12]; static bool CDUtility_Inited = false; static void InitScrambleTable(void) { - unsigned i, b; + unsigned i; unsigned cv = 1; for(i = 12; i < 2352; i++) { + unsigned b; unsigned char z = 0; for(b = 0; b < 8; b++) @@ -82,21 +81,23 @@ static void InitScrambleTable(void) cv = (cv >> 1) | (feedback << 14); } - cdutil_scramble_table[i - 12] = z; + scramble_table[i - 12] = z; } + + //for(int i = 0; i < 2352 - 12; i++) + // printf("0x%02x, ", scramble_table[i]); } void CDUtility_Init(void) { - if(!CDUtility_Inited) - { - Init_LEC_Correct(); + if(CDUtility_Inited) + return; - InitScrambleTable(); - lec_tables_init(); + Init_LEC_Correct(); - CDUtility_Inited = true; - } + InitScrambleTable(); + + CDUtility_Inited = true; } void encode_mode0_sector(uint32_t aba, uint8_t *sector_data) @@ -152,9 +153,10 @@ bool edc_lec_check_and_correct(uint8_t *sector_data, bool xa) bool subq_check_checksum(const uint8_t *SubQBuf) { unsigned i; - uint16_t crc = 0; - uint16_t stored_crc = SubQBuf[0xA] << 8; + uint16_t crc = 0; + uint16_t stored_crc = 0; + stored_crc = SubQBuf[0xA] << 8; stored_crc |= SubQBuf[0xB]; for(i = 0; i < 0xA; i++) @@ -173,7 +175,7 @@ void subq_generate_checksum(uint8_t *buf) for(i = 0; i < 0xA; i++) crc = subq_crctab[(crc >> 8) ^ buf[i]] ^ (crc << 8); - /* Checksum */ + // Checksum buf[0xa] = ~(crc >> 8); buf[0xb] = ~(crc); } @@ -181,7 +183,6 @@ void subq_generate_checksum(uint8_t *buf) void subq_deinterleave(const uint8_t *SubPWBuf, uint8_t *qbuf) { unsigned i; - memset(qbuf, 0, 0xC); for(i = 0; i < 96; i++) @@ -192,30 +193,31 @@ void subq_deinterleave(const uint8_t *SubPWBuf, uint8_t *qbuf) // Deinterleaves 96 bytes of subchannel P-W data from 96 bytes of interleaved subchannel PW data. void subpw_deinterleave(const uint8_t *in_buf, uint8_t *out_buf) { - unsigned ch, i; + unsigned ch; assert(in_buf != out_buf); memset(out_buf, 0, 96); for(ch = 0; ch < 8; ch++) { + unsigned i; for(i = 0; i < 96; i++) out_buf[(ch * 12) + (i >> 3)] |= ((in_buf[i] >> (7 - ch)) & 0x1) << (7 - (i & 0x7)); } - } // Interleaves 96 bytes of subchannel P-W data from 96 bytes of uninterleaved subchannel PW data. void subpw_interleave(const uint8_t *in_buf, uint8_t *out_buf) { - unsigned d, bitpoodle, ch; - + unsigned d; assert(in_buf != out_buf); for(d = 0; d < 12; d++) { + unsigned bitpoodle; for(bitpoodle = 0; bitpoodle < 8; bitpoodle++) { + unsigned ch; uint8_t rawb = 0; for(ch = 0; ch < 8; ch++) @@ -231,24 +233,29 @@ void subpw_interleave(const uint8_t *in_buf, uint8_t *out_buf) // and the leadout entry together before extracting the D2 bit. Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement // data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code). // -void subpw_synth_leadout_lba(const struct TOC *toc, const int32_t lba, uint8_t* SubPWBuf) +void subpw_synth_leadout_lba(const TOC& toc, const int32_t lba, uint8_t* SubPWBuf) { - unsigned i; uint8_t buf[0xC]; - uint32_t lba_relative = lba - toc->tracks[100].lba; - uint32_t f = (lba_relative % 75); - uint32_t s = ((lba_relative / 75) % 60); - uint32_t m = (lba_relative / 75 / 60); - uint32_t fa = (lba + 150) % 75; - uint32_t sa = ((lba + 150) / 75) % 60; - uint32_t ma = ((lba + 150) / 75 / 60); + uint32_t lba_relative; + uint32_t ma, sa, fa; + uint32_t m, s, f; - uint8_t adr = 0x1; // Q channel data encodes position - uint8_t control = toc->tracks[100].control; + lba_relative = lba - toc.tracks[100].lba; - if (toc->tracks[toc->last_track].valid) - control |= toc->tracks[toc->last_track].control & 0x4; - else if (toc->disc_type == DISC_TYPE_CD_I) + f = (lba_relative % 75); + s = ((lba_relative / 75) % 60); + m = (lba_relative / 75 / 60); + + fa = (lba + 150) % 75; + sa = ((lba + 150) / 75) % 60; + ma = ((lba + 150) / 75 / 60); + + uint8_t adr = 0x1; // Q channel data encodes position + uint8_t control = toc.tracks[100].control; + + if(toc.tracks[toc.last_track].valid) + control |= toc.tracks[toc.last_track].control & 0x4; + else if(toc.disc_type == DISC_TYPE_CD_I) control |= 0x4; memset(buf, 0, 0xC); @@ -256,25 +263,25 @@ void subpw_synth_leadout_lba(const struct TOC *toc, const int32_t lba, uint8_t* buf[1] = 0xAA; buf[2] = 0x01; - /* Track relative MSF address */ + // Track relative MSF address buf[3] = U8_to_BCD(m); buf[4] = U8_to_BCD(s); buf[5] = U8_to_BCD(f); - buf[6] = 0; /* Zerroooo */ + buf[6] = 0; // Zerroooo - /* Absolute MSF address */ + // Absolute MSF address buf[7] = U8_to_BCD(ma); buf[8] = U8_to_BCD(sa); buf[9] = U8_to_BCD(fa); subq_generate_checksum(buf); - for(i = 0; i < 96; i++) + for(int i = 0; i < 96; i++) SubPWBuf[i] = (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | 0x80; } -void synth_leadout_sector_lba(uint8_t mode, const struct TOC *toc, const int32_t lba, uint8_t* out_buf) +void synth_leadout_sector_lba(uint8_t mode, const TOC& toc, const int32_t lba, uint8_t* out_buf) { memset(out_buf, 0, 2352 + 96); subpw_synth_leadout_lba(toc, lba, out_buf + 2352); @@ -283,7 +290,7 @@ void synth_leadout_sector_lba(uint8_t mode, const struct TOC *toc, const int32_t { if(mode == 0xFF) { - if(toc->disc_type == DISC_TYPE_CD_XA || toc->disc_type == DISC_TYPE_CD_I) + if(toc.disc_type == DISC_TYPE_CD_XA || toc.disc_type == DISC_TYPE_CD_I) mode = 0x02; else mode = 0x01; @@ -308,17 +315,115 @@ void synth_leadout_sector_lba(uint8_t mode, const struct TOC *toc, const int32_t } } +// ISO/IEC 10149:1995 (E): 20.2 +// +void subpw_synth_udapp_lba(const TOC& toc, const int32_t lba, const int32_t lba_subq_relative_offs, uint8_t* SubPWBuf) +{ + uint8_t buf[0xC]; + uint32_t lba_relative; + uint32_t ma, sa, fa; + uint32_t m, s, f; + + if(lba < -150 || lba >= 0) + printf("[BUG] subpw_synth_udapp_lba() lba out of range --- %d\n", lba); + + { + int32_t lba_tmp = lba + lba_subq_relative_offs; + + if(lba_tmp < 0) + lba_relative = 0 - 1 - lba_tmp; + else + lba_relative = lba_tmp - 0; + } + + f = (lba_relative % 75); + s = ((lba_relative / 75) % 60); + m = (lba_relative / 75 / 60); + + fa = (lba + 150) % 75; + sa = ((lba + 150) / 75) % 60; + ma = ((lba + 150) / 75 / 60); + + uint8_t adr = 0x1; // Q channel data encodes position + uint8_t control; + + if(toc.disc_type == DISC_TYPE_CD_I && toc.first_track > 1) + control = 0x4; + else if(toc.tracks[toc.first_track].valid) + control = toc.tracks[toc.first_track].control; + else + control = 0x0; + + memset(buf, 0, 0xC); + buf[0] = (adr << 0) | (control << 4); + buf[1] = U8_to_BCD(toc.first_track); + buf[2] = U8_to_BCD(0x00); + + // Track relative MSF address + buf[3] = U8_to_BCD(m); + buf[4] = U8_to_BCD(s); + buf[5] = U8_to_BCD(f); + + buf[6] = 0; // Zerroooo + + // Absolute MSF address + buf[7] = U8_to_BCD(ma); + buf[8] = U8_to_BCD(sa); + buf[9] = U8_to_BCD(fa); + + subq_generate_checksum(buf); + + for(int i = 0; i < 96; i++) + SubPWBuf[i] = (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | 0x80; +} + +void synth_udapp_sector_lba(uint8_t mode, const TOC& toc, const int32_t lba, int32_t lba_subq_relative_offs, uint8_t* out_buf) +{ + memset(out_buf, 0, 2352 + 96); + subpw_synth_udapp_lba(toc, lba, lba_subq_relative_offs, out_buf + 2352); + + if(out_buf[2352 + 1] & 0x40) + { + if(mode == 0xFF) + { + if(toc.disc_type == DISC_TYPE_CD_XA || toc.disc_type == DISC_TYPE_CD_I) + mode = 0x02; + else + mode = 0x01; + } + + switch(mode) + { + default: + encode_mode0_sector(LBA_to_ABA(lba), out_buf); + break; + + case 0x01: + encode_mode1_sector(LBA_to_ABA(lba), out_buf); + break; + + case 0x02: + out_buf[12 + 6] = 0x20; + out_buf[12 + 10] = 0x20; + encode_mode2_form2_sector(LBA_to_ABA(lba), out_buf); + break; + } + } +} -/* ISO/IEC 10149:1995 (E): 20.2 */ #if 0 -/* TODO/FIXME - missing functions */ -void subpw_synth_udapp_lba(const TOC& toc, const int32 lba, const int32 lba_subq_relative_offs, uint8* SubPWBuf); -void synth_udapp_sector_lba(uint8 mode, const TOC& toc, const int32 lba, int32 lba_subq_relative_offs, uint8* out_buf); +bool subq_extrapolate(const uint8_t *subq_input, int32_t position_delta, uint8_t *subq_output) +{ + assert(subq_check_checksum(subq_input)); + + + subq_generate_checksum(subq_output); +} #endif void scrambleize_data_sector(uint8_t *sector_data) { unsigned i; for(i = 12; i < 2352; i++) - sector_data[i] ^= cdutil_scramble_table[i - 12]; + sector_data[i] ^= scramble_table[i - 12]; } diff --git a/mednafen/cdrom/CDUtility.h b/mednafen/cdrom/CDUtility.h index 4f18489..d33faeb 100644 --- a/mednafen/cdrom/CDUtility.h +++ b/mednafen/cdrom/CDUtility.h @@ -1,233 +1,233 @@ #ifndef __MDFN_CDROM_CDUTILITY_H #define __MDFN_CDROM_CDUTILITY_H -#include -#include + // Call once at app startup before creating any threads that could potentially cause re-entrancy to these functions. + // It will also be called automatically if needed for the first time a function in this namespace that requires + // the initialization function to be called is called, for potential + // usage in constructors of statically-declared objects. + void CDUtility_Init(void); -#include -#include + // Quick definitions here: + // + // ABA - Absolute block address, synonymous to absolute MSF + // aba = (m_a * 60 * 75) + (s_a * 75) + f_a + // + // LBA - Logical block address(related: data CDs are required to have a pregap of 2 seconds, IE 150 frames/sectors) + // lba = aba - 150 -#ifdef __cplusplus -extern "C" { -#endif -enum -{ - ADR_NOQINFO = 0x00, - ADR_CURPOS = 0x01, - ADR_MCN = 0x02, - ADR_ISRC = 0x03 -}; + enum + { + ADR_NOQINFO = 0x00, + ADR_CURPOS = 0x01, + ADR_MCN = 0x02, + ADR_ISRC = 0x03 + }; -struct TOC_Track -{ - uint8_t adr; - uint8_t control; - uint32_t lba; - bool valid; /* valid/present; oh CD-i... */ -}; -// SubQ control field flags. -enum -{ - SUBQ_CTRLF_PRE = 0x01, // With 50/15us pre-emphasis. - SUBQ_CTRLF_DCP = 0x02, // Digital copy permitted. - SUBQ_CTRLF_DATA = 0x04, // Data track. - SUBQ_CTRLF_4CH = 0x08 // 4-channel CD-DA. -}; + struct TOC_Track + { + uint8_t adr; + uint8_t control; + uint32_t lba; + bool valid; // valid/present; oh CD-i... + }; -enum -{ - DISC_TYPE_CDDA_OR_M1 = 0x00, - DISC_TYPE_CD_I = 0x10, - DISC_TYPE_CD_XA = 0x20 -}; + // SubQ control field flags. + enum + { + SUBQ_CTRLF_PRE = 0x01, /* With 50/15us pre-emphasis. */ + SUBQ_CTRLF_DCP = 0x02, /* Digital copy permitted. */ + SUBQ_CTRLF_DATA = 0x04, /* Data track. */ + SUBQ_CTRLF_4CH = 0x08 /* 4-channel CD-DA. */ + }; -struct TOC -{ - uint8_t first_track; - uint8_t last_track; - uint8_t disc_type; - struct TOC_Track tracks[100 + 1]; -}; + enum + { + DISC_TYPE_CDDA_OR_M1 = 0x00, + DISC_TYPE_CD_I = 0x10, + DISC_TYPE_CD_XA = 0x20 + }; -// Call once at app startup before creating any threads that could potentially cause re-entrancy to these functions. -// It will also be called automatically if needed for the first time a function in this namespace that requires -// the initialization function to be called is called, for potential -// usage in constructors of statically-declared objects. -void CDUtility_Init(void); + struct TOC + { + INLINE TOC() + { + Clear(); + } -// Quick definitions here: -// -// ABA - Absolute block address, synonymous to absolute MSF -// aba = (m_a * 60 * 75) + (s_a * 75) + f_a -// -// LBA - Logical block address(related: data CDs are required to have a pregap of 2 seconds, IE 150 frames/sectors) -// lba = aba - 150 + INLINE void Clear(void) + { + first_track = last_track = 0; + disc_type = 0; -static INLINE void TOC_Clear(struct TOC *toc) -{ - if (!toc) - return; + memset(tracks, 0, sizeof(tracks)); // FIXME if we change TOC_Track to non-POD type. + } - toc->first_track = 0; - toc->last_track = 0; - toc->disc_type = 0; - - memset(toc->tracks, 0, sizeof(toc->tracks)); -} - -static INLINE int TOC_FindTrackByLBA(struct TOC *toc, uint32_t LBA) -{ + INLINE int FindTrackByLBA(uint32_t LBA) const + { int32_t track; + int32_t lvt = 0; - for(track = toc->first_track; track <= (toc->last_track + 1); track++) + for(track = 1; track <= 100; track++) { - if(track == (toc->last_track + 1)) - { - if(LBA < toc->tracks[100].lba) - return(track - 1); - } - else - { - if(LBA < toc->tracks[track].lba) - return(track - 1); - } + if(!tracks[track].valid) + continue; + + if(LBA < tracks[track].lba) + break; + + lvt = track; } - return 0; -} + return(lvt); + } -// Address conversion functions. -static INLINE uint32_t AMSF_to_ABA(int32_t m_a, int32_t s_a, int32_t f_a) -{ - return(f_a + 75 * s_a + 75 * 60 * m_a); -} + uint8_t first_track; + uint8_t last_track; + uint8_t disc_type; + TOC_Track tracks[100 + 1]; // [0] is unused, [100] is for the leadout track. + }; -static INLINE void ABA_to_AMSF(uint32_t aba, uint8_t *m_a, uint8_t *s_a, uint8_t *f_a) -{ - *m_a = aba / 75 / 60; - *s_a = (aba - *m_a * 75 * 60) / 75; - *f_a = aba - (*m_a * 75 * 60) - (*s_a * 75); -} + // + // Address conversion functions. + // + static INLINE uint32_t AMSF_to_ABA(int32_t m_a, int32_t s_a, int32_t f_a) + { + return(f_a + 75 * s_a + 75 * 60 * m_a); + } -static INLINE int32_t ABA_to_LBA(uint32_t aba) -{ - return(aba - 150); -} + static INLINE void ABA_to_AMSF(uint32_t aba, uint8_t *m_a, uint8_t *s_a, uint8_t *f_a) + { + *m_a = aba / 75 / 60; + *s_a = (aba - *m_a * 75 * 60) / 75; + *f_a = aba - (*m_a * 75 * 60) - (*s_a * 75); + } -static INLINE uint32_t LBA_to_ABA(int32_t lba) -{ - return(lba + 150); -} + static INLINE int32_t ABA_to_LBA(uint32_t aba) + { + return(aba - 150); + } -static INLINE int32_t AMSF_to_LBA(uint8_t m_a, uint8_t s_a, uint8_t f_a) -{ - return(ABA_to_LBA(AMSF_to_ABA(m_a, s_a, f_a))); -} + static INLINE uint32_t LBA_to_ABA(int32_t lba) + { + return(lba + 150); + } -static INLINE void LBA_to_AMSF(int32_t lba, uint8_t *m_a, uint8_t *s_a, uint8_t *f_a) -{ - ABA_to_AMSF(LBA_to_ABA(lba), m_a, s_a, f_a); -} + static INLINE int32_t AMSF_to_LBA(uint8_t m_a, uint8_t s_a, uint8_t f_a) + { + return(ABA_to_LBA(AMSF_to_ABA(m_a, s_a, f_a))); + } -// -// BCD conversion functions -// -static INLINE bool BCD_is_valid(uint8_t bcd_number) -{ - if((bcd_number & 0xF0) >= 0xA0) - return(false); + static INLINE void LBA_to_AMSF(int32_t lba, uint8_t *m_a, uint8_t *s_a, uint8_t *f_a) + { + ABA_to_AMSF(LBA_to_ABA(lba), m_a, s_a, f_a); + } - if((bcd_number & 0x0F) >= 0x0A) - return(false); + /* BCD conversion functions */ + static INLINE bool BCD_is_valid(uint8_t bcd_number) + { + if((bcd_number & 0xF0) >= 0xA0) + return(false); - return(true); -} + if((bcd_number & 0x0F) >= 0x0A) + return(false); -static INLINE uint8_t BCD_to_U8(uint8_t bcd_number) -{ - return( ((bcd_number >> 4) * 10) + (bcd_number & 0x0F) ); -} + return(true); + } -static INLINE uint8_t U8_to_BCD(uint8_t num) -{ - return( ((num / 10) << 4) + (num % 10) ); -} + static INLINE uint8_t BCD_to_U8(uint8_t bcd_number) + { + return( ((bcd_number >> 4) * 10) + (bcd_number & 0x0F) ); + } -// should always perform the conversion, even if the bcd number is invalid. -static INLINE bool BCD_to_U8_check(uint8_t bcd_number, uint8_t *out_number) -{ - *out_number = BCD_to_U8(bcd_number); + static INLINE uint8_t U8_to_BCD(uint8_t num) + { + return( ((num / 10) << 4) + (num % 10) ); + } - if(!BCD_is_valid(bcd_number)) - return(false); + // should always perform the conversion, even if the bcd number is invalid. + static INLINE bool BCD_to_U8_check(uint8_t bcd_number, uint8_t *out_number) + { + *out_number = BCD_to_U8(bcd_number); - return(true); -} + if(!BCD_is_valid(bcd_number)) + return(false); -// -// Sector data encoding functions(to full 2352 bytes raw sector). -// -// sector_data must be able to contain at least 2352 bytes. -void encode_mode0_sector(uint32_t aba, uint8_t *sector_data); -void encode_mode1_sector(uint32_t aba, uint8_t *sector_data); // 2048 bytes of user data at offset 16 -void encode_mode2_sector(uint32_t aba, uint8_t *sector_data); // 2336 bytes of user data at offset 16 -void encode_mode2_form1_sector(uint32_t aba, uint8_t *sector_data); // 2048+8 bytes of user data at offset 16 -void encode_mode2_form2_sector(uint32_t aba, uint8_t *sector_data); // 2324+8 bytes of user data at offset 16 + return(true); + } + + // + // Sector data encoding functions(to full 2352 bytes raw sector). + // + // sector_data must be able to contain at least 2352 bytes. + void encode_mode0_sector(uint32_t aba, uint8_t *sector_data); + void encode_mode1_sector(uint32_t aba, uint8_t *sector_data); // 2048 bytes of user data at offset 16 + void encode_mode2_sector(uint32_t aba, uint8_t *sector_data); // 2336 bytes of user data at offset 16 + void encode_mode2_form1_sector(uint32_t aba, uint8_t *sector_data); // 2048+8 bytes of user data at offset 16 + void encode_mode2_form2_sector(uint32_t aba, uint8_t *sector_data); // 2324+8 bytes of user data at offset 16 -// out_buf must be able to contain 2352+96 bytes. -// "mode" is only used if(toc.tracks[100].control & 0x4) -void synth_leadout_sector_lba(uint8_t mode, const struct TOC *toc, const int32_t lba, uint8_t* out_buf); + // User data area pre-pause(MSF 00:00:00 through 00:01:74), lba -150 through -1 + // out_buf must be able to contain 2352+96 bytes. + // "mode" is not used if the area is to be encoded as audio. + // pass 0xFF for "mode" for "don't know", and to make guess based on the TOC. + void synth_udapp_sector_lba(uint8_t mode, const TOC& toc, const int32_t lba, int32_t lba_subq_relative_offs, uint8_t* out_buf); + void subpw_synth_udapp_lba(const TOC& toc, const int32_t lba, const int32_t lba_subq_relative_offs, uint8_t* SubPWBuf); -// -// User data error detection and correction -// + // out_buf must be able to contain 2352+96 bytes. + // "mode" is not used if the area is to be encoded as audio. + // pass 0xFF for "mode" for "don't know", and to make guess based on the TOC. + void synth_leadout_sector_lba(uint8_t mode, const TOC& toc, const int32_t lba, uint8_t* out_buf); + void subpw_synth_leadout_lba(const TOC& toc, const int32_t lba, uint8_t* SubPWBuf); -// Check EDC of a mode 1 or mode 2 form 1 sector. -// Returns "true" if checksum is ok(matches). -// Returns "false" if checksum mismatch. -// sector_data should contain 2352 bytes of raw sector data. -bool edc_check(const uint8_t *sector_data, bool xa); -// Check EDC and L-EC data of a mode 1 or mode 2 form 1 sector, and correct bit errors if any exist. -// Returns "true" if errors weren't detected, or they were corrected succesfully. -// Returns "false" if errors couldn't be corrected. -// sector_data should contain 2352 bytes of raw sector data. -bool edc_lec_check_and_correct(uint8_t *sector_data, bool xa); + // + // User data error detection and correction + // -// -// Subchannel(Q in particular) functions -// + // Check EDC of a mode 1 or mode 2 form 1 sector. + // Returns "true" if checksum is ok(matches). + // Returns "false" if checksum mismatch. + // sector_data should contain 2352 bytes of raw sector data. + bool edc_check(const uint8_t *sector_data, bool xa); -// Returns false on checksum mismatch, true on match. -bool subq_check_checksum(const uint8_t *subq_buf); + // Check EDC and L-EC data of a mode 1 or mode 2 form 1 sector, and correct bit errors if any exist. + // Returns "true" if errors weren't detected, or they were corrected succesfully. + // Returns "false" if errors couldn't be corrected. + // sector_data should contain 2352 bytes of raw sector data. + // + // Note: mode 2 form 1 L-EC data can't correct errors in the 4-byte sector header(address + mode), + // but the error(s) will still be detected by EDC. + bool edc_lec_check_and_correct(uint8_t *sector_data, bool xa); -// Calculates the checksum of Q subchannel data(not including the checksum bytes of course ;)) from subq_buf, and stores it into the appropriate position -// in subq_buf. -void subq_generate_checksum(uint8_t *subq_buf); + // + // Subchannel(Q in particular) functions + // -// Deinterleaves 12 bytes of subchannel Q data from 96 bytes of interleaved subchannel PW data. -void subq_deinterleave(const uint8_t *subpw_buf, uint8_t *subq_buf); + // Returns false on checksum mismatch, true on match. + bool subq_check_checksum(const uint8_t *subq_buf); -// Deinterleaves 96 bytes of subchannel P-W data from 96 bytes of interleaved subchannel PW data. -void subpw_deinterleave(const uint8_t *in_buf, uint8_t *out_buf); + // Calculates the checksum of Q subchannel data(not including the checksum bytes of course ;)) from subq_buf, and stores it into the appropriate position + // in subq_buf. + void subq_generate_checksum(uint8_t *subq_buf); -// Interleaves 96 bytes of subchannel P-W data from 96 bytes of uninterleaved subchannel PW data. -void subpw_interleave(const uint8_t *in_buf, uint8_t *out_buf); + // Deinterleaves 12 bytes of subchannel Q data from 96 bytes of interleaved subchannel PW data. + void subq_deinterleave(const uint8_t *subpw_buf, uint8_t *subq_buf); -// Extrapolates Q subchannel current position data from subq_input, with frame/sector delta position_delta, and writes to subq_output. -// Only valid for ADR_CURPOS. -// subq_input must pass subq_check_checksum(). -// TODO -//void subq_extrapolate(const uint8_t *subq_input, int32_t position_delta, uint8_t *subq_output); + // Deinterleaves 96 bytes of subchannel P-W data from 96 bytes of interleaved subchannel PW data. + void subpw_deinterleave(const uint8_t *in_buf, uint8_t *out_buf); -// (De)Scrambles data sector. -void scrambleize_data_sector(uint8_t *sector_data); + // Interleaves 96 bytes of subchannel P-W data from 96 bytes of uninterleaved subchannel PW data. + void subpw_interleave(const uint8_t *in_buf, uint8_t *out_buf); -#ifdef __cplusplus -} -#endif + // Extrapolates Q subchannel current position data from subq_input, with frame/sector delta position_delta, and writes to subq_output. + // Only valid for ADR_CURPOS. + // subq_input must pass subq_check_checksum(). + // TODO + //void subq_extrapolate(const uint8_t *subq_input, int32_t position_delta, uint8_t *subq_output); + + // (De)Scrambles data sector. + void scrambleize_data_sector(uint8_t *sector_data); #endif diff --git a/mednafen/cdrom/SimpleFIFO.h b/mednafen/cdrom/SimpleFIFO.h index bfae152..0e61243 100644 --- a/mednafen/cdrom/SimpleFIFO.h +++ b/mednafen/cdrom/SimpleFIFO.h @@ -1,6 +1,7 @@ #ifndef __MDFN_SIMPLEFIFO_H #define __MDFN_SIMPLEFIFO_H +#include #include #include "../math_ops.h" @@ -11,25 +12,54 @@ class SimpleFIFO public: // Constructor - SimpleFIFO(uint32 the_size) + SimpleFIFO(uint32 the_size) // Size should be a power of 2! { - /* Size should be a power of 2! */ - assert(the_size && !(the_size & (the_size - 1))); - - data = (T*)malloc(the_size * sizeof(T)); - size = the_size; - read_pos = 0; - write_pos = 0; - in_count = 0; + data.resize(round_up_pow2(the_size)); + size = the_size; + read_pos = 0; + write_pos = 0; + in_count = 0; } // Destructor INLINE ~SimpleFIFO() { - if (data) - free(data); + } + INLINE void SaveStatePostLoad(void) + { + read_pos %= data.size(); + write_pos %= data.size(); + in_count %= (data.size() + 1); + } + +#if 0 + INLINE int StateAction(StateMem *sm, int load, int data_only, const char* sname) + { + SFORMAT StateRegs[] = + { + std::vector data; + uint32 size; + + SFVAR(read_pos), + SFVAR(write_pos), + SFVAR(in_count), + SFEND; + } + int ret = MDFNSS_StateAction(sm, load, data_only, sname); + + if(load) + { + read_pos %= data.size(); + write_pos %= data.size(); + in_count %= (data.size() + 1); + } + + return(ret); + } +#endif + INLINE uint32 CanRead(void) { return(in_count); @@ -50,7 +80,7 @@ class SimpleFIFO if(!peek) { - read_pos = (read_pos + 1) & (size - 1); + read_pos = (read_pos + 1) & (data.size() - 1); in_count--; } @@ -72,7 +102,7 @@ class SimpleFIFO { data[write_pos] = *happy_data; - write_pos = (write_pos + 1) & (size - 1); + write_pos = (write_pos + 1) & (data.size() - 1); in_count++; happy_data++; happy_count--; @@ -98,14 +128,8 @@ class SimpleFIFO in_count = 0; } - INLINE void SaveStatePostLoad(void) - { - read_pos %= size; - write_pos %= size; - in_count %= (size + 1); - } - - T* data; + //private: + std::vector data; uint32 size; uint32 read_pos; // Read position uint32 write_pos; // Write position diff --git a/mednafen/cdrom/audioreader.cpp b/mednafen/cdrom/audioreader.cpp deleted file mode 100644 index 0eec0a2..0000000 --- a/mednafen/cdrom/audioreader.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* Mednafen - Multi-system Emulator - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -// AR_Open(), and AudioReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access -// to it for as long as the AudioReader object exists. - -// Don't allow exceptions to propagate into the vorbis/musepack/etc. libraries, as it could easily leave the state of the library's decoder "object" in an -// inconsistent state, which would cause all sorts of unfun when we try to destroy it while handling the exception farther up. - -#include "../mednafen.h" -#include "audioreader.h" - -#include "../tremor/ivorbisfile.h" - -#include -#include -#include - -#include "../general.h" -#include "../mednafen-endian.h" - -AudioReader::AudioReader() : LastReadPos(0) -{ - -} - -AudioReader::~AudioReader() -{ - -} - -int64_t AudioReader::Read_(int16_t *buffer, int64_t frames) -{ - abort(); - return(false); -} - -bool AudioReader::Seek_(int64_t frame_offset) -{ - abort(); - return(false); -} - -int64_t AudioReader::FrameCount(void) -{ - abort(); - return(0); -} - -class OggVorbisReader : public AudioReader -{ - public: - OggVorbisReader(Stream *fp); - ~OggVorbisReader(); - - int64_t Read_(int16_t *buffer, int64_t frames); - bool Seek_(int64_t frame_offset); - int64_t FrameCount(void); - - private: - OggVorbis_File ovfile; -}; - - -static size_t iov_read_func(void *ptr, size_t size, size_t nmemb, void *user_data) -{ - Stream *fw = (Stream*)user_data; - - if(!size || !fw) - return(0); - - return fw->read(ptr, size * nmemb, false) / size; -} - -static int iov_seek_func(void *user_data, ogg_int64_t offset, int whence) -{ - Stream *fw = (Stream*)user_data; - - if (fw) - fw->seek(offset, whence); - return(0); -} - -static int iov_close_func(void *user_data) -{ - Stream *fw = (Stream*)user_data; - - if (fw) - fw->close(); - return(0); -} - -static long iov_tell_func(void *user_data) -{ - Stream *fw = (Stream*)user_data; - - if (!fw) - return -1; - - return fw->tell(); -} - -OggVorbisReader::OggVorbisReader(Stream *fp) -{ - ov_callbacks cb; - - memset(&cb, 0, sizeof(cb)); - cb.read_func = iov_read_func; - cb.seek_func = iov_seek_func; - cb.close_func = iov_close_func; - cb.tell_func = iov_tell_func; - - fp->seek(0, SEEK_SET); - if(ov_open_callbacks(fp, &ovfile, NULL, 0, cb)) - throw(0); -} - -OggVorbisReader::~OggVorbisReader() -{ - ov_clear(&ovfile); -} - -int64_t OggVorbisReader::Read_(int16_t *buffer, int64_t frames) -{ - uint8 *tw_buf = (uint8 *)buffer; - int cursection = 0; - long toread = frames * sizeof(int16_t) * 2; - - while(toread > 0) - { - long didread = ov_read(&ovfile, (char*)tw_buf, toread, &cursection); - - if(didread == 0) - break; - - tw_buf = (uint8 *)tw_buf + didread; - toread -= didread; - } - - return(frames - toread / sizeof(int16_t) / 2); -} - -bool OggVorbisReader::Seek_(int64_t frame_offset) -{ - ov_pcm_seek(&ovfile, frame_offset); - return(true); -} - -int64_t OggVorbisReader::FrameCount(void) -{ - return(ov_pcm_total(&ovfile, -1)); -} - -AudioReader *AR_Open(Stream *fp) -{ - return new OggVorbisReader(fp); -} diff --git a/mednafen/cdrom/audioreader.h b/mednafen/cdrom/audioreader.h deleted file mode 100644 index d6fbdee..0000000 --- a/mednafen/cdrom/audioreader.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef __MDFN_AUDIOREADER_H -#define __MDFN_AUDIOREADER_H - -#include "../Stream.h" - -class AudioReader -{ - public: - AudioReader(); - virtual ~AudioReader(); - - virtual int64_t FrameCount(void); - INLINE int64_t Read(int64_t frame_offset, int16_t *buffer, int64_t frames) - { - int64_t ret; - - if(LastReadPos != frame_offset) - { - //puts("SEEK"); - if(!Seek_(frame_offset)) - return(0); - LastReadPos = frame_offset; - } - - ret = Read_(buffer, frames); - LastReadPos += ret; - return(ret); - } - - private: - virtual int64_t Read_(int16_t *buffer, int64_t frames); - virtual bool Seek_(int64_t frame_offset); - - int64_t LastReadPos; -}; - -// AR_Open(), and AudioReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access -// to it for as long as the AudioReader object exists. -AudioReader *AR_Open(Stream *fp); - -#endif diff --git a/mednafen/cdrom/cdromif.cpp b/mednafen/cdrom/cdromif.cpp index 5394fe4..40316d3 100644 --- a/mednafen/cdrom/cdromif.cpp +++ b/mednafen/cdrom/cdromif.cpp @@ -25,24 +25,21 @@ #include #include -#include #include -#include "../../libretro.h" - -extern retro_log_printf_t log_cb; - enum { - // Status/Error messages - CDIF_MSG_DONE = 0, - CDIF_MSG_INFO, - CDIF_MSG_FATAL_ERROR, + /* Status/Error messages */ + CDIF_MSG_DONE = 0, /* Read -> emu. args: No args. */ + CDIF_MSG_INFO, /* Read -> emu. args: str_message */ + CDIF_MSG_FATAL_ERROR, /* Read -> emu. args: *TODO ARGS* */ - // Command messages. - CDIF_MSG_DIEDIEDIE, - CDIF_MSG_READ_SECTOR, - CDIF_MSG_EJECT + /* Command messages. */ + CDIF_MSG_DIEDIEDIE, /* Emu -> read */ + + CDIF_MSG_READ_SECTOR /* Emu -> read + args[0] = lba + */ }; class CDIF_Message @@ -50,92 +47,25 @@ class CDIF_Message public: CDIF_Message(); - CDIF_Message(unsigned int message_, uint32 arg0 = 0, uint32 arg1 = 0, uint32 arg2 = 0, uint32 arg3 = 0); + CDIF_Message(unsigned int message_, uint32_t arg0 = 0, uint32_t arg1 = 0, uint32_t arg2 = 0, uint32_t arg3 = 0); CDIF_Message(unsigned int message_, const std::string &str); ~CDIF_Message(); unsigned int message; - uint32 args[4]; + uint32_t args[4]; void *parg; std::string str_message; }; -class CDIF_Queue -{ - public: - - CDIF_Queue(); - ~CDIF_Queue(); - - bool Read(CDIF_Message *message, bool blocking = true); - - void Write(const CDIF_Message &message); - - private: - std::queue ze_queue; - slock_t *ze_mutex; - scond_t *ze_cond; -}; - - typedef struct { bool valid; bool error; - uint32 lba; - uint8 data[2352 + 96]; + int32_t lba; + uint8_t data[2352 + 96]; } CDIF_Sector_Buffer; -/* TODO: prohibit copy constructor */ -class CDIF_MT : public CDIF -{ - public: - - CDIF_MT(CDAccess *cda); - virtual ~CDIF_MT(); - - virtual void HintReadSector(uint32 lba); - virtual bool ReadRawSector(uint8 *buf, uint32 lba); - virtual bool ReadRawSectorPWOnly(uint8 *buf, uint32 lba, bool hint_fullread); - - // Return true if operation succeeded or it was a NOP(either due to not being implemented, or the current status matches eject_status). - // Returns false on failure(usually drive error of some kind; not completely fatal, can try again). - virtual bool Eject(bool eject_status); - - // FIXME: Semi-private: - int ReadThreadStart(void); - - private: - - CDAccess *disc_cdaccess; - sthread_t *CDReadThread; - - // Queue for messages to the read thread. - CDIF_Queue ReadThreadQueue; - - // Queue for messages to the emu thread. - CDIF_Queue EmuThreadQueue; - - - enum { SBSize = 256 }; - CDIF_Sector_Buffer SectorBuffers[SBSize]; - - uint32 SBWritePos; - - slock_t *SBMutex; - scond_t *SBCond; - - // - // Read-thread-only: - // - bool RT_EjectDisc(bool eject_status, bool skip_actual_eject = false); - - uint32 ra_lba; - int ra_count; - uint32 last_read_lba; -}; - -/* TODO: prohibit copy constructor */ +// TODO: prohibit copy constructor class CDIF_ST : public CDIF { public: @@ -143,18 +73,17 @@ class CDIF_ST : public CDIF CDIF_ST(CDAccess *cda); virtual ~CDIF_ST(); - virtual void HintReadSector(uint32 lba); - virtual bool ReadRawSector(uint8 *buf, uint32 lba); - virtual bool ReadRawSectorPWOnly(uint8 *buf, uint32 lba, bool hint_fullread); - virtual bool Eject(bool eject_status); + virtual void HintReadSector(int32_t lba); + virtual bool ReadRawSector(uint8_t *buf, int32_t lba); + virtual bool ReadRawSectorPWOnly(uint8_t* pwbuf, int32_t lba, bool hint_fullread); private: CDAccess *disc_cdaccess; }; -CDIF::CDIF() : UnrecoverableError(false), DiscEjected(false) +CDIF::CDIF() : UnrecoverableError(false) { - TOC_Clear(&disc_toc); + } CDIF::~CDIF() @@ -170,7 +99,7 @@ CDIF_Message::CDIF_Message() memset(args, 0, sizeof(args)); } -CDIF_Message::CDIF_Message(unsigned int message_, uint32 arg0, uint32 arg1, uint32 arg2, uint32 arg3) +CDIF_Message::CDIF_Message(unsigned int message_, uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3) { message = message_; args[0] = arg0; @@ -190,242 +119,7 @@ CDIF_Message::~CDIF_Message() } -CDIF_Queue::CDIF_Queue() -{ - ze_mutex = slock_new(); - ze_cond = scond_new(); -} - -CDIF_Queue::~CDIF_Queue() -{ - slock_free(ze_mutex); - scond_free(ze_cond); -} - -// Returns false if message not read, true if it was read. Will always return true if "blocking" is set. -// Will throw MDFN_Error if the read message code is CDIF_MSG_FATAL_ERROR -bool CDIF_Queue::Read(CDIF_Message *message, bool blocking) -{ - bool ret = true; - - slock_lock((slock_t*)ze_mutex); - - if(blocking) - { - while(ze_queue.size() == 0) // while, not just if. - scond_wait((scond_t*)ze_cond, (slock_t*)ze_mutex); - } - - if(ze_queue.size() == 0) - ret = false; - else - { - *message = ze_queue.front(); - ze_queue.pop(); - } - - slock_unlock((slock_t*)ze_mutex); - - if(ret && message->message == CDIF_MSG_FATAL_ERROR) - { - MDFN_Error(0, "%s", message->str_message.c_str()); - return false; - } - - return(ret); -} - -void CDIF_Queue::Write(const CDIF_Message &message) -{ - slock_lock((slock_t*)ze_mutex); - - ze_queue.push(message); - - scond_signal((scond_t*)ze_cond); // Signal while the mutex is held to prevent icky race conditions - - slock_unlock((slock_t*)ze_mutex); -} - - -bool CDIF_MT::RT_EjectDisc(bool eject_status, bool skip_actual_eject) -{ - int32_t old_de = DiscEjected; - - DiscEjected = eject_status; - - if(old_de != DiscEjected) - { - if(!skip_actual_eject) - disc_cdaccess->Eject(eject_status); - - if(!eject_status) // Re-read the TOC - { - disc_cdaccess->Read_TOC(&disc_toc); - - if(disc_toc.first_track < 1 || disc_toc.last_track > 99 || disc_toc.first_track > disc_toc.last_track) - { - MDFN_Error(0, _("TOC first(%d)/last(%d) track numbers bad."), disc_toc.first_track, disc_toc.last_track); - return false; - } - } - - SBWritePos = 0; - ra_lba = 0; - ra_count = 0; - last_read_lba = ~0U; - memset(SectorBuffers, 0, SBSize * sizeof(CDIF_Sector_Buffer)); - } - - return true; -} - -struct RTS_Args -{ - CDIF_MT *cdif_ptr; -}; - -static int ReadThreadStart_C(void *v_arg) -{ - RTS_Args *args = (RTS_Args *)v_arg; - - return args->cdif_ptr->ReadThreadStart(); -} - -int CDIF_MT::ReadThreadStart() -{ - bool Running = true; - - DiscEjected = true; - SBWritePos = 0; - ra_lba = 0; - ra_count = 0; - last_read_lba = ~0U; - - RT_EjectDisc(false, true); - - EmuThreadQueue.Write(CDIF_Message(CDIF_MSG_DONE)); - - while(Running) - { - CDIF_Message msg; - - // Only do a blocking-wait for a message if we don't have any sectors to read-ahead. - // MDFN_DispMessage("%d %d %d\n", last_read_lba, ra_lba, ra_count); - if(ReadThreadQueue.Read(&msg, ra_count ? false : true)) - { - switch(msg.message) - { - case CDIF_MSG_DIEDIEDIE: - Running = false; - break; - - case CDIF_MSG_EJECT: - RT_EjectDisc(msg.args[0]); - EmuThreadQueue.Write(CDIF_Message(CDIF_MSG_DONE)); - break; - - case CDIF_MSG_READ_SECTOR: - { - static const int max_ra = 16; - static const int initial_ra = 1; - static const int speedmult_ra = 2; - uint32_t new_lba = msg.args[0]; - - assert((unsigned int)max_ra < (SBSize / 4)); - - if(last_read_lba != ~0U && new_lba == (last_read_lba + 1)) - { - int how_far_ahead = ra_lba - new_lba; - - if(how_far_ahead <= max_ra) - ra_count = MIN(speedmult_ra, 1 + max_ra - how_far_ahead); - else - ra_count++; - } - else if(new_lba != last_read_lba) - { - ra_lba = new_lba; - ra_count = initial_ra; - } - - last_read_lba = new_lba; - } - break; - } - } - - // Don't read >= the "end" of the disc, silly snake. Slither. - if(ra_count && ra_lba == disc_toc.tracks[100].lba) - { - ra_count = 0; - //printf("Ephemeral scarabs: %d!\n", ra_lba); - } - - if(ra_count) - { - uint8_t tmpbuf[2352 + 96]; - bool error_condition = false; - - disc_cdaccess->Read_Raw_Sector(tmpbuf, ra_lba); - - slock_lock((slock_t*)SBMutex); - - SectorBuffers[SBWritePos].lba = ra_lba; - memcpy(SectorBuffers[SBWritePos].data, tmpbuf, 2352 + 96); - SectorBuffers[SBWritePos].valid = true; - SectorBuffers[SBWritePos].error = error_condition; - SBWritePos = (SBWritePos + 1) % SBSize; - - scond_signal((scond_t*)SBCond); - slock_unlock((slock_t*)SBMutex); - - ra_lba++; - ra_count--; - } - } - - return(1); -} - -CDIF_MT::CDIF_MT(CDAccess *cda) : disc_cdaccess(cda), CDReadThread(NULL), SBMutex(NULL), SBCond(NULL) -{ - CDIF_Message msg; - RTS_Args s; - - SBMutex = slock_new(); - SBCond = scond_new(); - UnrecoverableError = false; - - s.cdif_ptr = this; - - CDReadThread = sthread_create((void (*)(void*))ReadThreadStart_C, &s); - EmuThreadQueue.Read(&msg); -} - - -CDIF_MT::~CDIF_MT() -{ - bool thread_deaded_failed = false; - - ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_DIEDIEDIE)); - - if(!thread_deaded_failed) - sthread_join((sthread_t*)CDReadThread); - - if(SBMutex) - { - slock_free((slock_t*)SBMutex); - SBMutex = NULL; - } - - if(disc_cdaccess) - { - delete disc_cdaccess; - disc_cdaccess = NULL; - } -} - -bool CDIF::ValidateRawSector(uint8 *buf) +bool CDIF::ValidateRawSector(uint8_t *buf) { int mode = buf[12 + 3]; @@ -438,174 +132,91 @@ bool CDIF::ValidateRawSector(uint8 *buf) return(true); } -bool CDIF_MT::ReadRawSector(uint8 *buf, uint32 lba) -{ - bool found = false; - bool error_condition = false; - - if(UnrecoverableError) - { - memset(buf, 0, 2352 + 96); - return(false); - } - - // This shouldn't happen, the emulated-system-specific CDROM emulation code should make sure the emulated program doesn't try - // to read past the last "real" sector of the disc. - if(lba >= disc_toc.tracks[100].lba) - { - printf("Attempt to read LBA %d, >= LBA %d\n", lba, disc_toc.tracks[100].lba); - return(false); - } - - ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_READ_SECTOR, lba)); - - slock_lock((slock_t*)SBMutex); - - do - { - int i; - for(i = 0; i < SBSize; i++) - { - if(SectorBuffers[i].valid && SectorBuffers[i].lba == lba) - { - error_condition = SectorBuffers[i].error; - memcpy(buf, SectorBuffers[i].data, 2352 + 96); - found = true; - } - } - - if(!found) - scond_wait((scond_t*)SBCond, (slock_t*)SBMutex); - } while(!found); - - slock_unlock((slock_t*)SBMutex); - - return(!error_condition); -} - -bool CDIF_MT::ReadRawSectorPWOnly(uint8 *buf, uint32 lba, bool hint_fullread) -{ - uint8 tmpbuf[2352 + 96]; - bool ret; - - if(UnrecoverableError) - { - memset(buf, 0, 96); - return(false); - } - - // This shouldn't happen, the emulated-system-specific CDROM emulation code should make sure the emulated program doesn't try - // to read past the last "real" sector of the disc. - if(lba >= disc_toc.tracks[100].lba) - { - printf("Attempt to read LBA %d, >= LBA %d\n", lba, disc_toc.tracks[100].lba); - memset(buf, 0, 96); - return(false); - } - - ret = ReadRawSector(tmpbuf, lba); - memcpy(buf, tmpbuf + 2352, 96); - - return ret; -} - -void CDIF_MT::HintReadSector(uint32 lba) -{ - if(UnrecoverableError) - return; - - ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_READ_SECTOR, lba)); -} - -int CDIF::ReadSector(uint8* pBuf, uint32 lba, uint32 nSectors) +int CDIF::ReadSector(uint8_t* buf, int32_t lba, uint32_t sector_count, bool suppress_uncorrectable_message) { int ret = 0; if(UnrecoverableError) return(false); - while(nSectors--) + while(sector_count--) { - int mode; uint8_t tmpbuf[2352 + 96]; if(!ReadRawSector(tmpbuf, lba)) { puts("CDIF Raw Read error"); - return(false); + return false; } if(!ValidateRawSector(tmpbuf)) - return(false); + { + if(!suppress_uncorrectable_message) + { + MDFN_DispMessage("Uncorrectable data at sector %d", lba); + log_cb(RETRO_LOG_ERROR, "Uncorrectable data at sector %d\n", lba); + } - mode = tmpbuf[12 + 3]; + return(false); + } + + const int mode = tmpbuf[12 + 3]; if(!ret) ret = mode; - switch (mode) + if(mode == 1) { - case 1: - memcpy(pBuf, &tmpbuf[12 + 4], 2048); - break; - case 2: - memcpy(pBuf, &tmpbuf[12 + 4 + 8], 2048); - break; - default: - printf("CDIF_ReadSector() invalid sector type at LBA=%u\n", (unsigned int)lba); - return(false); + memcpy(buf, &tmpbuf[12 + 4], 2048); + } + else if(mode == 2) + { + memcpy(buf, &tmpbuf[12 + 4 + 8], 2048); + } + else + { + printf("CDIF_ReadSector() invalid sector type at LBA=%u\n", (unsigned int)lba); + return(false); } - pBuf += 2048; + buf += 2048; lba++; } return(ret); } -bool CDIF_MT::Eject(bool eject_status) -{ - CDIF_Message msg; - - if(UnrecoverableError) - return(false); - - ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_EJECT, eject_status)); - EmuThreadQueue.Read(&msg); - - return(true); -} - +// +// // Single-threaded implementation follows. +// +// CDIF_ST::CDIF_ST(CDAccess *cda) : disc_cdaccess(cda) { //puts("***WARNING USING SINGLE-THREADED CD READER***"); UnrecoverableError = false; - DiscEjected = false; disc_cdaccess->Read_TOC(&disc_toc); if(disc_toc.first_track < 1 || disc_toc.last_track > 99 || disc_toc.first_track > disc_toc.last_track) - throw(MDFN_Error(0, _("TOC first(%d)/last(%d) track numbers bad."), disc_toc.first_track, disc_toc.last_track)); + { + throw(MDFN_Error(0, "TOC first(%d)/last(%d) track numbers bad.", disc_toc.first_track, disc_toc.last_track)); + } } CDIF_ST::~CDIF_ST() { - if(disc_cdaccess) - { - delete disc_cdaccess; - disc_cdaccess = NULL; - } + } -void CDIF_ST::HintReadSector(uint32 lba) +void CDIF_ST::HintReadSector(int32_t lba) { - /* TODO: disc_cdaccess seek hint? (probably not, would require asynchronousitycamel) */ + // TODO: disc_cdaccess seek hint? (probably not, would require asynchronousitycamel) } -bool CDIF_ST::ReadRawSector(uint8 *buf, uint32 lba) +bool CDIF_ST::ReadRawSector(uint8_t *buf, int32_t lba) { if(UnrecoverableError) { @@ -613,208 +224,50 @@ bool CDIF_ST::ReadRawSector(uint8 *buf, uint32 lba) return(false); } + if(lba < LBA_Read_Minimum || lba > LBA_Read_Maximum) + { + printf("Attempt to read sector out of bounds; LBA=%d\n", lba); + memset(buf, 0, 2352 + 96); + return(false); + } + disc_cdaccess->Read_Raw_Sector(buf, lba); return(true); } -bool CDIF_ST::ReadRawSectorPWOnly(uint8 *buf, uint32 lba, bool hint_fullread) -{ - uint8 tmpbuf[2352 + 96]; - bool ret; - - if(UnrecoverableError) - { - memset(buf, 0, 96); - return(false); - } - - // This shouldn't happen, the emulated-system-specific CDROM emulation code should make sure the emulated program doesn't try - // to read past the last "real" sector of the disc. - if(lba >= disc_toc.tracks[100].lba) - { - printf("Attempt to read LBA %d, >= LBA %d\n", lba, disc_toc.tracks[100].lba); - memset(buf, 0, 96); - return(false); - } - - ret = ReadRawSector(tmpbuf, lba); - memcpy(buf, tmpbuf + 2352, 96); - - return ret; -} - -bool CDIF_ST::Eject(bool eject_status) +bool CDIF_ST::ReadRawSectorPWOnly(uint8_t* pwbuf, int32_t lba, bool hint_fullread) { if(UnrecoverableError) + { + memset(pwbuf, 0, 96); return(false); - - int32_t old_de = DiscEjected; - - DiscEjected = eject_status; - - if(old_de != DiscEjected) - { - disc_cdaccess->Eject(eject_status); - - if(!eject_status) // Re-read the TOC - { - disc_cdaccess->Read_TOC(&disc_toc); - - if(disc_toc.first_track < 1 || disc_toc.last_track > 99 || disc_toc.first_track > disc_toc.last_track) - { - MDFN_Error(0, _("TOC first(%d)/last(%d) track numbers bad."), disc_toc.first_track, disc_toc.last_track); - return false; - } - } } - return(true); -} - - -class CDIF_Stream_Thing : public Stream -{ - public: - - CDIF_Stream_Thing(CDIF *cdintf_arg, uint32 lba_arg, uint32 sector_count_arg); - ~CDIF_Stream_Thing(); - - virtual uint64 attributes(void); - virtual uint8 *map(void); - virtual void unmap(void); - - virtual uint64 read(void *data, uint64 count, bool error_on_eos = true); - virtual void write(const void *data, uint64 count); - - virtual void seek(int64 offset, int whence); - virtual int64 tell(void); - virtual int64 size(void); - virtual void close(void); - - private: - CDIF *cdintf; - const uint32 start_lba; - const uint32 sector_count; - int64 position; -}; - -CDIF_Stream_Thing::CDIF_Stream_Thing(CDIF *cdintf_arg, uint32 start_lba_arg, - uint32 sector_count_arg) : cdintf(cdintf_arg), start_lba(start_lba_arg), sector_count(sector_count_arg) -{ - -} - -CDIF_Stream_Thing::~CDIF_Stream_Thing() -{ - -} - -uint64 CDIF_Stream_Thing::attributes(void) -{ - return(ATTRIBUTE_READABLE | ATTRIBUTE_SEEKABLE); -} - -uint8 *CDIF_Stream_Thing::map(void) -{ - return NULL; -} - -void CDIF_Stream_Thing::unmap(void) -{ - -} - -uint64 CDIF_Stream_Thing::read(void *data, uint64 count, bool error_on_eos) -{ - uint64_t rp; - - if(count > (((uint64)sector_count * 2048) - position)) + if(lba < LBA_Read_Minimum || lba > LBA_Read_Maximum) { - if(error_on_eos) - throw MDFN_Error(0, "EOF"); - - count = ((uint64)sector_count * 2048) - position; + printf("Attempt to read sector out of bounds; LBA=%d\n", lba); + memset(pwbuf, 0, 96); + return(false); } - if(!count) - return(0); - - for(rp = position; rp < (position + count); rp = (rp &~ 2047) + 2048) + if(disc_cdaccess->Fast_Read_Raw_PW_TSRE(pwbuf, lba)) + return(true); + else { - uint8_t buf[2048]; + uint8_t tmpbuf[2352 + 96]; + bool ret; - if(!cdintf->ReadSector(buf, start_lba + (rp / 2048), 1)) - throw MDFN_Error(ErrnoHolder(EIO)); + ret = ReadRawSector(tmpbuf, lba); + memcpy(pwbuf, tmpbuf + 2352, 96); - memcpy((uint8_t*)data + (rp - position), - buf + (rp & 2047), - std::min(2048 - (rp & 2047),count - (rp - position)) - ); + return ret; } - - position += count; - - return count; } -void CDIF_Stream_Thing::write(const void *data, uint64 count) +CDIF *CDIF_Open(const std::string& path, bool image_memcache) { - throw MDFN_Error(ErrnoHolder(EBADF)); -} - -void CDIF_Stream_Thing::seek(int64 offset, int whence) -{ - int64 new_position; - - switch(whence) - { - case SEEK_SET: - new_position = offset; - break; - - case SEEK_CUR: - new_position = position + offset; - break; - - case SEEK_END: - new_position = ((int64)sector_count * 2048) + offset; - break; - } - - if(new_position < 0 || new_position > ((int64)sector_count * 2048)) - throw MDFN_Error(ErrnoHolder(EINVAL)); - - position = new_position; -} - -int64 CDIF_Stream_Thing::tell(void) -{ - return position; -} - -int64 CDIF_Stream_Thing::size(void) -{ - return(sector_count * 2048); -} - -void CDIF_Stream_Thing::close(void) -{ - -} - - -Stream *CDIF::MakeStream(uint32 lba, uint32 sector_count) -{ - return new CDIF_Stream_Thing(this, lba, sector_count); -} - - -CDIF *CDIF_Open(bool *success, const char *path, const bool is_device, bool image_memcache) -{ - CDAccess *cda = cdaccess_open_image(success, path, image_memcache); - - if(!image_memcache) - return new CDIF_MT(cda); - return new CDIF_ST(cda); + CDAccess *cda = CDAccess_Open(path, image_memcache); + + return new CDIF_ST(cda); } diff --git a/mednafen/cdrom/cdromif.h b/mednafen/cdrom/cdromif.h index a7f7200..92100fb 100644 --- a/mednafen/cdrom/cdromif.h +++ b/mednafen/cdrom/cdromif.h @@ -19,50 +19,46 @@ #define __MDFN_CDROM_CDROMIF_H #include "CDUtility.h" -#include "../Stream.h" +#include #include -typedef TOC CD_TOC; - class CDIF { - public: + public: - CDIF(); - virtual ~CDIF(); + CDIF(); + virtual ~CDIF(); - inline void ReadTOC(TOC *read_target) - { - *read_target = disc_toc; - } + static const int32_t LBA_Read_Minimum = -150; + static const int32_t LBA_Read_Maximum = 449849; // 100 * 75 * 60 - 150 - 1 - virtual void HintReadSector(uint32_t lba) = 0; - virtual bool ReadRawSector(uint8_t *buf, uint32_t lba) = 0; - virtual bool ReadRawSectorPWOnly(uint8_t *buf, uint32_t lba, bool hint_fullread) = 0; + inline void ReadTOC(TOC *read_target) + { + *read_target = disc_toc; + } - // Call for mode 1 or mode 2 form 1 only. - bool ValidateRawSector(uint8_t *buf); + virtual void HintReadSector(int32_t lba) = 0; + virtual bool ReadRawSector(uint8_t *buf, int32_t lba) = 0; // Reads 2352+96 bytes of data into buf. + virtual bool ReadRawSectorPWOnly(uint8_t* pwbuf, int32_t lba, bool hint_fullread) = 0; // Reads 96 bytes(of raw subchannel PW data) into pwbuf. - // Utility/Wrapped functions - // Reads mode 1 and mode2 form 1 sectors(2048 bytes per sector returned) - // Will return the type(1, 2) of the first sector read to the buffer supplied, 0 on error - int ReadSector(uint8_t *pBuf, uint32_t lba, uint32_t nSectors); + // Call for mode 1 or mode 2 form 1 only. + bool ValidateRawSector(uint8_t *buf); - // Return true if operation succeeded or it was a NOP(either due to not being implemented, or the current status matches eject_status). - // Returns false on failure(usually drive error of some kind; not completely fatal, can try again). - virtual bool Eject(bool eject_status) = 0; + // Utility/Wrapped functions + // Reads mode 1 and mode2 form 1 sectors(2048 bytes per sector returned) + // Will return the type(1, 2) of the first sector read to the buffer supplied, 0 on error + int ReadSector(uint8_t* buf, int32_t lba, uint32_t sector_count, bool suppress_uncorrectable_message = false); - // For Mode 1, or Mode 2 Form 1. - // No reference counting or whatever is done, so if you destroy the CDIF object before you destroy the returned Stream, things will go BOOM. - Stream *MakeStream(uint32_t lba, uint32_t sector_count); + // For Mode 1, or Mode 2 Form 1. + // No reference counting or whatever is done, so if you destroy the CDIF object before you destroy the returned Stream, things will go BOOM. + Stream *MakeStream(int32_t lba, uint32_t sector_count); - protected: - bool UnrecoverableError; - TOC disc_toc; - bool DiscEjected; + protected: + bool UnrecoverableError; + TOC disc_toc; }; -CDIF *CDIF_Open(bool *success, const char *path, const bool is_device, bool image_memcache); +CDIF *CDIF_Open(const std::string& path, bool image_memcache); #endif diff --git a/mednafen/cdrom/dvdisaster.h b/mednafen/cdrom/dvdisaster.h new file mode 100644 index 0000000..89703d7 --- /dev/null +++ b/mednafen/cdrom/dvdisaster.h @@ -0,0 +1,170 @@ +/* dvdisaster: Additional error correction for optical media. + * Copyright (C) 2004-2007 Carsten Gnoerlich. + * Project home page: http://www.dvdisaster.com + * Email: carsten@dvdisaster.com -or- cgnoerlich@fsfe.org + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA, + * or direct your browser at http://www.gnu.org. + */ + +#ifndef DVDISASTER_H +#define DVDISASTER_H + +/* "Dare to be gorgeous and unique. + * But don't ever be cryptic or otherwise unfathomable. + * Make it unforgettably great." + * + * From "A Final Note on Style", + * Amiga Intuition Reference Manual, 1986, p. 231 + */ + +/*** + *** I'm too lazy to mess with #include dependencies. + *** Everything #includeable is rolled up herein... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*** + *** dvdisaster.c + ***/ + +void PrepareDeadSector(void); + +void CreateEcc(void); +void FixEcc(void); +void Verify(void); + +uint32_t EDCCrc32(const unsigned char*, int); + +/*** + *** galois.c + *** + * This is currently the hardcoded GF(2**8). + * int32_t gives abundant space for the GF. + * Squeezing it down to uint8 won't probably gain much, + * so we implement this defensively here. + * + * Note that some performance critical stuff needs to + * be #included from galois-inlines.h + */ + +/* Galois field parameters for 8bit symbol Reed-Solomon code */ + +#define GF_SYMBOLSIZE 8 +#define GF_FIELDSIZE (1< +#include "dvdisaster.h" /*** *** EDC checksum used in CDROM sectors @@ -47,7 +47,7 @@ /* */ /*****************************************************************/ -unsigned long edctable[256] = +static const unsigned long edctable[256] = { 0x00000000L, 0x90910101L, 0x91210201L, 0x01B00300L, 0x92410401L, 0x02D00500L, 0x03600600L, 0x93F10701L, @@ -120,11 +120,11 @@ unsigned long edctable[256] = */ uint32_t EDCCrc32(const unsigned char *data, int len) -{ - uint32_t crc = 0; +{ + uint32_t crc = 0; - while(len--) - crc = edctable[(crc ^ *data++) & 0xFF] ^ (crc >> 8); + while(len--) + crc = edctable[(crc ^ *data++) & 0xFF] ^ (crc >> 8); - return crc; + return crc; } diff --git a/mednafen/cdrom/edc_crc32.h b/mednafen/cdrom/edc_crc32.h deleted file mode 100644 index 63b0a3f..0000000 --- a/mednafen/cdrom/edc_crc32.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _EDC_CRC32_H -#define _EDC_CRC32_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -uint32_t EDCCrc32(const unsigned char*, int); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/mednafen/cdrom/galois-inlines.h b/mednafen/cdrom/galois-inlines.h new file mode 100644 index 0000000..b15902f --- /dev/null +++ b/mednafen/cdrom/galois-inlines.h @@ -0,0 +1,40 @@ +/* dvdisaster: Additional error correction for optical media. + * Copyright (C) 2004-2007 Carsten Gnoerlich. + * Project home page: http://www.dvdisaster.com + * Email: carsten@dvdisaster.com -or- cgnoerlich@fsfe.org + * + * The Reed-Solomon error correction draws a lot of inspiration - and even code - + * from Phil Karn's excellent Reed-Solomon library: http://www.ka9q.net/code/fec/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA, + * or direct your browser at http://www.gnu.org. + */ + +#include "dvdisaster.h" + +/* + * The following routine is performance critical. + */ + +static inline int mod_fieldmax(int x) +{ + while (x >= GF_FIELDMAX) + { + x -= GF_FIELDMAX; + x = (x >> GF_SYMBOLSIZE) + (x & GF_FIELDMAX); + } + + return x; +} diff --git a/mednafen/cdrom/galois.c b/mednafen/cdrom/galois.cpp similarity index 71% rename from mednafen/cdrom/galois.c rename to mednafen/cdrom/galois.cpp index 0e701e9..b6d4291 100644 --- a/mednafen/cdrom/galois.c +++ b/mednafen/cdrom/galois.cpp @@ -22,9 +22,9 @@ * or direct your browser at http://www.gnu.org. */ -#include -#include -#include "galois.h" +#include "dvdisaster.h" + +#include "galois-inlines.h" /*** *** Galois field arithmetic. @@ -36,10 +36,11 @@ /* Initialize the Galois field tables */ + GaloisTables* CreateGaloisTables(int32_t gf_generator) -{ - int32_t b,log; +{ GaloisTables *gt = (GaloisTables *)calloc(1, sizeof(GaloisTables)); + int32_t b,log; /* Allocate the tables. The encoder uses a special version of alpha_to which has the mod_fieldmax() @@ -50,7 +51,7 @@ GaloisTables* CreateGaloisTables(int32_t gf_generator) gt->indexOf = (int32_t *)calloc(GF_FIELDSIZE, sizeof(int32_t)); gt->alphaTo = (int32_t *)calloc(GF_FIELDSIZE, sizeof(int32_t)); gt->encAlphaTo = (int32_t *)calloc(2*GF_FIELDSIZE, sizeof(int32_t)); - + /* create the log/ilog values */ for(b=1, log=0; logalphaTo[log] = b; b = b << 1; if(b & GF_FIELDSIZE) - b = b ^ gf_generator; + b = b ^ gf_generator; } if(b!=1) { - printf("Failed to create the Galois field log tables!\n"); - exit(1); + printf("Failed to create the Galois field log tables!\n"); + exit(1); } /* we're even closed using infinity (makes things easier) */ @@ -73,21 +74,18 @@ GaloisTables* CreateGaloisTables(int32_t gf_generator) gt->alphaTo[GF_ALPHA0] = 0; /* and the other way around */ for(b=0; b<2*GF_FIELDSIZE; b++) - gt->encAlphaTo[b] = gt->alphaTo[mod_fieldmax(b)]; + gt->encAlphaTo[b] = gt->alphaTo[mod_fieldmax(b)]; return gt; } void FreeGaloisTables(GaloisTables *gt) { - if(gt->indexOf) - free(gt->indexOf); - if(gt->alphaTo) - free(gt->alphaTo); - if(gt->encAlphaTo) - free(gt->encAlphaTo); + if(gt->indexOf) free(gt->indexOf); + if(gt->alphaTo) free(gt->alphaTo); + if(gt->encAlphaTo) free(gt->encAlphaTo); - free(gt); + free(gt); } /*** @@ -96,13 +94,11 @@ void FreeGaloisTables(GaloisTables *gt) */ ReedSolomonTables *CreateReedSolomonTables(GaloisTables *gt, - int32_t first_consecutive_root, - int32_t prim_elem, - int nroots_in) -{ + int32_t first_consecutive_root, + int32_t prim_elem, + int nroots_in) +{ ReedSolomonTables *rt = (ReedSolomonTables *)calloc(1, sizeof(ReedSolomonTables)); int32_t i,j,root; - ReedSolomonTables *rt = (ReedSolomonTables *) - calloc(1, sizeof(ReedSolomonTables)); rt->gfTables = gt; rt->fcr = first_consecutive_root; @@ -119,31 +115,42 @@ ReedSolomonTables *CreateReedSolomonTables(GaloisTables *gt, for(i=0, root=first_consecutive_root*prim_elem; inroots; i++, root+=prim_elem) { rt->gpoly[i+1] = 1; - /* Multiply gpoly by alpha**(root+x) */ + /* Multiply gpoly by alpha**(root+x) */ - for(j=i; j>0; j--) - { - if(rt->gpoly[j] != 0) - rt->gpoly[j] = rt->gpoly[j-1] ^ gt->alphaTo[mod_fieldmax(gt->indexOf[rt->gpoly[j]] + root)]; - else - rt->gpoly[j] = rt->gpoly[j-1]; - } + for(j=i; j>0; j--) + { + if(rt->gpoly[j] != 0) + rt->gpoly[j] = rt->gpoly[j-1] ^ gt->alphaTo[mod_fieldmax(gt->indexOf[rt->gpoly[j]] + root)]; + else + rt->gpoly[j] = rt->gpoly[j-1]; + } - rt->gpoly[0] = gt->alphaTo[mod_fieldmax(gt->indexOf[rt->gpoly[0]] + root)]; + rt->gpoly[0] = gt->alphaTo[mod_fieldmax(gt->indexOf[rt->gpoly[0]] + root)]; } /* Store the polynomials index for faster encoding */ for(i=0; i<=rt->nroots; i++) - rt->gpoly[i] = gt->indexOf[rt->gpoly[i]]; + rt->gpoly[i] = gt->indexOf[rt->gpoly[i]]; + +#if 0 + /* for the precalculated unrolled loops only */ + + for(i=gt->nroots-1; i>0; i--) + PrintCLI( + " par_idx[((++spk)&%d)] ^= enc_alpha_to[feedback + %3d];\n", + nroots-1,gt->gpoly[i]); + + PrintCLI(" par_idx[sp] = enc_alpha_to[feedback + %3d];\n", + gt->gpoly[0]); +#endif return rt; } void FreeReedSolomonTables(ReedSolomonTables *rt) { - if(rt->gpoly) - free(rt->gpoly); + if(rt->gpoly) free(rt->gpoly); - free(rt); + free(rt); } diff --git a/mednafen/cdrom/galois.h b/mednafen/cdrom/galois.h deleted file mode 100644 index d7f4381..0000000 --- a/mednafen/cdrom/galois.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef _GALOIS_H -#define _GALOIS_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/*** - *** galois.c - *** - * This is currently the hardcoded GF(2**8). - * int32_t gives abundant space for the GF. - * Squeezing it down to uint8 won't probably gain much, - * so we implement this defensively here. - * - * Note that some performance critical stuff needs to - * be #included from galois-inlines.h - */ - -/* Galois field parameters for 8bit symbol Reed-Solomon code */ - -#define GF_SYMBOLSIZE 8 -#define GF_FIELDSIZE (1<= GF_FIELDMAX) - { - x -= GF_FIELDMAX; - x = (x >> GF_SYMBOLSIZE) + (x & GF_FIELDMAX); - } - - return x; -} - -GaloisTables* CreateGaloisTables(int32_t a); -void FreeGaloisTables(GaloisTables *a); - -ReedSolomonTables *CreateReedSolomonTables(GaloisTables *a, int32_t b, int32_t c, int d); -void FreeReedSolomonTables(ReedSolomonTables *a); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/mednafen/cdrom/l-ec.c b/mednafen/cdrom/l-ec.cpp similarity index 78% rename from mednafen/cdrom/l-ec.c rename to mednafen/cdrom/l-ec.cpp index 59701a3..a92409a 100644 --- a/mednafen/cdrom/l-ec.c +++ b/mednafen/cdrom/l-ec.cpp @@ -22,9 +22,9 @@ * or direct your browser at http://www.gnu.org. */ -#include "l-ec.h" -#include -#include +#include "dvdisaster.h" + +#include "galois-inlines.h" #include @@ -228,8 +228,7 @@ int CountC2Errors(unsigned char *frame) int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, int *erasure_list, int erasure_count) -{ - GaloisTables *gt = rt->gfTables; +{ GaloisTables *gt = rt->gfTables; int syndrome[NROOTS]; int lambda[NROOTS+1]; int omega[NROOTS+1]; @@ -244,18 +243,18 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, int corrected = 0; int i,j,k; int r,el; - + /*** Form the syndromes: Evaluate data(x) at roots of g(x) */ for(i=0; ialphaTo[mod_fieldmax(gt->indexOf[syndrome[i]] - + (LEC_FIRST_ROOT+i)*LEC_PRIM_ELEM)]; + for(i=0; ialphaTo[mod_fieldmax(gt->indexOf[syndrome[i]] + + (LEC_FIRST_ROOT+i)*LEC_PRIM_ELEM)]; /*** Convert syndrome to index form, check for nonzero condition. */ @@ -268,7 +267,7 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, /*** If the syndrome is zero, everything is fine. */ if(!syn_error) - return 0; + return 0; /*** Initialize lambda to be the erasure locator polynomial */ @@ -279,7 +278,7 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, erasure_list[1] += padding; if(erasure_count > 2) /* sanity check */ - erasure_count = 0; + erasure_count = 0; if(erasure_count > 0) { lambda[1] = gt->alphaTo[mod_fieldmax(LEC_PRIM_ELEM*(GF_FIELDMAX-1-erasure_list[0]))]; @@ -288,17 +287,17 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, { int u = mod_fieldmax(LEC_PRIM_ELEM*(GF_FIELDMAX-1-erasure_list[i])); for(j=i+1; j>0; j--) - { int tmp = gt->indexOf[lambda[j-1]]; - + { int tmp = gt->indexOf[lambda[j-1]]; + if(tmp != GF_ALPHA0) - lambda[j] ^= gt->alphaTo[mod_fieldmax(u + tmp)]; - } + lambda[j] ^= gt->alphaTo[mod_fieldmax(u + tmp)]; + } } } for(i=0; iindexOf[lambda[i]]; - + b[i] = gt->indexOf[lambda[i]]; + /*** Berlekamp-Massey algorithm to determine error+erasure locator polynomial */ r = erasure_count; /* r is the step number */ @@ -310,42 +309,42 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, { int discr_r = 0; for(i=0; ialphaTo[mod_fieldmax(gt->indexOf[lambda[i]] + syndrome[r-i-1])]; + if((lambda[i] != 0) && (syndrome[r-i-1] != GF_ALPHA0)) + discr_r ^= gt->alphaTo[mod_fieldmax(gt->indexOf[lambda[i]] + syndrome[r-i-1])]; discr_r = gt->indexOf[discr_r]; if(discr_r == GF_ALPHA0) { /* B(x) = x*B(x) */ - memmove(b+1, b, NROOTS*sizeof(b[0])); - b[0] = GF_ALPHA0; + memmove(b+1, b, NROOTS*sizeof(b[0])); + b[0] = GF_ALPHA0; } else { int t[NROOTS+1]; - /* T(x) = lambda(x) - discr_r*x*b(x) */ - t[0] = lambda[0]; - for(i=0; ialphaTo[mod_fieldmax(discr_r + b[i])]; - else t[i+1] = lambda[i+1]; - } + /* T(x) = lambda(x) - discr_r*x*b(x) */ + t[0] = lambda[0]; + for(i=0; ialphaTo[mod_fieldmax(discr_r + b[i])]; + else t[i+1] = lambda[i+1]; + } - if(2*el <= r+erasure_count-1) - { el = r + erasure_count - el; + if(2*el <= r+erasure_count-1) + { el = r + erasure_count - el; - /* B(x) <-- inv(discr_r) * lambda(x) */ - for(i=0; i<=NROOTS; i++) - b[i] = (lambda[i] == 0) ? GF_ALPHA0 - : mod_fieldmax(gt->indexOf[lambda[i]] - discr_r + GF_FIELDMAX); - } - else - { /* 2 lines below: B(x) <-- x*B(x) */ - memmove(b+1, b, NROOTS*sizeof(b[0])); - b[0] = GF_ALPHA0; - } + /* B(x) <-- inv(discr_r) * lambda(x) */ + for(i=0; i<=NROOTS; i++) + b[i] = (lambda[i] == 0) ? GF_ALPHA0 + : mod_fieldmax(gt->indexOf[lambda[i]] - discr_r + GF_FIELDMAX); + } + else + { /* 2 lines below: B(x) <-- x*B(x) */ + memmove(b+1, b, NROOTS*sizeof(b[0])); + b[0] = GF_ALPHA0; + } - memcpy(lambda, t, (NROOTS+1)*sizeof(t[0])); + memcpy(lambda, t, (NROOTS+1)*sizeof(t[0])); } } @@ -355,7 +354,7 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, for(i=0; iindexOf[lambda[i]]; if(lambda[i] != GF_ALPHA0) - deg_lambda = i; + deg_lambda = i; } /*** Find roots of the error+erasure locator polynomial by Chien search */ @@ -368,9 +367,9 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, for(j=deg_lambda; j>0; j--) { if(reg[j] != GF_ALPHA0) - { reg[j] = mod_fieldmax(reg[j] + j); - q ^= gt->alphaTo[reg[j]]; - } + { reg[j] = mod_fieldmax(reg[j] + j); + q ^= gt->alphaTo[reg[j]]; + } } if(q != 0) continue; /* Not a root */ @@ -402,7 +401,7 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, for(j=i; j>=0; j--) { if((syndrome[i - j] != GF_ALPHA0) && (lambda[j] != GF_ALPHA0)) - tmp ^= gt->alphaTo[mod_fieldmax(syndrome[i - j] + lambda[j])]; + tmp ^= gt->alphaTo[mod_fieldmax(syndrome[i - j] + lambda[j])]; } omega[i] = gt->indexOf[tmp]; @@ -421,54 +420,59 @@ int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, for(i=deg_omega; i>=0; i--) { if(omega[i] != GF_ALPHA0) - num1 ^= gt->alphaTo[mod_fieldmax(omega[i] + i * root[j])]; + num1 ^= gt->alphaTo[mod_fieldmax(omega[i] + i * root[j])]; } num2 = gt->alphaTo[mod_fieldmax(root[j] * (LEC_FIRST_ROOT - 1) + GF_FIELDMAX)]; den = 0; - + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ for(i=MIN(deg_lambda, NROOTS-1) & ~1; i>=0; i-=2) { if(lambda[i+1] != GF_ALPHA0) - den ^= gt->alphaTo[mod_fieldmax(lambda[i+1] + i * root[j])]; + den ^= gt->alphaTo[mod_fieldmax(lambda[i+1] + i * root[j])]; } /* Apply error to data */ if(num1 != 0 && location >= padding) { - corrected++; - data[location-padding] ^= gt->alphaTo[mod_fieldmax(gt->indexOf[num1] + gt->indexOf[num2] - + GF_FIELDMAX - gt->indexOf[den])]; + corrected++; + data[location-padding] ^= gt->alphaTo[mod_fieldmax(gt->indexOf[num1] + gt->indexOf[num2] + + GF_FIELDMAX - gt->indexOf[den])]; - /* If no erasures were given, at most one error was corrected. - Return its position in erasure_list[0]. */ + /* If no erasures were given, at most one error was corrected. + Return its position in erasure_list[0]. */ - if(!erasure_count) - erasure_list[0] = location-padding; + if(!erasure_count) + erasure_list[0] = location-padding; } - else - return -3; +#if 1 + else return -3; +#endif } /*** Form the syndromes: Evaluate data(x) at roots of g(x) */ for(i=0; ialphaTo[mod_fieldmax(gt->indexOf[syndrome[i]] - + (LEC_FIRST_ROOT+i)*LEC_PRIM_ELEM)]; - } + for(i=0; ialphaTo[mod_fieldmax(gt->indexOf[syndrome[i]] + + (LEC_FIRST_ROOT+i)*LEC_PRIM_ELEM)]; + } /*** Convert syndrome to index form, check for nonzero condition. */ +#if 1 for(i=0; i -#include "galois.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define N_P_VECTORS 86 /* 43 16bit p vectors */ -#define P_VECTOR_SIZE 26 /* using RS(26,24) ECC */ - -#define N_Q_VECTORS 52 /* 26 16bit q vectors */ -#define Q_VECTOR_SIZE 45 /* using RS(45,43) ECC */ - -#define P_PADDING 229 /* padding values for */ -#define Q_PADDING 210 /* shortened RS code */ - -int PToByteIndex(int, int); -int QToByteIndex(int, int); -void ByteIndexToP(int, int*, int*); -void ByteIndexToQ(int, int*, int*); - -void GetPVector(unsigned char*, unsigned char*, int); -void SetPVector(unsigned char*, unsigned char*, int); -void FillPVector(unsigned char*, unsigned char, int); -void AndPVector(unsigned char*, unsigned char, int); -void OrPVector(unsigned char*, unsigned char, int); - -void GetQVector(unsigned char*, unsigned char*, int); -void SetQVector(unsigned char*, unsigned char*, int); -void FillQVector(unsigned char*, unsigned char, int); -void AndQVector(unsigned char*, unsigned char, int); -void OrQVector(unsigned char*, unsigned char, int); - -int DecodePQ(ReedSolomonTables*, unsigned char*, int, int*, int); - -int CountC2Errors(unsigned char*); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/mednafen/cdrom/lec.c b/mednafen/cdrom/lec.c deleted file mode 100644 index 93ca06a..0000000 --- a/mednafen/cdrom/lec.c +++ /dev/null @@ -1,555 +0,0 @@ -/* cdrdao - write audio CD-Rs in disc-at-once mode - * - * Copyright (C) 1998-2002 Andreas Mueller - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include - -#include "lec.h" - -#define GF8_PRIM_POLY 0x11d /* x^8 + x^4 + x^3 + x^2 + 1 */ - -#define EDC_POLY 0x8001801b /* (x^16 + x^15 + x^2 + 1) (x^16 + x^2 + x + 1) */ - -#define LEC_HEADER_OFFSET 12 -#define LEC_DATA_OFFSET 16 -#define LEC_MODE1_DATA_LEN 2048 -#define LEC_MODE1_EDC_OFFSET 2064 -#define LEC_MODE1_INTERMEDIATE_OFFSET 2068 -#define LEC_MODE1_P_PARITY_OFFSET 2076 -#define LEC_MODE1_Q_PARITY_OFFSET 2248 -#define LEC_MODE2_FORM1_DATA_LEN (2048+8) -#define LEC_MODE2_FORM1_EDC_OFFSET 2072 -#define LEC_MODE2_FORM2_DATA_LEN (2324+8) -#define LEC_MODE2_FORM2_EDC_OFFSET 2348 - -static uint8_t GF8_LOG[256]; -static uint8_t GF8_ILOG[256]; - -uint16_t cf8_table[43][256]; -uint32_t crc_table[256]; -uint8_t scramble_table[2340]; - -/* Addition in the GF(8) domain: just the XOR of the values. - */ -#define gf8_add(a, b) (a) ^ (b) - -/* Division in the GF(8) domain: Like multiplication but logarithms a - * subtracted. - */ -static uint8_t gf8_div(uint8_t a, uint8_t b) -{ - int16_t sum; - - assert(b != 0); - - if (a == 0) - return 0; - - sum = GF8_LOG[a] - GF8_LOG[b]; - - if (sum < 0) - sum += 255; - - return GF8_ILOG[sum]; -} - - -/* Reverses the bits in 'd'. 'bits' defines the bit width of 'd'. - */ -static uint32_t mirror_bits(uint32_t d, int bits) -{ - int i; - uint32_t r = 0; - - for (i = 0; i < bits; i++) - { - r <<= 1; - - if ((d & 0x1) != 0) - r |= 0x1; - - d >>= 1; - } - - return r; -} - - -/* Calculates the CRC of given data with given lengths based on the - * table lookup algorithm. - */ -static uint32_t calc_edc(uint8_t *data, int len) -{ - uint32_t crc = 0; - - while (len--) - crc = crc_table[(int)(crc ^ *data++) & 0xff] ^ (crc >> 8); - - return crc; -} - -/* Build the scramble table as defined in the yellow book. The bytes - 12 to 2351 of a sector will be XORed with the data of this table. - */ -static void scramble_table_init(void) -{ - uint8_t d; - uint16_t i, j; - uint16_t reg = 1; - - for (i = 0; i < 2340; i++) - { - d = 0; - - for (j = 0; j < 8; j++) - { - d >>= 1; - - if ((reg & 0x1) != 0) - d |= 0x80; - - reg >>= 1; - if ((reg & 0x1) != ((reg >> 1) & 0x1)) - reg |= 0x4000; /* 15-bit register */ - } - - scramble_table[i] = d; - } -} - -/* Build the CRC lookup table for EDC_POLY poly. The CRC is 32 bit wide - * and reversed (i.e. the bit stream is divided by the EDC_POLY with the - * LSB first order). - */ -static void crc_table_init(void) -{ - uint32_t i, j; - uint32_t r; - - for (i = 0; i < 256; i++) - { - r = mirror_bits(i, 8); - - r <<= 24; - - for (j = 0; j < 8; j++) - { - r <<= 1; - if ((r & 0x80000000) != 0) - r ^= EDC_POLY; - } - - r = mirror_bits(r, 32); - - crc_table[i] = r; - } -} - -/* Creates the logarithm and inverse logarithm table that is required - * for performing multiplication in the GF(8) domain. - */ -static void gf8_create_log_tables(void) -{ - uint8_t log; - uint16_t b; - - for (b = 0; b <= 255; b++) - { - GF8_LOG[b] = 0; - GF8_ILOG[b] = 0; - } - - b = 1; - - for (log = 0; log < 255; log++) - { - GF8_LOG[(uint8_t)b] = log; - GF8_ILOG[log] = (uint8_t)b; - - b <<= 1; - - if ((b & 0x100) != 0) - b ^= GF8_PRIM_POLY; - } -} - -static void cf8_table_init(void) -{ - int i, j; - uint16_t c; - uint8_t GF8_COEFFS_HELP[2][45]; - uint8_t GF8_Q_COEFFS[2][45]; - - gf8_create_log_tables(); - - /* build matrix H: - * 1 1 ... 1 1 - * a^44 a^43 ... a^1 a^0 - * - * - */ - - for (j = 0; j < 45; j++) - { - GF8_COEFFS_HELP[0][j] = 1; /* e0 */ - GF8_COEFFS_HELP[1][j] = GF8_ILOG[44-j]; /* e1 */ - } - - - /* resolve equation system for parity byte 0 and 1 */ - - /* e1' = e1 + e0 */ - for (j = 0; j < 45; j++) - GF8_Q_COEFFS[1][j] = gf8_add(GF8_COEFFS_HELP[1][j], - GF8_COEFFS_HELP[0][j]); - - /* e1'' = e1' / (a^1 + 1) */ - for (j = 0; j < 45; j++) - GF8_Q_COEFFS[1][j] = gf8_div(GF8_Q_COEFFS[1][j], GF8_Q_COEFFS[1][43]); - - /* e0' = e0 + e1 / a^1 */ - for (j = 0; j < 45; j++) - GF8_Q_COEFFS[0][j] = gf8_add(GF8_COEFFS_HELP[0][j], - gf8_div(GF8_COEFFS_HELP[1][j], - GF8_ILOG[1])); - - /* e0'' = e0' / (1 + 1 / a^1) */ - for (j = 0; j < 45; j++) - GF8_Q_COEFFS[0][j] = gf8_div(GF8_Q_COEFFS[0][j], GF8_Q_COEFFS[0][44]); - - /* - * Compute the products of 0..255 with all of the Q coefficients in - * advance. When building the scalar product between the data vectors - * and the P/Q vectors the individual products can be looked up in - * this table - * - * The P parity coefficients are just a subset of the Q coefficients so - * that we do not need to create a separate table for them. - */ - - for (j = 0; j < 43; j++) - { - - cf8_table[j][0] = 0; - - for (i = 1; i < 256; i++) - { - c = GF8_LOG[i] + GF8_LOG[GF8_Q_COEFFS[0][j]]; - if (c >= 255) c -= 255; - cf8_table[j][i] = GF8_ILOG[c]; - - c = GF8_LOG[i] + GF8_LOG[GF8_Q_COEFFS[1][j]]; - if (c >= 255) c -= 255; - cf8_table[j][i] |= GF8_ILOG[c]<<8; - } - } -} - -void lec_tables_init(void) -{ - scramble_table_init(); - crc_table_init(); - cf8_table_init(); -} - -/* Calc EDC for a MODE 1 sector - */ -static void calc_mode1_edc(uint8_t *sector) -{ - uint32_t crc = calc_edc(sector, LEC_MODE1_DATA_LEN + 16); - - sector[LEC_MODE1_EDC_OFFSET] = crc & 0xffL; - sector[LEC_MODE1_EDC_OFFSET + 1] = (crc >> 8) & 0xffL; - sector[LEC_MODE1_EDC_OFFSET + 2] = (crc >> 16) & 0xffL; - sector[LEC_MODE1_EDC_OFFSET + 3] = (crc >> 24) & 0xffL; -} - -/* Calc EDC for a XA form 1 sector - */ -static void calc_mode2_form1_edc(uint8_t *sector) -{ - uint32_t crc = calc_edc(sector + LEC_DATA_OFFSET, - LEC_MODE2_FORM1_DATA_LEN); - - sector[LEC_MODE2_FORM1_EDC_OFFSET] = crc & 0xffL; - sector[LEC_MODE2_FORM1_EDC_OFFSET + 1] = (crc >> 8) & 0xffL; - sector[LEC_MODE2_FORM1_EDC_OFFSET + 2] = (crc >> 16) & 0xffL; - sector[LEC_MODE2_FORM1_EDC_OFFSET + 3] = (crc >> 24) & 0xffL; -} - -/* Calc EDC for a XA form 2 sector - */ -static void calc_mode2_form2_edc(uint8_t *sector) -{ - uint32_t crc = calc_edc(sector + LEC_DATA_OFFSET, - LEC_MODE2_FORM2_DATA_LEN); - - sector[LEC_MODE2_FORM2_EDC_OFFSET] = crc & 0xffL; - sector[LEC_MODE2_FORM2_EDC_OFFSET + 1] = (crc >> 8) & 0xffL; - sector[LEC_MODE2_FORM2_EDC_OFFSET + 2] = (crc >> 16) & 0xffL; - sector[LEC_MODE2_FORM2_EDC_OFFSET + 3] = (crc >> 24) & 0xffL; -} - -/* Writes the sync pattern to the given sector. - */ -static void set_sync_pattern(uint8_t *sector) -{ - sector[0] = 0; - - sector[1] = sector[2] = sector[3] = sector[4] = sector[5] = - sector[6] = sector[7] = sector[8] = sector[9] = sector[10] = 0xff; - - sector[11] = 0; -} - - -static uint8_t bin2bcd(uint8_t b) -{ - return (((b/10) << 4) & 0xf0) | ((b%10) & 0x0f); -} - -/* Builds the sector header. - */ -static void set_sector_header(uint8_t mode, uint32_t adr, uint8_t *sector) -{ - sector[LEC_HEADER_OFFSET] = bin2bcd(adr / (60*75)); - sector[LEC_HEADER_OFFSET + 1] = bin2bcd((adr / 75) % 60); - sector[LEC_HEADER_OFFSET + 2] = bin2bcd(adr % 75); - sector[LEC_HEADER_OFFSET + 3] = mode; -} - -/* Calculate the P parities for the sector. - * The 43 P vectors of length 24 are combined with the GF8_P_COEFFS. - */ -static void calc_P_parity(uint8_t *sector) -{ - int i, j; - uint16_t p01_msb, p01_lsb; - uint8_t *p_lsb_start; - uint8_t *p_lsb; - uint8_t *p0, *p1; - uint8_t d0,d1; - - p_lsb_start = sector + LEC_HEADER_OFFSET; - - p1 = sector + LEC_MODE1_P_PARITY_OFFSET; - p0 = sector + LEC_MODE1_P_PARITY_OFFSET + 2 * 43; - - for (i = 0; i <= 42; i++) - { - p_lsb = p_lsb_start; - - p01_lsb = p01_msb = 0; - - for (j = 19; j <= 42; j++) - { - d0 = *p_lsb; - d1 = *(p_lsb+1); - - p01_lsb ^= cf8_table[j][d0]; - p01_msb ^= cf8_table[j][d1]; - - p_lsb += 2 * 43; - } - - *p0 = p01_lsb; - *(p0 + 1) = p01_msb; - - *p1 = p01_lsb>>8; - *(p1 + 1) = p01_msb>>8; - - p0 += 2; - p1 += 2; - - p_lsb_start += 2; - } -} - -/* Calculate the Q parities for the sector. - * The 26 Q vectors of length 43 are combined with the GF8_Q_COEFFS. - */ -static void calc_Q_parity(uint8_t *sector) -{ - int i, j; - uint16_t q01_lsb, q01_msb; - uint8_t *q_lsb_start; - uint8_t *q_lsb; - uint8_t *q0, *q1, *q_start; - uint8_t d0,d1; - - q_lsb_start = sector + LEC_HEADER_OFFSET; - - q_start = sector + LEC_MODE1_Q_PARITY_OFFSET; - q1 = sector + LEC_MODE1_Q_PARITY_OFFSET; - q0 = sector + LEC_MODE1_Q_PARITY_OFFSET + 2 * 26; - - for (i = 0; i <= 25; i++) - { - q_lsb = q_lsb_start; - - q01_lsb = q01_msb = 0; - - for (j = 0; j <= 42; j++) - { - d0 = *q_lsb; - d1 = *(q_lsb+1); - - q01_lsb ^= cf8_table[j][d0]; - q01_msb ^= cf8_table[j][d1]; - - q_lsb += 2 * 44; - - if (q_lsb >= q_start) - q_lsb -= 2 * 1118; - } - - *q0 = q01_lsb; - *(q0 + 1) = q01_msb; - - *q1 = q01_lsb>>8; - *(q1 + 1) = q01_msb>>8; - - q0 += 2; - q1 += 2; - - q_lsb_start += 2 * 43; - } -} - -/* Encodes a MODE 0 sector. - * 'adr' is the current physical sector address - * 'sector' must be 2352 byte wide - */ -void lec_encode_mode0_sector(uint32_t adr, uint8_t *sector) -{ - uint16_t i; - - set_sync_pattern(sector); - set_sector_header(0, adr, sector); - - sector += 16; - - for (i = 0; i < 2336; i++) - *sector++ = 0; -} - -/* Encodes a MODE 1 sector. - * 'adr' is the current physical sector address - * 'sector' must be 2352 byte wide containing 2048 bytes user data at - * offset 16 - */ -void lec_encode_mode1_sector(uint32_t adr, uint8_t *sector) -{ - set_sync_pattern(sector); - set_sector_header(1, adr, sector); - - calc_mode1_edc(sector); - - /* clear the intermediate field */ - sector[LEC_MODE1_INTERMEDIATE_OFFSET] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 1] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 2] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 3] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 4] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 5] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 6] = - sector[LEC_MODE1_INTERMEDIATE_OFFSET + 7] = 0; - - calc_P_parity(sector); - calc_Q_parity(sector); -} - -/* Encodes a MODE 2 sector. - * 'adr' is the current physical sector address - * 'sector' must be 2352 byte wide containing 2336 bytes user data at - * offset 16 - */ -void lec_encode_mode2_sector(uint32_t adr, uint8_t *sector) -{ - set_sync_pattern(sector); - set_sector_header(2, adr, sector); -} - -/* Encodes a XA form 1 sector. - * 'adr' is the current physical sector address - * 'sector' must be 2352 byte wide containing 2048+8 bytes user data at - * offset 16 - */ -void lec_encode_mode2_form1_sector(uint32_t adr, uint8_t *sector) -{ - set_sync_pattern(sector); - - calc_mode2_form1_edc(sector); - - /* P/Q partiy must not contain the sector header so clear it */ - sector[LEC_HEADER_OFFSET] = - sector[LEC_HEADER_OFFSET + 1] = - sector[LEC_HEADER_OFFSET + 2] = - sector[LEC_HEADER_OFFSET + 3] = 0; - - calc_P_parity(sector); - calc_Q_parity(sector); - - /* finally add the sector header */ - set_sector_header(2, adr, sector); -} - -/* Encodes a XA form 2 sector. - * 'adr' is the current physical sector address - * 'sector' must be 2352 byte wide containing 2324+8 bytes user data at - * offset 16 - */ -void lec_encode_mode2_form2_sector(uint32_t adr, uint8_t *sector) -{ - set_sync_pattern(sector); - - calc_mode2_form2_edc(sector); - - set_sector_header(2, adr, sector); -} - -/* Scrambles and byte swaps an encoded sector. - * 'sector' must be 2352 byte wide. - */ -void lec_scramble(uint8_t *sector) -{ - uint16_t i; - uint8_t *p = sector; - uint8_t tmp; - const uint8_t *stable = scramble_table; - - for (i = 0; i < 6; i++) - { - /* just swap bytes of sector sync */ - tmp = *p; - *p = *(p + 1); - p++; - *p++ = tmp; - } - - for (;i < (2352 / 2); i++) - { - /* scramble and swap bytes */ - tmp = *p ^ *stable++; - *p = *(p + 1) ^ *stable++; - p++; - *p++ = tmp; - } -} diff --git a/mednafen/cdrom/lec.cpp b/mednafen/cdrom/lec.cpp new file mode 100644 index 0000000..9c22a0b --- /dev/null +++ b/mednafen/cdrom/lec.cpp @@ -0,0 +1,688 @@ +/* cdrdao - write audio CD-Rs in disc-at-once mode + * + * Copyright (C) 1998-2002 Andreas Mueller + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include + +#include "lec.h" + +#define GF8_PRIM_POLY 0x11d /* x^8 + x^4 + x^3 + x^2 + 1 */ + +#define EDC_POLY 0x8001801b /* (x^16 + x^15 + x^2 + 1) (x^16 + x^2 + x + 1) */ + +#define LEC_HEADER_OFFSET 12 +#define LEC_DATA_OFFSET 16 +#define LEC_MODE1_DATA_LEN 2048 +#define LEC_MODE1_EDC_OFFSET 2064 +#define LEC_MODE1_INTERMEDIATE_OFFSET 2068 +#define LEC_MODE1_P_PARITY_OFFSET 2076 +#define LEC_MODE1_Q_PARITY_OFFSET 2248 +#define LEC_MODE2_FORM1_DATA_LEN (2048+8) +#define LEC_MODE2_FORM1_EDC_OFFSET 2072 +#define LEC_MODE2_FORM2_DATA_LEN (2324+8) +#define LEC_MODE2_FORM2_EDC_OFFSET 2348 + + +typedef uint8_t gf8_t; + +static uint8_t GF8_LOG[256]; +static gf8_t GF8_ILOG[256]; + +static const class Gf8_Q_Coeffs_Results_01 { +private: + uint16_t table[43][256]; +public: + Gf8_Q_Coeffs_Results_01(); + ~Gf8_Q_Coeffs_Results_01() {} + const uint16_t *operator[] (int i) const { return &table[i][0]; } + operator const uint16_t *() const { return &table[0][0]; } +} CF8_Q_COEFFS_RESULTS_01; + +static const class CrcTable { +private: + uint32_t table[256]; +public: + CrcTable(); + ~CrcTable() {} + uint32_t operator[](int i) const { return table[i]; } + operator const uint32_t *() const { return table; } +} CRCTABLE; + +static const class ScrambleTable { +private: + uint8_t table[2340]; +public: + ScrambleTable(); + ~ScrambleTable() {} + uint8_t operator[](int i) const { return table[i]; } + operator const uint8_t *() const { return table; } +} SCRAMBLE_TABLE; + +/* Creates the logarithm and inverse logarithm table that is required + * for performing multiplication in the GF(8) domain. + */ +static void gf8_create_log_tables() +{ + uint8_t log; + uint16_t b; + + for (b = 0; b <= 255; b++) { + GF8_LOG[b] = 0; + GF8_ILOG[b] = 0; + } + + b = 1; + + for (log = 0; log < 255; log++) { + GF8_LOG[(uint8_t)b] = log; + GF8_ILOG[log] = (uint8_t)b; + + b <<= 1; + + if ((b & 0x100) != 0) + b ^= GF8_PRIM_POLY; + } +} + +/* Addition in the GF(8) domain: just the XOR of the values. + */ +#define gf8_add(a, b) (a) ^ (b) + + +/* Multiplication in the GF(8) domain: add the logarithms (modulo 255) + * and return the inverse logarithm. Not used! + */ +#if 0 +static gf8_t gf8_mult(gf8_t a, gf8_t b) +{ + int16_t sum; + + if (a == 0 || b == 0) + return 0; + + sum = GF8_LOG[a] + GF8_LOG[b]; + + if (sum >= 255) + sum -= 255; + + return GF8_ILOG[sum]; +} +#endif + +/* Division in the GF(8) domain: Like multiplication but logarithms a + * subtracted. + */ +static gf8_t gf8_div(gf8_t a, gf8_t b) +{ + int16_t sum; + + assert(b != 0); + + if (a == 0) + return 0; + + sum = GF8_LOG[a] - GF8_LOG[b]; + + if (sum < 0) + sum += 255; + + return GF8_ILOG[sum]; +} + +Gf8_Q_Coeffs_Results_01::Gf8_Q_Coeffs_Results_01() +{ + int i, j; + uint16_t c; + gf8_t GF8_COEFFS_HELP[2][45]; + uint8_t GF8_Q_COEFFS[2][45]; + + + gf8_create_log_tables(); + + /* build matrix H: + * 1 1 ... 1 1 + * a^44 a^43 ... a^1 a^0 + * + * + */ + + for (j = 0; j < 45; j++) { + GF8_COEFFS_HELP[0][j] = 1; /* e0 */ + GF8_COEFFS_HELP[1][j] = GF8_ILOG[44-j]; /* e1 */ + } + + + /* resolve equation system for parity byte 0 and 1 */ + + /* e1' = e1 + e0 */ + for (j = 0; j < 45; j++) { + GF8_Q_COEFFS[1][j] = gf8_add(GF8_COEFFS_HELP[1][j], + GF8_COEFFS_HELP[0][j]); + } + + /* e1'' = e1' / (a^1 + 1) */ + for (j = 0; j < 45; j++) { + GF8_Q_COEFFS[1][j] = gf8_div(GF8_Q_COEFFS[1][j], GF8_Q_COEFFS[1][43]); + } + + /* e0' = e0 + e1 / a^1 */ + for (j = 0; j < 45; j++) { + GF8_Q_COEFFS[0][j] = gf8_add(GF8_COEFFS_HELP[0][j], + gf8_div(GF8_COEFFS_HELP[1][j], + GF8_ILOG[1])); + } + + /* e0'' = e0' / (1 + 1 / a^1) */ + for (j = 0; j < 45; j++) { + GF8_Q_COEFFS[0][j] = gf8_div(GF8_Q_COEFFS[0][j], GF8_Q_COEFFS[0][44]); + } + + /* + * Compute the products of 0..255 with all of the Q coefficients in + * advance. When building the scalar product between the data vectors + * and the P/Q vectors the individual products can be looked up in + * this table + * + * The P parity coefficients are just a subset of the Q coefficients so + * that we do not need to create a separate table for them. + */ + + for (j = 0; j < 43; j++) { + + table[j][0] = 0; + + for (i = 1; i < 256; i++) { + c = GF8_LOG[i] + GF8_LOG[GF8_Q_COEFFS[0][j]]; + if (c >= 255) c -= 255; + table[j][i] = GF8_ILOG[c]; + + c = GF8_LOG[i] + GF8_LOG[GF8_Q_COEFFS[1][j]]; + if (c >= 255) c -= 255; + table[j][i] |= GF8_ILOG[c]<<8; + } + } +} + +/* Reverses the bits in 'd'. 'bits' defines the bit width of 'd'. + */ +static uint32_t mirror_bits(uint32_t d, int bits) +{ + int i; + uint32_t r = 0; + + for (i = 0; i < bits; i++) { + r <<= 1; + + if ((d & 0x1) != 0) + r |= 0x1; + + d >>= 1; + } + + return r; +} + +/* Build the CRC lookup table for EDC_POLY poly. The CRC is 32 bit wide + * and reversed (i.e. the bit stream is divided by the EDC_POLY with the + * LSB first order). + */ +CrcTable::CrcTable () +{ + uint32_t i, j; + uint32_t r; + + for (i = 0; i < 256; i++) { + r = mirror_bits(i, 8); + + r <<= 24; + + for (j = 0; j < 8; j++) { + if ((r & 0x80000000) != 0) { + r <<= 1; + r ^= EDC_POLY; + } + else { + r <<= 1; + } + } + + r = mirror_bits(r, 32); + + table[i] = r; + } +} + +/* Calculates the CRC of given data with given lengths based on the + * table lookup algorithm. + */ +static uint32_t calc_edc(uint8_t *data, int len) +{ + uint32_t crc = 0; + + while (len--) { + crc = CRCTABLE[(int)(crc ^ *data++) & 0xff] ^ (crc >> 8); + } + + return crc; +} + +/* Build the scramble table as defined in the yellow book. The bytes + 12 to 2351 of a sector will be XORed with the data of this table. + */ +ScrambleTable::ScrambleTable() +{ + uint16_t i, j; + uint16_t reg = 1; + uint8_t d; + + for (i = 0; i < 2340; i++) { + d = 0; + + for (j = 0; j < 8; j++) { + d >>= 1; + + if ((reg & 0x1) != 0) + d |= 0x80; + + if ((reg & 0x1) != ((reg >> 1) & 0x1)) { + reg >>= 1; + reg |= 0x4000; /* 15-bit register */ + } + else { + reg >>= 1; + } + } + + table[i] = d; + } +} + +/* Calc EDC for a MODE 1 sector + */ +static void calc_mode1_edc(uint8_t *sector) +{ + uint32_t crc = calc_edc(sector, LEC_MODE1_DATA_LEN + 16); + + sector[LEC_MODE1_EDC_OFFSET] = crc & 0xffL; + sector[LEC_MODE1_EDC_OFFSET + 1] = (crc >> 8) & 0xffL; + sector[LEC_MODE1_EDC_OFFSET + 2] = (crc >> 16) & 0xffL; + sector[LEC_MODE1_EDC_OFFSET + 3] = (crc >> 24) & 0xffL; +} + +/* Calc EDC for a XA form 1 sector + */ +static void calc_mode2_form1_edc(uint8_t *sector) +{ + uint32_t crc = calc_edc(sector + LEC_DATA_OFFSET, + LEC_MODE2_FORM1_DATA_LEN); + + sector[LEC_MODE2_FORM1_EDC_OFFSET] = crc & 0xffL; + sector[LEC_MODE2_FORM1_EDC_OFFSET + 1] = (crc >> 8) & 0xffL; + sector[LEC_MODE2_FORM1_EDC_OFFSET + 2] = (crc >> 16) & 0xffL; + sector[LEC_MODE2_FORM1_EDC_OFFSET + 3] = (crc >> 24) & 0xffL; +} + +/* Calc EDC for a XA form 2 sector + */ +static void calc_mode2_form2_edc(uint8_t *sector) +{ + uint32_t crc = calc_edc(sector + LEC_DATA_OFFSET, + LEC_MODE2_FORM2_DATA_LEN); + + sector[LEC_MODE2_FORM2_EDC_OFFSET] = crc & 0xffL; + sector[LEC_MODE2_FORM2_EDC_OFFSET + 1] = (crc >> 8) & 0xffL; + sector[LEC_MODE2_FORM2_EDC_OFFSET + 2] = (crc >> 16) & 0xffL; + sector[LEC_MODE2_FORM2_EDC_OFFSET + 3] = (crc >> 24) & 0xffL; +} + +/* Writes the sync pattern to the given sector. + */ +static void set_sync_pattern(uint8_t *sector) +{ + sector[0] = 0; + + sector[1] = sector[2] = sector[3] = sector[4] = sector[5] = + sector[6] = sector[7] = sector[8] = sector[9] = sector[10] = 0xff; + + sector[11] = 0; +} + + +static uint8_t bin2bcd(uint8_t b) +{ + return (((b/10) << 4) & 0xf0) | ((b%10) & 0x0f); +} + +/* Builds the sector header. + */ +static void set_sector_header(uint8_t mode, uint32_t adr, uint8_t *sector) +{ + sector[LEC_HEADER_OFFSET] = bin2bcd(adr / (60*75)); + sector[LEC_HEADER_OFFSET + 1] = bin2bcd((adr / 75) % 60); + sector[LEC_HEADER_OFFSET + 2] = bin2bcd(adr % 75); + sector[LEC_HEADER_OFFSET + 3] = mode; +} + +/* Calculate the P parities for the sector. + * The 43 P vectors of length 24 are combined with the GF8_P_COEFFS. + */ +static void calc_P_parity(uint8_t *sector) +{ + int i, j; + uint16_t p01_msb, p01_lsb; + uint8_t *p_lsb_start; + uint8_t *p_lsb; + uint8_t *p0, *p1; + uint8_t d0,d1; + + p_lsb_start = sector + LEC_HEADER_OFFSET; + + p1 = sector + LEC_MODE1_P_PARITY_OFFSET; + p0 = sector + LEC_MODE1_P_PARITY_OFFSET + 2 * 43; + + for (i = 0; i <= 42; i++) { + p_lsb = p_lsb_start; + + p01_lsb = p01_msb = 0; + + for (j = 19; j <= 42; j++) { + d0 = *p_lsb; + d1 = *(p_lsb+1); + + p01_lsb ^= CF8_Q_COEFFS_RESULTS_01[j][d0]; + p01_msb ^= CF8_Q_COEFFS_RESULTS_01[j][d1]; + + p_lsb += 2 * 43; + } + + *p0 = p01_lsb; + *(p0 + 1) = p01_msb; + + *p1 = p01_lsb>>8; + *(p1 + 1) = p01_msb>>8; + + p0 += 2; + p1 += 2; + + p_lsb_start += 2; + } +} + +/* Calculate the Q parities for the sector. + * The 26 Q vectors of length 43 are combined with the GF8_Q_COEFFS. + */ +static void calc_Q_parity(uint8_t *sector) +{ + int i, j; + uint16_t q01_lsb, q01_msb; + uint8_t *q_lsb_start; + uint8_t *q_lsb; + uint8_t *q0, *q1, *q_start; + uint8_t d0,d1; + + q_lsb_start = sector + LEC_HEADER_OFFSET; + + q_start = sector + LEC_MODE1_Q_PARITY_OFFSET; + q1 = sector + LEC_MODE1_Q_PARITY_OFFSET; + q0 = sector + LEC_MODE1_Q_PARITY_OFFSET + 2 * 26; + + for (i = 0; i <= 25; i++) { + q_lsb = q_lsb_start; + + q01_lsb = q01_msb = 0; + + for (j = 0; j <= 42; j++) { + d0 = *q_lsb; + d1 = *(q_lsb+1); + + q01_lsb ^= CF8_Q_COEFFS_RESULTS_01[j][d0]; + q01_msb ^= CF8_Q_COEFFS_RESULTS_01[j][d1]; + + q_lsb += 2 * 44; + + if (q_lsb >= q_start) { + q_lsb -= 2 * 1118; + } + } + + *q0 = q01_lsb; + *(q0 + 1) = q01_msb; + + *q1 = q01_lsb>>8; + *(q1 + 1) = q01_msb>>8; + + q0 += 2; + q1 += 2; + + q_lsb_start += 2 * 43; + } +} + +/* Encodes a MODE 0 sector. + * 'adr' is the current physical sector address + * 'sector' must be 2352 byte wide + */ +void lec_encode_mode0_sector(uint32_t adr, uint8_t *sector) +{ + uint16_t i; + + set_sync_pattern(sector); + set_sector_header(0, adr, sector); + + sector += 16; + + for (i = 0; i < 2336; i++) + *sector++ = 0; +} + +/* Encodes a MODE 1 sector. + * 'adr' is the current physical sector address + * 'sector' must be 2352 byte wide containing 2048 bytes user data at + * offset 16 + */ +void lec_encode_mode1_sector(uint32_t adr, uint8_t *sector) +{ + set_sync_pattern(sector); + set_sector_header(1, adr, sector); + + calc_mode1_edc(sector); + + /* clear the intermediate field */ + sector[LEC_MODE1_INTERMEDIATE_OFFSET] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 1] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 2] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 3] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 4] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 5] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 6] = + sector[LEC_MODE1_INTERMEDIATE_OFFSET + 7] = 0; + + calc_P_parity(sector); + calc_Q_parity(sector); +} + +/* Encodes a MODE 2 sector. + * 'adr' is the current physical sector address + * 'sector' must be 2352 byte wide containing 2336 bytes user data at + * offset 16 + */ +void lec_encode_mode2_sector(uint32_t adr, uint8_t *sector) +{ + set_sync_pattern(sector); + set_sector_header(2, adr, sector); +} + +/* Encodes a XA form 1 sector. + * 'adr' is the current physical sector address + * 'sector' must be 2352 byte wide containing 2048+8 bytes user data at + * offset 16 + */ +void lec_encode_mode2_form1_sector(uint32_t adr, uint8_t *sector) +{ + set_sync_pattern(sector); + + calc_mode2_form1_edc(sector); + + /* P/Q partiy must not contain the sector header so clear it */ + sector[LEC_HEADER_OFFSET] = + sector[LEC_HEADER_OFFSET + 1] = + sector[LEC_HEADER_OFFSET + 2] = + sector[LEC_HEADER_OFFSET + 3] = 0; + + calc_P_parity(sector); + calc_Q_parity(sector); + + /* finally add the sector header */ + set_sector_header(2, adr, sector); +} + +/* Encodes a XA form 2 sector. + * 'adr' is the current physical sector address + * 'sector' must be 2352 byte wide containing 2324+8 bytes user data at + * offset 16 + */ +void lec_encode_mode2_form2_sector(uint32_t adr, uint8_t *sector) +{ + set_sync_pattern(sector); + + calc_mode2_form2_edc(sector); + + set_sector_header(2, adr, sector); +} + +/* Scrambles and byte swaps an encoded sector. + * 'sector' must be 2352 byte wide. + */ +void lec_scramble(uint8_t *sector) +{ + uint16_t i; + const uint8_t *stable = SCRAMBLE_TABLE; + uint8_t *p = sector; + uint8_t tmp; + + + for (i = 0; i < 6; i++) { + /* just swap bytes of sector sync */ + tmp = *p; + *p = *(p + 1); + p++; + *p++ = tmp; + } + for (;i < (2352 / 2); i++) { + /* scramble and swap bytes */ + tmp = *p ^ *stable++; + *p = *(p + 1) ^ *stable++; + p++; + *p++ = tmp; + } +} + +#if 0 +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *infile; + char *outfile; + int fd_in, fd_out; + uint8_t buffer1[2352]; + uint8_t buffer2[2352]; + uint32_t lba; + int i; + +#if 0 + for (i = 0; i < 2048; i++) + buffer1[i + 16] = 234; + + lba = 150; + + for (i = 0; i < 100000; i++) { + lec_encode_mode1_sector(lba, buffer1); + lec_scramble(buffer2); + lba++; + } + +#else + + if (argc != 3) + return 1; + + infile = argv[1]; + outfile = argv[2]; + + + if ((fd_in = open(infile, O_RDONLY)) < 0) { + perror("Cannot open input file"); + return 1; + } + + if ((fd_out = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { + perror("Cannot open output file"); + return 1; + } + + lba = 150; + + do { + if (read(fd_in, buffer1, 2352) != 2352) + break; + + switch (*(buffer1 + 12 + 3)) { + case 1: + memcpy(buffer2 + 16, buffer1 + 16, 2048); + + lec_encode_mode1_sector(lba, buffer2); + break; + + case 2: + if ((*(buffer1 + 12 + 4 + 2) & 0x20) != 0) { + /* form 2 sector */ + memcpy(buffer2 + 16, buffer1 + 16, 2324 + 8); + lec_encode_mode2_form2_sector(lba, buffer2); + } + else { + /* form 1 sector */ + memcpy(buffer2 + 16, buffer1 + 16, 2048 + 8); + lec_encode_mode2_form1_sector(lba, buffer2); + } + break; + } + + if (memcmp(buffer1, buffer2, 2352) != 0) { + printf("Verify error at lba %ld\n", lba); + } + + lec_scramble(buffer2); + write(fd_out, buffer2, 2352); + + lba++; + } while (1); + + close(fd_in); + close(fd_out); + +#endif + + return 0; +} +#endif diff --git a/mednafen/cdrom/lec.h b/mednafen/cdrom/lec.h index 05ab4c0..8a6ebb1 100644 --- a/mednafen/cdrom/lec.h +++ b/mednafen/cdrom/lec.h @@ -21,15 +21,6 @@ #define __LEC_H__ #include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef TRUE -#define TRUE 1 -#endif /* Encodes a MODE 0 sector. * 'adr' is the current physical sector address @@ -68,12 +59,6 @@ void lec_encode_mode2_form2_sector(uint32_t adr, uint8_t *sector); /* Scrambles and byte swaps an encoded sector. * 'sector' must be 2352 byte wide. */ -void lec_scramble(uint8_t *sector); - -void lec_tables_init(void); - -#ifdef __cplusplus -} -#endif +void lec_scramble(int8_t *sector); #endif diff --git a/mednafen/cdrom/misc.cpp b/mednafen/cdrom/misc.cpp deleted file mode 100644 index 11bb195..0000000 --- a/mednafen/cdrom/misc.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -#include - -#include "misc.h" - -void MDFN_strtoupper(char *str) -{ - size_t x; - for(x = 0; str[x]; x++) - { - if(str[x] >= 'a' && str[x] <= 'z') - str[x] = str[x] - 'a' + 'A'; - } -} - -void MDFN_strtoupper(std::string &str) -{ - size_t x; - const size_t len = str.length(); - - for(x = 0; x < len; x++) - { - if(str[x] >= 'a' && str[x] <= 'z') - str[x] = str[x] - 'a' + 'A'; - } -} diff --git a/mednafen/cdrom/misc.h b/mednafen/cdrom/misc.h deleted file mode 100644 index 434fc99..0000000 --- a/mednafen/cdrom/misc.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __MDFN_CDROM_MISC_H -#define __MDFN_CDROM_MISC_H - -void MDFN_strtoupper(std::string &str); -void MDFN_strtoupper(char *str); - -#endif diff --git a/mednafen/cdrom/recover-raw.c b/mednafen/cdrom/recover-raw.cpp similarity index 53% rename from mednafen/cdrom/recover-raw.c rename to mednafen/cdrom/recover-raw.cpp index 77412f9..7cfee1e 100644 --- a/mednafen/cdrom/recover-raw.c +++ b/mednafen/cdrom/recover-raw.cpp @@ -19,28 +19,24 @@ * or direct your browser at http://www.gnu.org. */ -#include -#include -#include "recover-raw.h" -#include "l-ec.h" -#include "edc_crc32.h" -#include "galois.h" +#include +#include "dvdisaster.h" static GaloisTables *gt = NULL; /* for L-EC Reed-Solomon */ static ReedSolomonTables *rt = NULL; bool Init_LEC_Correct(void) { - gt = CreateGaloisTables(0x11d); - rt = CreateReedSolomonTables(gt, 0, 1, 10); + gt = CreateGaloisTables(0x11d); + rt = CreateReedSolomonTables(gt, 0, 1, 10); - return(1); + return(1); } void Kill_LEC_Correct(void) { - FreeGaloisTables(gt); - FreeReedSolomonTables(rt); + FreeGaloisTables(gt); + FreeReedSolomonTables(rt); } /*** @@ -49,29 +45,31 @@ void Kill_LEC_Correct(void) /* * Test raw sector against its 32bit CRC. - * Returns TRUE if frame is good. + * Returns true if frame is good. */ int CheckEDC(const unsigned char *cd_frame, bool xa_mode) { - unsigned int expected_crc, real_crc; - unsigned int crc_base = xa_mode ? 2072 : 2064; + unsigned int expected_crc, real_crc; + unsigned int crc_base = xa_mode ? 2072 : 2064; - expected_crc = cd_frame[crc_base + 0] << 0; - expected_crc |= cd_frame[crc_base + 1] << 8; - expected_crc |= cd_frame[crc_base + 2] << 16; - expected_crc |= cd_frame[crc_base + 3] << 24; + expected_crc = cd_frame[crc_base + 0] << 0; + expected_crc |= cd_frame[crc_base + 1] << 8; + expected_crc |= cd_frame[crc_base + 2] << 16; + expected_crc |= cd_frame[crc_base + 3] << 24; - if(xa_mode) - real_crc = EDCCrc32(cd_frame+16, 2056); - else - real_crc = EDCCrc32(cd_frame, 2064); + if(xa_mode) + real_crc = EDCCrc32(cd_frame+16, 2056); + else + real_crc = EDCCrc32(cd_frame, 2064); - if(expected_crc == real_crc) - return(1); - - //printf("Bad EDC CRC: Calculated: %08x, Recorded: %08x\n", real_crc, expected_crc); - return(0); + if(expected_crc == real_crc) + return(1); + else + { + //printf("Bad EDC CRC: Calculated: %08x, Recorded: %08x\n", real_crc, expected_crc); + return(0); + } } /*** @@ -104,34 +102,31 @@ static int simple_lec(unsigned char *frame) /* Perform Q-Parity error correction */ for(q=0; q 2) - { - GetPVector(byte_state, p_state, p); - erasure_count = 0; + { GetPVector(byte_state, p_state, p); + erasure_count = 0; - for(i=0; i 0 && erasure_count <= 2) - { - GetPVector(frame, p_vector, p); - err = DecodePQ(rt, p_vector, P_PADDING, erasures, erasure_count); - } + if(erasure_count > 0 && erasure_count <= 2) + { GetPVector(frame, p_vector, p); + err = DecodePQ(rt, p_vector, P_PADDING, erasures, erasure_count); + } } /* See what we've got */ if(err < 0) /* Uncorrectable. */ - p_failures++; + { p_failures++; + } else /* Correctable. */ - { - if(err == 1 || err == 2) /* Store back corrected vector */ - { SetPVector(frame, p_vector, p); - p_corrected++; - } + { if(err == 1 || err == 2) /* Store back corrected vector */ + { SetPVector(frame, p_vector, p); + p_corrected++; + } } } /* Sum up */ if(q_failures || p_failures || q_corrected || p_corrected) - return 1; + { + return 1; + } return 0; } @@ -183,25 +178,36 @@ static int simple_lec(unsigned char *frame) ***/ int ValidateRawSector(unsigned char *frame, bool xaMode) -{ - int lec_did_sth = false; +{ + int lec_did_sth = false; - // Silence GCC warning - (void)lec_did_sth; + /* Do simple L-EC. + It seems that drives stop their internal L-EC as soon as the + EDC is okay, so we may see uncorrected errors in the parity bytes. + Since we are also interested in the user data only and doing the + L-EC is expensive, we skip our L-EC as well when the EDC is fine. */ - /* Do simple L-EC. - It seems that drives stop their internal L-EC as soon as the - EDC is okay, so we may see uncorrected errors in the parity bytes. - Since we are also interested in the user data only and doing the - L-EC is expensive, we skip our L-EC as well when the EDC is fine. */ + if(!CheckEDC(frame, xaMode)) + { + unsigned char header[4]; - if(!CheckEDC(frame, xaMode)) - lec_did_sth = simple_lec(frame); - /* Test internal sector checksum again */ + if(xaMode) + { + memcpy(header, frame + 12, 4); + memset(frame + 12, 0, 4); + } + lec_did_sth = simple_lec(frame); + + if(xaMode) + memcpy(frame + 12, header, 4); + } + + /* Test internal sector checksum again */ + if(!CheckEDC(frame, xaMode)) /* EDC failure in RAW sector */ - if(!CheckEDC(frame, xaMode)) - return false; + return false; - return true; + return true; } + diff --git a/mednafen/cdrom/recover-raw.h b/mednafen/cdrom/recover-raw.h deleted file mode 100644 index 08eb3ff..0000000 --- a/mednafen/cdrom/recover-raw.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef _RECOVER_RAW_H -#define _RECOVER_RAW_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define CD_RAW_SECTOR_SIZE 2352 -#define CD_RAW_C2_SECTOR_SIZE (2352+294) /* main channel plus C2 vector */ - -int CheckEDC(const unsigned char *a, bool b); -int CheckMSF(unsigned char *a, int b); - - -int ValidateRawSector(unsigned char *frame, bool xaMode); -bool Init_LEC_Correct(void); -void Kill_LEC_Correct(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/mednafen/cdrom/scsicd-pce-commands.inc b/mednafen/cdrom/scsicd-pce-commands.inc new file mode 100644 index 0000000..ea6cf91 --- /dev/null +++ b/mednafen/cdrom/scsicd-pce-commands.inc @@ -0,0 +1,259 @@ +/******************************************************** +* * +* PC Engine CD Command 0xD8 - SAPSP * +* * +********************************************************/ +static void DoNEC_PCE_SAPSP(const uint8_t *cdb) +{ + uint32 new_read_sec_start; + + //printf("Set audio start: %02x %02x %02x %02x %02x %02x %02x\n", cdb[9], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6]); + switch (cdb[9] & 0xc0) + { + default: //SCSIDBG("Unknown SAPSP 9: %02x\n", cdb[9]); + case 0x00: + new_read_sec_start = (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; + break; + + case 0x40: + new_read_sec_start = AMSF_to_LBA(BCD_to_U8(cdb[2]), BCD_to_U8(cdb[3]), BCD_to_U8(cdb[4])); + break; + + case 0x80: + { + int track = BCD_to_U8(cdb[2]); + + if(!track) + track = 1; + else if(track >= toc.last_track + 1) + track = 100; + new_read_sec_start = toc.tracks[track].lba; + } + break; + } + + //printf("%lld\n", (long long)(monotonic_timestamp - pce_lastsapsp_timestamp) * 1000 / System_Clock); + if(cdda.CDDAStatus == CDDASTATUS_PLAYING && new_read_sec_start == read_sec_start && ((int64)(monotonic_timestamp - pce_lastsapsp_timestamp) * 1000 / System_Clock) < 190) + { + pce_lastsapsp_timestamp = monotonic_timestamp; + + SendStatusAndMessage(STATUS_GOOD, 0x00); + CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE); + return; + } + + pce_lastsapsp_timestamp = monotonic_timestamp; + + read_sec = read_sec_start = new_read_sec_start; + read_sec_end = toc.tracks[100].lba; + + + cdda.CDDAReadPos = 588; + + cdda.CDDAStatus = CDDASTATUS_PAUSED; + cdda.PlayMode = PLAYMODE_SILENT; + + if(cdb[1]) + { + cdda.PlayMode = PLAYMODE_NORMAL; + cdda.CDDAStatus = CDDASTATUS_PLAYING; + } + + if(read_sec < toc.tracks[100].lba) + Cur_CDIF->HintReadSector(read_sec); + + SendStatusAndMessage(STATUS_GOOD, 0x00); + CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE); +} + + + +/******************************************************** +* * +* PC Engine CD Command 0xD9 - SAPEP * +* * +********************************************************/ +static void DoNEC_PCE_SAPEP(const uint8_t *cdb) +{ + uint32 new_read_sec_end; + + //printf("Set audio end: %02x %02x %02x %02x %02x %02x %02x\n", cdb[9], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6]); + + switch (cdb[9] & 0xc0) + { + default: //SCSIDBG("Unknown SAPEP 9: %02x\n", cdb[9]); + + case 0x00: + new_read_sec_end = (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; + break; + + case 0x40: + new_read_sec_end = BCD_to_U8(cdb[4]) + 75 * (BCD_to_U8(cdb[3]) + 60 * BCD_to_U8(cdb[2])); + new_read_sec_end -= 150; + break; + + case 0x80: + { + int track = BCD_to_U8(cdb[2]); + + if(!track) + track = 1; + else if(track >= toc.last_track + 1) + track = 100; + new_read_sec_end = toc.tracks[track].lba; + } + break; + } + + read_sec_end = new_read_sec_end; + + switch(cdb[1]) // PCE CD(TODO: Confirm these, and check the mode mask): + { + default: + case 0x03: cdda.PlayMode = PLAYMODE_NORMAL; + cdda.CDDAStatus = CDDASTATUS_PLAYING; + break; + + case 0x02: cdda.PlayMode = PLAYMODE_INTERRUPT; + cdda.CDDAStatus = CDDASTATUS_PLAYING; + break; + + case 0x01: cdda.PlayMode = PLAYMODE_LOOP; + cdda.CDDAStatus = CDDASTATUS_PLAYING; + break; + + case 0x00: cdda.PlayMode = PLAYMODE_SILENT; + cdda.CDDAStatus = CDDASTATUS_STOPPED; + break; + } + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* PC Engine CD Command 0xDA - Pause * +* * +********************************************************/ +static void DoNEC_PCE_PAUSE(const uint8_t *cdb) +{ + if(cdda.CDDAStatus != CDDASTATUS_STOPPED) // Hmm, should we give an error if it tries to pause and it's already paused? + { + cdda.CDDAStatus = CDDASTATUS_PAUSED; + SendStatusAndMessage(STATUS_GOOD, 0x00); + } + else // Definitely give an error if it tries to pause when no track is playing! + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_AUDIO_NOT_PLAYING); + } +} + + + +/******************************************************** +* * +* PC Engine CD Command 0xDD - Read Subchannel Q * +* * +********************************************************/ +static void DoNEC_PCE_READSUBQ(const uint8_t *cdb) +{ + uint8_t *SubQBuf = cd.SubQBuf[QMode_Time]; + uint8_t data_in[8192]; + + memset(data_in, 0x00, 10); + + data_in[2] = SubQBuf[1]; // Track + data_in[3] = SubQBuf[2]; // Index + data_in[4] = SubQBuf[3]; // M(rel) + data_in[5] = SubQBuf[4]; // S(rel) + data_in[6] = SubQBuf[5]; // F(rel) + data_in[7] = SubQBuf[7]; // M(abs) + data_in[8] = SubQBuf[8]; // S(abs) + data_in[9] = SubQBuf[9]; // F(abs) + + if(cdda.CDDAStatus == CDDASTATUS_PAUSED) + data_in[0] = 2; // Pause + else if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) // FIXME: Is this the correct status code for scanning playback? + data_in[0] = 0; // Playing + else + data_in[0] = 3; // Stopped + + DoSimpleDataIn(data_in, 10); +} + + + +/******************************************************** +* * +* PC Engine CD Command 0xDE - Get Directory Info * +* * +********************************************************/ +static void DoNEC_PCE_GETDIRINFO(const uint8_t *cdb) +{ + // Problems: + // Returned data lengths on real PCE are not confirmed. + // Mode 0x03 behavior not tested on real PCE + + uint8_t data_in[2048]; + uint32 data_in_size = 0; + + memset(data_in, 0, sizeof(data_in)); + + switch(cdb[1]) + { + default: //MDFN_DispMessage("Unknown GETDIRINFO Mode: %02x", cdb[1]); + //printf("Unknown GETDIRINFO Mode: %02x", cdb[1]); + case 0x0: + data_in[0] = U8_to_BCD(toc.first_track); + data_in[1] = U8_to_BCD(toc.last_track); + + data_in_size = 2; + break; + + case 0x1: + { + uint8_t m, s, f; + + LBA_to_AMSF(toc.tracks[100].lba, &m, &s, &f); + + data_in[0] = U8_to_BCD(m); + data_in[1] = U8_to_BCD(s); + data_in[2] = U8_to_BCD(f); + + data_in_size = 3; + } + break; + + case 0x2: + { + uint8_t m, s, f; + int track = BCD_to_U8(cdb[2]); + + if(!track) + track = 1; + else if(cdb[2] == 0xAA) + { + track = 100; + } + else if(track > 99) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + LBA_to_AMSF(toc.tracks[track].lba, &m, &s, &f); + + data_in[0] = U8_to_BCD(m); + data_in[1] = U8_to_BCD(s); + data_in[2] = U8_to_BCD(f); + data_in[3] = toc.tracks[track].control; + data_in_size = 4; + } + break; + } + + DoSimpleDataIn(data_in, data_in_size); +} + diff --git a/mednafen/cdrom/scsicd.cpp b/mednafen/cdrom/scsicd.cpp new file mode 100644 index 0000000..ace203b --- /dev/null +++ b/mednafen/cdrom/scsicd.cpp @@ -0,0 +1,3243 @@ +/* Mednafen - Multi-system Emulator + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "scsicd.h" +#include "cdromif.h" +#include "SimpleFIFO.h" + +#if defined(__SSE2__) +#include +#include +#endif + +using namespace CDUtility; + +static uint32_t CD_DATA_TRANSFER_RATE; +static uint32_t System_Clock; +static void (*CDIRQCallback)(int); +static void (*CDStuffSubchannels)(uint8_t, int); +static int32_t* HRBufs[2]; +static int WhichSystem; + +static CDIF *Cur_CDIF; +static bool TrayOpen; + +// Internal operation to the SCSI CD unit. Only pass 1 or 0 to these macros! +#define SetIOP(mask, set) { cd_bus.signals &= ~mask; if(set) cd_bus.signals |= mask; } + +#define SetBSY(set) SetIOP(SCSICD_BSY_mask, set) +#define SetIO(set) SetIOP(SCSICD_IO_mask, set) +#define SetCD(set) SetIOP(SCSICD_CD_mask, set) +#define SetMSG(set) SetIOP(SCSICD_MSG_mask, set) + +static INLINE void SetREQ(bool set) +{ + if(set && !REQ_signal) + CDIRQCallback(SCSICD_IRQ_MAGICAL_REQ); + + SetIOP(SCSICD_REQ_mask, set); +} + +#define SetkingACK(set) SetIOP(SCSICD_kingACK_mask, set) +#define SetkingRST(set) SetIOP(SCSICD_kingRST_mask, set) +#define SetkingSEL(set) SetIOP(SCSICD_kingSEL_mask, set) +#define SetkingATN(set) SetIOP(SCSICD_kingATN_mask, set) + + +enum +{ + QMode_Zero = 0, + QMode_Time = 1, + QMode_MCN = 2, // Media Catalog Number + QMode_ISRC = 3 // International Standard Recording Code +}; + +typedef struct +{ + bool last_RST_signal; + + // The pending message to send(in the message phase) + uint8_t message_pending; + + bool status_sent, message_sent; + + // Pending error codes + uint8_t key_pending, asc_pending, ascq_pending, fru_pending; + + uint8_t command_buffer[256]; + uint8_t command_buffer_pos; + uint8_t command_size_left; + + // FALSE if not all pending data is in the FIFO, TRUE if it is. + // Used for multiple sector CD reads. + bool data_transfer_done; + + // To target(the cd unit); for "MODE SELECT". + uint8_t data_out[256]; // Technically it only needs to be 255, but powers of 2 are better than those degenerate powers of 2 minus one goons. + uint8_t data_out_pos; // Current index for writing into data_out. + uint8_t data_out_want; // Total number of bytes to buffer into data_out. + + bool DiscChanged; + + uint8_t SubQBuf[4][0xC]; // One for each of the 4 most recent q-Modes. + uint8_t SubQBuf_Last[0xC]; // The most recent q subchannel data, regardless of q-mode. + + uint8_t SubPWBuf[96]; + +} scsicd_t; + +enum +{ + CDDASTATUS_PAUSED = -1, + CDDASTATUS_STOPPED = 0, + CDDASTATUS_PLAYING = 1, + CDDASTATUS_SCANNING = 2 +}; + +enum +{ + PLAYMODE_SILENT = 0x00, + PLAYMODE_NORMAL, + PLAYMODE_INTERRUPT, + PLAYMODE_LOOP +}; + +typedef struct +{ + uint32_t CDDADivAcc; + uint8_t CDDADivAccVolFudge; // For PC-FX CD-DA rate control RE impulses and resampling; 100 = 1.0. + uint32_t scan_sec_end; + + uint8_t PlayMode; + int32_t CDDAVolume[2]; // 65536 = 1.0, the maximum. + int16 CDDASectorBuffer[1176]; + uint32_t CDDAReadPos; + + int8_t CDDAStatus; + uint8_t ScanMode; + int64_t CDDADiv; + int CDDATimeDiv; + + int16 OversampleBuffer[2][0x10 * 2]; // *2 so our MAC loop can blast through without masking the index. + unsigned OversamplePos; + + int16 sr[2]; + + uint8_t OutPortChSelect[2]; + uint32_t OutPortChSelectCache[2]; + int32_t OutPortVolumeCache[2]; + + float DeemphState[2][2]; +} cdda_t; + +void MakeSense(uint8_t * target, uint8_t key, uint8_t asc, uint8_t ascq, uint8_t fru) +{ + memset(target, 0, 18); + + target[0] = 0x70; // Current errors and sense data is not SCSI compliant + target[2] = key; + target[7] = 0x0A; + target[12] = asc; // Additional Sense Code + target[13] = ascq; // Additional Sense Code Qualifier + target[14] = fru; // Field Replaceable Unit code +} + +static void (*SCSILog)(const char *, const char *format, ...); +static void InitModePages(void); + +static scsicd_timestamp_t lastts; +static int64_t monotonic_timestamp; +static int64_t pce_lastsapsp_timestamp; + +scsicd_t cd; +scsicd_bus_t cd_bus; +static cdda_t cdda; + +static SimpleFIFO *din = NULL; + +static CDUtility::TOC toc; + +static uint32_t read_sec_start; +static uint32_t read_sec; +static uint32_t read_sec_end; + +static int32_t CDReadTimer; +static uint32_t SectorAddr; +static uint32_t SectorCount; + + +enum +{ + PHASE_BUS_FREE = 0, + PHASE_COMMAND, + PHASE_DATA_IN, + PHASE_DATA_OUT, + PHASE_STATUS, + PHASE_MESSAGE_IN, + PHASE_MESSAGE_OUT +}; +static unsigned int CurrentPhase; +static void ChangePhase(const unsigned int new_phase); + + +static void FixOPV(void) +{ + for(int port = 0; port < 2; port++) + { + int32_t tmpvol = cdda.CDDAVolume[port] * 100 / (2 * cdda.CDDADivAccVolFudge); + + //printf("TV: %d\n", tmpvol); + + cdda.OutPortVolumeCache[port] = tmpvol; + + if(cdda.OutPortChSelect[port] & 0x01) + cdda.OutPortChSelectCache[port] = 0; + else if(cdda.OutPortChSelect[port] & 0x02) + cdda.OutPortChSelectCache[port] = 1; + else + { + cdda.OutPortChSelectCache[port] = 0; + cdda.OutPortVolumeCache[port] = 0; + } + } +} + +static void VirtualReset(void) +{ + InitModePages(); + + din->Flush(); + + CDReadTimer = 0; + + pce_lastsapsp_timestamp = monotonic_timestamp; + + SectorAddr = SectorCount = 0; + read_sec_start = read_sec = 0; + read_sec_end = ~0; + + cdda.PlayMode = PLAYMODE_SILENT; + cdda.CDDAReadPos = 0; + cdda.CDDAStatus = CDDASTATUS_STOPPED; + cdda.CDDADiv = 0; + + cdda.ScanMode = 0; + cdda.scan_sec_end = 0; + + cdda.OversamplePos = 0; + memset(cdda.sr, 0, sizeof(cdda.sr)); + memset(cdda.OversampleBuffer, 0, sizeof(cdda.OversampleBuffer)); + memset(cdda.DeemphState, 0, sizeof(cdda.DeemphState)); + + memset(cd.data_out, 0, sizeof(cd.data_out)); + cd.data_out_pos = 0; + cd.data_out_want = 0; + + + FixOPV(); + + ChangePhase(PHASE_BUS_FREE); +} + +void SCSICD_Power(scsicd_timestamp_t system_timestamp) +{ + memset(&cd, 0, sizeof(scsicd_t)); + memset(&cd_bus, 0, sizeof(scsicd_bus_t)); + + monotonic_timestamp = system_timestamp; + + cd.DiscChanged = false; + + if(Cur_CDIF && !TrayOpen) + Cur_CDIF->ReadTOC(&toc); + + CurrentPhase = PHASE_BUS_FREE; + + VirtualReset(); +} + + +void SCSICD_SetDB(uint8_t data) +{ + cd_bus.DB = data; + //printf("Set DB: %02x\n", data); +} + +void SCSICD_SetACK(bool set) +{ + SetkingACK(set); + //printf("Set ACK: %d\n", set); +} + +void SCSICD_SetSEL(bool set) +{ + SetkingSEL(set); + //printf("Set SEL: %d\n", set); +} + +void SCSICD_SetRST(bool set) +{ + SetkingRST(set); + //printf("Set RST: %d\n", set); +} + +void SCSICD_SetATN(bool set) +{ + SetkingATN(set); + //printf("Set ATN: %d\n", set); +} + +static void GenSubQFromSubPW(void) +{ + uint8_t SubQBuf[0xC]; + + memset(SubQBuf, 0, 0xC); + + for(int i = 0; i < 96; i++) + SubQBuf[i >> 3] |= ((cd.SubPWBuf[i] & 0x40) >> 6) << (7 - (i & 7)); + + //printf("Real %d/ SubQ %d - ", read_sec, BCD_to_U8(SubQBuf[7]) * 75 * 60 + BCD_to_U8(SubQBuf[8]) * 75 + BCD_to_U8(SubQBuf[9]) - 150); + // Debug code, remove me. + //for(int i = 0; i < 0xC; i++) + // printf("%02x ", SubQBuf[i]); + //printf("\n"); + + if(!subq_check_checksum(SubQBuf)) + { + //SCSIDBG("SubQ checksum error!"); + } + else + { + memcpy(cd.SubQBuf_Last, SubQBuf, 0xC); + + uint8_t adr = SubQBuf[0] & 0xF; + + if(adr <= 0x3) + memcpy(cd.SubQBuf[adr], SubQBuf, 0xC); + + //if(adr == 0x02) + //for(int i = 0; i < 12; i++) + // printf("%02x\n", cd.SubQBuf[0x2][i]); + } +} + + +#define STATUS_GOOD 0 +#define STATUS_CHECK_CONDITION 1 +#define STATUS_CONDITION_MET 2 +#define STATUS_BUSY 4 +#define STATUS_INTERMEDIATE 8 + +#define SENSEKEY_NO_SENSE 0x0 +#define SENSEKEY_NOT_READY 0x2 +#define SENSEKEY_MEDIUM_ERROR 0x3 +#define SENSEKEY_HARDWARE_ERROR 0x4 +#define SENSEKEY_ILLEGAL_REQUEST 0x5 +#define SENSEKEY_UNIT_ATTENTION 0x6 +#define SENSEKEY_ABORTED_COMMAND 0xB + +#define ASC_MEDIUM_NOT_PRESENT 0x3A + + +// NEC sub-errors(ASC), no ASCQ. +#define NSE_NO_DISC 0x0B // Used with SENSEKEY_NOT_READY - This condition occurs when tray is closed with no disc present. +#define NSE_TRAY_OPEN 0x0D // Used with SENSEKEY_NOT_READY +#define NSE_SEEK_ERROR 0x15 +#define NSE_HEADER_READ_ERROR 0x16 // Used with SENSEKEY_MEDIUM_ERROR +#define NSE_NOT_AUDIO_TRACK 0x1C // Used with SENSEKEY_MEDIUM_ERROR +#define NSE_NOT_DATA_TRACK 0x1D // Used with SENSEKEY_MEDIUM_ERROR +#define NSE_INVALID_COMMAND 0x20 +#define NSE_INVALID_ADDRESS 0x21 +#define NSE_INVALID_PARAMETER 0x22 +#define NSE_END_OF_VOLUME 0x25 +#define NSE_INVALID_REQUEST_IN_CDB 0x27 +#define NSE_DISC_CHANGED 0x28 // Used with SENSEKEY_UNIT_ATTENTION +#define NSE_AUDIO_NOT_PLAYING 0x2C + +// ASC, ASCQ pair +#define AP_UNRECOVERED_READ_ERROR 0x11, 0x00 +#define AP_LEC_UNCORRECTABLE_ERROR 0x11, 0x05 +#define AP_CIRC_UNRECOVERED_ERROR 0x11, 0x06 + +#define AP_UNKNOWN_MEDIUM_FORMAT 0x30, 0x01 +#define AP_INCOMPAT_MEDIUM_FORMAT 0x30, 0x02 + +static void ChangePhase(const unsigned int new_phase) +{ + //printf("New phase: %d %lld\n", new_phase, monotonic_timestamp); + switch(new_phase) + { + case PHASE_BUS_FREE: + SetBSY(false); + SetMSG(false); + SetCD(false); + SetIO(false); + SetREQ(false); + + CDIRQCallback(0x8000 | SCSICD_IRQ_DATA_TRANSFER_DONE); + break; + + case PHASE_DATA_IN: // Us to them + SetBSY(true); + SetMSG(false); + SetCD(false); + SetIO(true); + //SetREQ(true); + SetREQ(false); + break; + + case PHASE_STATUS: // Us to them + SetBSY(true); + SetMSG(false); + SetCD(true); + SetIO(true); + SetREQ(true); + break; + + case PHASE_MESSAGE_IN: // Us to them + SetBSY(true); + SetMSG(true); + SetCD(true); + SetIO(true); + SetREQ(true); + break; + + + case PHASE_DATA_OUT: // Them to us + SetBSY(true); + SetMSG(false); + SetCD(false); + SetIO(false); + SetREQ(true); + break; + + case PHASE_COMMAND: // Them to us + SetBSY(true); + SetMSG(false); + SetCD(true); + SetIO(false); + SetREQ(true); + break; + + case PHASE_MESSAGE_OUT: // Them to us + SetBSY(true); + SetMSG(true); + SetCD(true); + SetIO(false); + SetREQ(true); + break; + } + CurrentPhase = new_phase; +} + +static void SendStatusAndMessage(uint8_t status, uint8_t message) +{ + // This should never ever happen, but that doesn't mean it won't. ;) + if(din->CanRead()) + { + //printf("[SCSICD] BUG: %d bytes still in SCSI CD FIFO\n", din->CanRead()); + din->Flush(); + } + + cd.message_pending = message; + + cd.status_sent = FALSE; + cd.message_sent = FALSE; + + if(WhichSystem == SCSICD_PCE) + { + if(status == STATUS_GOOD || status == STATUS_CONDITION_MET) + cd_bus.DB = 0x00; + else + cd_bus.DB = 0x01; + } + else + cd_bus.DB = status << 1; + + ChangePhase(PHASE_STATUS); +} + +static void DoSimpleDataIn(const uint8_t *data_in, uint32_t len) +{ + din->Write(data_in, len); + + cd.data_transfer_done = true; + + ChangePhase(PHASE_DATA_IN); +} + +void SCSICD_SetDisc(bool new_tray_open, CDIF *cdif, bool no_emu_side_effects) +{ + Cur_CDIF = cdif; + + // Closing the tray. + if(TrayOpen && !new_tray_open) + { + TrayOpen = false; + + if(cdif) + { + cdif->ReadTOC(&toc); + + if(!no_emu_side_effects) + { + memset(cd.SubQBuf, 0, sizeof(cd.SubQBuf)); + memset(cd.SubQBuf_Last, 0, sizeof(cd.SubQBuf_Last)); + cd.DiscChanged = true; + } + } + } + else if(!TrayOpen && new_tray_open) // Opening the tray + { + TrayOpen = true; + } +} + +static void CommandCCError(int key, int asc = 0, int ascq = 0) +{ + //printf("[SCSICD] CC Error: %02x %02x %02x\n", key, asc, ascq); + + cd.key_pending = key; + cd.asc_pending = asc; + cd.ascq_pending = ascq; + cd.fru_pending = 0x00; + + SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00); +} + +static bool ValidateRawDataSector(uint8_t *data, const uint32_t lba) +{ + if(!Cur_CDIF->ValidateRawSector(data)) + { + MDFN_DispMessage(_("Uncorrectable data at sector %d"), lba); + MDFN_PrintError(_("Uncorrectable data at sector %d"), lba); + + din->Flush(); + cd.data_transfer_done = false; + + CommandCCError(SENSEKEY_MEDIUM_ERROR, AP_LEC_UNCORRECTABLE_ERROR); + return(false); + } + + return(true); +} + +static void DoMODESELECT6(const uint8_t *cdb) +{ + if(cdb[4]) + { + cd.data_out_pos = 0; + cd.data_out_want = cdb[4]; + //printf("Switch to DATA OUT phase, len: %d\n", cd.data_out_want); + + ChangePhase(PHASE_DATA_OUT); + } + else + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +/* + All Japan Female Pro Wrestle: + Datumama: 10, 00 00 00 00 00 00 00 00 00 0a + + Kokuu Hyouryuu Nirgends: + Datumama: 10, 00 00 00 00 00 00 00 00 00 0f + Datumama: 10, 00 00 00 00 00 00 00 00 00 0f + + Last Imperial Prince: + Datumama: 10, 00 00 00 00 00 00 00 00 00 0f + Datumama: 10, 00 00 00 00 00 00 00 00 00 0f + + Megami Paradise II: + Datumama: 10, 00 00 00 00 00 00 00 00 00 0a + + Miraculum: + Datumama: 7, 00 00 00 00 29 01 00 + Datumama: 10, 00 00 00 00 00 00 00 00 00 0f + Datumama: 7, 00 00 00 00 29 01 00 + Datumama: 10, 00 00 00 00 00 00 00 00 00 00 + Datumama: 7, 00 00 00 00 29 01 00 + + Pachio Kun FX: + Datumama: 10, 00 00 00 00 00 00 00 00 00 14 + + Return to Zork: + Datumama: 10, 00 00 00 00 00 00 00 00 00 00 + + Sotsugyou II: + Datumama: 10, 00 00 00 00 01 00 00 00 00 01 + + Tokimeki Card Paradise: + Datumama: 10, 00 00 00 00 00 00 00 00 00 14 + Datumama: 10, 00 00 00 00 00 00 00 00 00 07 + + Tonari no Princess Rolfee: + Datumama: 10, 00 00 00 00 00 00 00 00 00 00 + + Zoku Hakutoi Monogatari: + Datumama: 10, 00 00 00 00 00 00 00 00 00 14 +*/ + + // Page 151: MODE SENSE(6) + // PC = 0 current + // PC = 1 Changeable + // PC = 2 Default + // PC = 3 Saved + // Page 183: Mode parameter header. + // Page 363: CD-ROM density codes. + // Page 364: CD-ROM mode page codes. + // Page 469: ASC and ASCQ table + + +struct ModePageParam +{ + uint8_t default_value; + uint8_t alterable_mask; // Alterable mask reported when PC == 1 + uint8_t real_mask; // Real alterable mask. +}; + +struct ModePage +{ + const uint8_t code; + const uint8_t param_length; + const ModePageParam params[64]; // 64 should be more than enough + uint8_t current_value[64]; +}; + +/* + Mode pages present: + 0x00: + 0x0E: + 0x28: + 0x29: + 0x2A: + 0x2B: + 0x3F(Yes, not really a mode page but a fetch method) +*/ +// Remember to update the code in StateAction() if we change the number or layout of modepages here. +static const int NumModePages = 5; +static ModePage ModePages[NumModePages] = +{ + // Unknown + { 0x28, + 0x04, + { + { 0x00, 0x00, 0xFF }, + { 0x00, 0x00, 0xFF }, + { 0x00, 0x00, 0xFF }, + { 0x00, 0x00, 0xFF }, + } + }, + + // Unknown + { 0x29, + 0x01, + { + { 0x00, 0x00, 0xFF }, + } + }, + + // Unknown + { 0x2a, + 0x02, + { + { 0x00, 0x00, 0xFF }, + { 0x11, 0x00, 0xFF }, + } + }, + + // CD-DA playback speed modifier + { 0x2B, + 0x01, + { + { 0x00, 0x00, 0xFF }, + } + }, + + // 0x0E goes last, for correct order of return data when page code == 0x3F + // Real mask values are probably not right; some functionality not emulated yet. + // CD-ROM audio control parameters + { 0x0E, + 0x0E, + { + { 0x04, 0x04, 0x04 }, // Immed + { 0x00, 0x00, 0x00 }, // Reserved + { 0x00, 0x00, 0x00 }, // Reserved + { 0x00, 0x01, 0x01 }, // Reserved? + { 0x00, 0x00, 0x00 }, // MSB of LBA per second. + { 0x00, 0x00, 0x00 }, // LSB of LBA per second. + { 0x01, 0x01, 0x03 }, // Outport port 0 channel selection. + { 0xFF, 0x00, 0x00 }, // Outport port 0 volume. + { 0x02, 0x02, 0x03 }, // Outport port 1 channel selection. + { 0xFF, 0x00, 0x00 }, // Outport port 1 volume. + { 0x00, 0x00, 0x00 }, // Outport port 2 channel selection. + { 0x00, 0x00, 0x00 }, // Outport port 2 volume. + { 0x00, 0x00, 0x00 }, // Outport port 3 channel selection. + { 0x00, 0x00, 0x00 }, // Outport port 3 volume. + } + }, +}; + +static void UpdateMPCacheP(const ModePage* mp) +{ + switch(mp->code) + { + case 0x0E: + { + const uint8_t *pd = &mp->current_value[0]; + + for(int i = 0; i < 2; i++) + cdda.OutPortChSelect[i] = pd[6 + i * 2]; + FixOPV(); + } + break; + + case 0x28: + break; + + case 0x29: + break; + + case 0x2A: + break; + + case 0x2B: + { + int speed; + int rate; + + // + // Not sure what the actual limits are, or what happens when exceeding them, but these will at least keep the + // CD-DA playback system from imploding in on itself. + // + // The range of speed values accessible via the BIOS CD-DA player is apparently -10 to 10. + // + // No game is known to use the CD-DA playback speed control. It may be useful in homebrew to lower the rate for fitting more CD-DA onto the disc, + // is implemented on the PC-FX in such a way that it degrades audio quality, so it wouldn't really make sense to increase the rate in homebrew. + // + // Due to performance considerations, we only partially emulate the CD-DA oversampling filters used on the PC Engine and PC-FX, and instead + // blast impulses into the 1.78MHz buffer, relying on the final sound resampler to kill spectrum mirrors. This is less than ideal, but generally + // works well in practice, except when lowering CD-DA playback rate...which causes the spectrum mirrors to enter the non-murder zone, causing + // the sound output amplitude to approach overflow levels. + // But, until there's a killer PC-FX homebrew game that necessitates more computationally-expensive CD-DA handling, + // I don't see a good reason to change how CD-DA resampling is currently implemented. + // + speed = std::max(-32, std::min(32, (int8_t)mp->current_value[0])); + rate = 44100 + 441 * speed; + + //printf("[SCSICD] Speed: %d(pre-clamped=%d) %d\n", speed, (int8_t)mp->current_value[0], rate); + cdda.CDDADivAcc = ((int64_t)System_Clock * (1024 * 1024) / (2 * rate)); + cdda.CDDADivAccVolFudge = 100 + speed; + FixOPV(); // Resampler impulse amplitude volume adjustment(call after setting cdda.CDDADivAccVolFudge) + } + break; + } +} + +static void UpdateMPCache(uint8_t code) +{ + for(int pi = 0; pi < NumModePages; pi++) + { + const ModePage* mp = &ModePages[pi]; + + if(mp->code == code) + { + UpdateMPCacheP(mp); + break; + } + } +} + +static void InitModePages(void) +{ + for(int pi = 0; pi < NumModePages; pi++) + { + ModePage *mp = &ModePages[pi]; + const ModePageParam *params = &ModePages[pi].params[0]; + + for(int parami = 0; parami < mp->param_length; parami++) + mp->current_value[parami] = params[parami].default_value; + + UpdateMPCacheP(mp); + } +} + +static void FinishMODESELECT6(const uint8_t *data, const uint8_t data_len) +{ + uint8_t mode_data_length, medium_type, device_specific, block_descriptor_length; + uint32_t offset = 0; + + //printf("[SCSICD] Mode Select (6) Data: Length=0x%02x, ", data_len); + //for(uint32_t i = 0; i < data_len; i++) + // printf("0x%02x ", data[i]); + //printf("\n"); + + if(data_len < 4) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + mode_data_length = data[offset++]; + medium_type = data[offset++]; + device_specific = data[offset++]; + block_descriptor_length = data[offset++]; + + // For now, shut up gcc. + (void)mode_data_length; + (void)medium_type; + (void)device_specific; + + if(block_descriptor_length & 0x7) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if((offset + block_descriptor_length) > data_len) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + // TODO: block descriptors. + offset += block_descriptor_length; + + // Now handle mode pages + while(offset < data_len) + { + const uint8_t code = data[offset++]; + uint8_t param_len = 0; + bool page_found = false; + + if(code == 0x00) + { + if((offset + 0x5) > data_len) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + UpdateMPCache(0x00); + + offset += 0x5; + continue; + } + + if(offset >= data_len) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + param_len = data[offset++]; + + for(int pi = 0; pi < NumModePages; pi++) + { + ModePage *mp = &ModePages[pi]; + + if(code == mp->code) + { + page_found = true; + + if(param_len != mp->param_length) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if((param_len + offset) > data_len) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + for(int parami = 0; parami < mp->param_length; parami++) + { + mp->current_value[parami] &= ~mp->params[parami].real_mask; + mp->current_value[parami] |= (data[offset++]) & mp->params[parami].real_mask; + } + + UpdateMPCacheP(mp); + break; + } + } + + if(!page_found) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + } + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoMODESENSE6(const uint8_t *cdb) +{ + unsigned int PC = (cdb[2] >> 6) & 0x3; + unsigned int PageCode = cdb[2] & 0x3F; + bool DBD = cdb[1] & 0x08; + int AllocSize = cdb[4]; + int index = 0; + uint8_t data_in[8192]; + uint8_t PageMatchOR = 0x00; + bool AnyPageMatch = false; + + //SCSIDBG("Mode sense 6: %02x %d %d %d", PageCode, PC, DBD, AllocSize); + + if(!AllocSize) + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + + if(PC == 3) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(PageCode == 0x00) // Special weird case. + { + if(DBD || PC) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + memset(data_in, 0, 0xA); + data_in[0] = 0x09; + data_in[2] = 0x80; + data_in[9] = 0x0F; + + if(AllocSize > 0xA) + AllocSize = 0xA; + + DoSimpleDataIn(data_in, AllocSize); + return; + } + + data_in[0] = 0x00; // Fill this in later. + data_in[1] = 0x00; // Medium type + data_in[2] = 0x00; // Device-specific parameter. + data_in[3] = DBD ? 0x00 : 0x08; // Block descriptor length. + index += 4; + + if(!DBD) + { + data_in[index++] = 0x00; // Density code. + MDFN_en24msb(&data_in[index], 0x6E); // FIXME: Number of blocks? + index += 3; + + data_in[index++] = 0x00; // Reserved + MDFN_en24msb(&data_in[index], 0x800); // Block length; + index += 3; + } + + PageMatchOR = 0x00; + if(PageCode == 0x3F) + PageMatchOR = 0x3F; + + for(int pi = 0; pi < NumModePages; pi++) + { + const ModePage *mp = &ModePages[pi]; + const ModePageParam *params = &ModePages[pi].params[0]; + + if((mp->code | PageMatchOR) != PageCode) + continue; + + AnyPageMatch = true; + + data_in[index++] = mp->code; + data_in[index++] = mp->param_length; + + for(int parami = 0; parami < mp->param_length; parami++) + { + uint8_t data; + + if(PC == 0x02) + data = params[parami].default_value; + else if(PC == 0x01) + data = params[parami].alterable_mask; + else + data = mp->current_value[parami]; + + data_in[index++] = data; + } + } + + if(!AnyPageMatch) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(AllocSize > index) + AllocSize = index; + + data_in[0] = AllocSize - 1; + + DoSimpleDataIn(data_in, AllocSize); +} + +static void DoSTARTSTOPUNIT6(const uint8_t *cdb) +{ + //bool Immed = cdb[1] & 0x01; + //bool LoEj = cdb[4] & 0x02; + //bool Start = cdb[4] & 0x01; + + //SCSIDBG("Do start stop unit 6: %d %d %d\n", Immed, LoEj, Start); + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoREZEROUNIT(const uint8_t *cdb) +{ + //SCSIDBG("Rezero Unit: %02x\n", cdb[5]); + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +// This data was originally taken from a PC-FXGA software loader, but +// while it was mostly correct(maybe it is correct for the FXGA, but not for the PC-FX?), +// it was 3 bytes too long, and the last real byte was 0x45 instead of 0x20. +// TODO: Investigate this discrepancy by testing an FXGA with the official loader software. +#if 0 +static const uint8_t InqData[0x24] = +{ + // Standard + 0x05, 0x80, 0x02, 0x00, + + // Additional Length + 0x1F, + + // Vendor Specific + 0x00, 0x00, 0x00, 0x4E, 0x45, 0x43, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x43, 0x44, 0x2D, 0x52, 0x4F, + 0x4D, 0x20, 0x44, 0x52, 0x49, 0x56, 0x45, 0x3A, + 0x46, 0x58, 0x20, 0x31, 0x2E, 0x30, 0x20 +}; +#endif + +// Miraculum behaves differently if the last byte(offset 0x23) of the inquiry data is 0x45(ASCII character 'E'). Relavent code is at PC=0x3E382 +// If it's = 0x45, it will run MODE SELECT, and transfer this data to the CD unit: 00 00 00 00 29 01 00 +static const uint8_t InqData[0x24] = +{ + // Peripheral device-type: CD-ROM/read-only direct access device + 0x05, + + // Removable media: yes + // Device-type qualifier: 0 + 0x80, + + // ISO version: 0 + // ECMA version: 0 + // ANSI version: 2 (SCSI-2? ORLY?) + 0x02, + + // Supports asynchronous event notification: no + // Supports the terminate I/O process message: no + // Response data format: 0 (not exactly correct, not exactly incorrect, meh. :b) + 0x00, + + // Additional Length + 0x1F, + + // Reserved + 0x00, 0x00, + + // Yay, no special funky features. + 0x00, + + // 8-15, vendor ID + // NEC + 0x4E, 0x45, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20, + + // 16-31, product ID + // CD-ROM DRIVE:FX + 0x43, 0x44, 0x2D, 0x52, 0x4F, 0x4D, 0x20, 0x44, 0x52, 0x49, 0x56, 0x45, 0x3A, 0x46, 0x58, 0x20, + + // 32-35, product revision level + // 1.0 + 0x31, 0x2E, 0x30, 0x20 +}; + +static void DoINQUIRY(const uint8_t *cdb) +{ + unsigned int AllocSize = (cdb[4] < sizeof(InqData)) ? cdb[4] : sizeof(InqData); + + if(AllocSize) + DoSimpleDataIn(InqData, AllocSize); + else + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoNEC_NOP(const uint8_t *cdb) +{ + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* PC-FX CD Command 0xDC - EJECT * +* * +********************************************************/ +static void DoNEC_EJECT(const uint8_t *cdb) +{ + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_REQUEST_IN_CDB); +} + +static void DoREQUESTSENSE(const uint8_t *cdb) +{ + uint8_t data_in[8192]; + + MakeSense(data_in, cd.key_pending, cd.asc_pending, cd.ascq_pending, cd.fru_pending); + + DoSimpleDataIn(data_in, 18); + + cd.key_pending = 0; + cd.asc_pending = 0; + cd.ascq_pending = 0; + cd.fru_pending = 0; +} + +static void EncodeM3TOC(uint8_t *buf, uint8_t POINTER_RAW, int32_t LBA, uint32_t PLBA, uint8_t control) +{ + uint8_t MIN, SEC, FRAC; + uint8_t PMIN, PSEC, PFRAC; + + LBA_to_AMSF(LBA, &MIN, &SEC, &FRAC); + LBA_to_AMSF(PLBA, &PMIN, &PSEC, &PFRAC); + + buf[0x0] = control << 4; + buf[0x1] = 0x00; // TNO + buf[0x2] = POINTER_RAW; + buf[0x3] = U8_to_BCD(MIN); + buf[0x4] = U8_to_BCD(SEC); + buf[0x5] = U8_to_BCD(FRAC); + buf[0x6] = 0x00; // Zero + buf[0x7] = U8_to_BCD(PMIN); + buf[0x8] = U8_to_BCD(PSEC); + buf[0x9] = U8_to_BCD(PFRAC); +} + +/******************************************************** +* * +* PC-FX CD Command 0xDE - Get Directory Info * +* * +********************************************************/ +static void DoNEC_GETDIRINFO(const uint8_t *cdb) +{ + // Problems: + // Mode 0x03 has a few semi-indeterminate(but within a range, and they only change when the disc is reloaded) fields on a real PC-FX, that correspond to where in the lead-in area the data + // was read, that we don't bother to handle here. + // Mode 0x03 returns weird/wrong control field data for the "last track" and "leadout" entries in the "Blue Breaker" TOC. + // A bug in the PC-FX CD firmware, or an oddity of the disc(maybe other PC-FX discs are similar)? Or maybe it's an undefined field in that context? + // "Match" value of 0xB0 is probably not handled properly. Is it to return the catalog number, or something else? + + uint8_t data_in[2048]; + uint32_t data_in_size = 0; + + memset(data_in, 0, sizeof(data_in)); + + switch(cdb[1] & 0x03) + { + // This commands returns relevant raw TOC data as encoded in the Q subchannel(sans the CRC bytes). + case 0x3: + { + int offset = 0; + int32_t lilba = -150; + uint8_t match = cdb[2]; + + if(match != 0x00 && match != 0xA0 && match != 0xA1 && match != 0xA2 && match != 0xB0) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_ADDRESS); + return; + } + memset(data_in, 0, sizeof(data_in)); + + data_in[0] = 0x00; // Size MSB??? + data_in[1] = 0x00; // Total Size - 2(we'll fill it in later). + offset = 2; + + if(!match || match == 0xA0) + { + EncodeM3TOC(&data_in[offset], 0xA0, lilba, toc.first_track * 75 * 60 - 150, toc.tracks[toc.first_track].control); + lilba++; + offset += 0xA; + } + + if(!match || match == 0xA1) + { + EncodeM3TOC(&data_in[offset], 0xA1, lilba, toc.last_track * 75 * 60 - 150, toc.tracks[toc.last_track].control); + lilba++; + offset += 0xA; + } + + if(!match || match == 0xA2) + { + EncodeM3TOC(&data_in[offset], 0xA2, lilba, toc.tracks[100].lba, toc.tracks[100].control); + lilba++; + offset += 0xA; + } + + if(!match) + for(int track = toc.first_track; track <= toc.last_track; track++) + { + EncodeM3TOC(&data_in[offset], U8_to_BCD(track), lilba, toc.tracks[track].lba, toc.tracks[track].control); + lilba++; + offset += 0xA; + } + + if(match == 0xB0) + { + memset(&data_in[offset], 0, 0x14); + offset += 0x14; + } + + assert((unsigned int)offset <= sizeof(data_in)); + data_in_size = offset; + MDFN_en16msb(&data_in[0], offset - 2); + } + break; + + case 0x0: + data_in[0] = U8_to_BCD(toc.first_track); + data_in[1] = U8_to_BCD(toc.last_track); + + data_in_size = 4; + break; + + case 0x1: + { + uint8_t m, s, f; + + LBA_to_AMSF(toc.tracks[100].lba, &m, &s, &f); + + data_in[0] = U8_to_BCD(m); + data_in[1] = U8_to_BCD(s); + data_in[2] = U8_to_BCD(f); + + data_in_size = 4; + } + break; + + case 0x2: + { + uint8_t m, s, f; + int track = BCD_to_U8(cdb[2]); + + if(track < toc.first_track || track > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_ADDRESS); + return; + } + + LBA_to_AMSF(toc.tracks[track].lba, &m, &s, &f); + + data_in[0] = U8_to_BCD(m); + data_in[1] = U8_to_BCD(s); + data_in[2] = U8_to_BCD(f); + data_in[3] = toc.tracks[track].control; + data_in_size = 4; + } + break; + } + + DoSimpleDataIn(data_in, data_in_size); +} + +static void DoREADTOC(const uint8_t *cdb) +{ + uint8_t data_in[8192]; + int FirstTrack = toc.first_track; + int LastTrack = toc.last_track; + int StartingTrack = cdb[6]; + unsigned int AllocSize = (cdb[7] << 8) | cdb[8]; + unsigned int RealSize = 0; + const bool WantInMSF = cdb[1] & 0x2; + + if(!AllocSize) + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + + if((cdb[1] & ~0x2) || cdb[2] || cdb[3] || cdb[4] || cdb[5] || cdb[9]) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(!StartingTrack) + StartingTrack = 1; + else if(StartingTrack == 0xAA) + { + StartingTrack = LastTrack + 1; + } + else if(StartingTrack > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + data_in[2] = FirstTrack; + data_in[3] = LastTrack; + + RealSize += 4; + + // Read leadout track too LastTrack + 1 ??? + for(int track = StartingTrack; track <= (LastTrack + 1); track++) + { + uint8_t *subptr = &data_in[RealSize]; + uint32_t lba; + uint8_t m, s, f; + uint32_t eff_track; + + if(track == (LastTrack + 1)) + eff_track = 100; + else + eff_track = track; + + lba = toc.tracks[eff_track].lba; + LBA_to_AMSF(lba, &m, &s, &f); + + subptr[0] = 0; + subptr[1] = toc.tracks[eff_track].control | (toc.tracks[eff_track].adr << 4); + + if(eff_track == 100) + subptr[2] = 0xAA; + else + subptr[2] = track; + + subptr[3] = 0; + + if(WantInMSF) + { + subptr[4] = 0; + subptr[5] = m; // Min + subptr[6] = s; // Sec + subptr[7] = f; // Frames + } + else + { + subptr[4] = lba >> 24; + subptr[5] = lba >> 16; + subptr[6] = lba >> 8; + subptr[7] = lba >> 0; + } + RealSize += 8; + } + + // PC-FX: AllocSize too small doesn't reflect in this. + data_in[0] = (RealSize - 2) >> 8; + data_in[1] = (RealSize - 2) >> 0; + + DoSimpleDataIn(data_in, (AllocSize < RealSize) ? AllocSize : RealSize); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x25 - READ CD-ROM CAPACITY * +* * +********************************************************/ +static void DoREADCDCAP10(const uint8_t *cdb) +{ + bool pmi = cdb[8] & 0x1; + uint32_t lba = MDFN_de32msb(cdb + 0x2); + uint32_t ret_lba; + uint32_t ret_bl; + uint8_t data_in[8]; + + memset(data_in, 0, sizeof(data_in)); + + if(lba > 0x05FF69) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + + ret_lba = toc.tracks[100].lba - 1; + + if(pmi) + { + // Look for the track containing the LBA specified, then search for the first track afterwards that has a different track type(audio, data), + // and set the returned LBA to the sector preceding that track. + // + // If the specified LBA is >= leadout track, return the LBA of the sector immediately before the leadout track. + // + // If the specified LBA is < than the LBA of the first track, then return the LBA of sector preceding the first track. (I don't know if PC-FX can even handle discs like this, though) + if(lba >= toc.tracks[100].lba) + ret_lba = toc.tracks[100].lba - 1; + else if(lba < toc.tracks[toc.first_track].lba) + ret_lba = toc.tracks[toc.first_track].lba - 1; + else + { + const int track = toc.FindTrackByLBA(lba); + + for(int st = track + 1; st <= toc.last_track; st++) + { + if((toc.tracks[st].control ^ toc.tracks[track].control) & 0x4) + { + ret_lba = toc.tracks[st].lba - 1; + break; + } + } + } + } + + ret_bl = 2048; + + MDFN_en32msb(&data_in[0], ret_lba); + MDFN_en32msb(&data_in[4], ret_bl); + + cdda.CDDAStatus = CDDASTATUS_STOPPED; + + DoSimpleDataIn(data_in, 8); +} + +static void DoREADHEADER10(const uint8_t *cdb) +{ + uint8_t data_in[8192]; + bool WantInMSF = cdb[1] & 0x2; + uint32_t HeaderLBA = MDFN_de32msb(cdb + 0x2); + int AllocSize = MDFN_de16msb(cdb + 0x7); + uint8_t raw_buf[2352 + 96]; + uint8_t mode; + int m, s, f; + uint32_t lba; + + // Don't run command at all if AllocSize == 0(FIXME: On a real PC-FX, this command will return success + // if there's no CD when AllocSize == 0, implement this here, might require refactoring). + if(!AllocSize) + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + + if(HeaderLBA >= toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(HeaderLBA < toc.tracks[toc.first_track].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + Cur_CDIF->ReadRawSector(raw_buf, HeaderLBA); //, HeaderLBA + 1); + if(!ValidateRawDataSector(raw_buf, HeaderLBA)) + return; + + m = BCD_to_U8(raw_buf[12 + 0]); + s = BCD_to_U8(raw_buf[12 + 1]); + f = BCD_to_U8(raw_buf[12 + 2]); + mode = raw_buf[12 + 3]; + lba = AMSF_to_LBA(m, s, f); + + //printf("%d:%d:%d(LBA=%08x) %02x\n", m, s, f, lba, mode); + + data_in[0] = mode; + data_in[1] = 0; + data_in[2] = 0; + data_in[3] = 0; + + if(WantInMSF) + { + data_in[4] = 0; + data_in[5] = m; // Min + data_in[6] = s; // Sec + data_in[7] = f; // Frames + } + else + { + data_in[4] = lba >> 24; + data_in[5] = lba >> 16; + data_in[6] = lba >> 8; + data_in[7] = lba >> 0; + } + + cdda.CDDAStatus = CDDASTATUS_STOPPED; + + DoSimpleDataIn(data_in, 8); +} + +static void DoNEC_SST(const uint8_t *cdb) // Command 0xDB, Set Stop Time +{ + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoPABase(const uint32_t lba, const uint32_t length, unsigned int status = CDDASTATUS_PLAYING, unsigned int mode = PLAYMODE_NORMAL) +{ + if(lba > toc.tracks[100].lba) // > is not a typo, it's a PC-FX bug apparently. + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(lba < toc.tracks[toc.first_track].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(!length) // FIXME to return good status in this case even if no CD is present + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + else + { + if(toc.tracks[toc.FindTrackByLBA(lba)].control & 0x04) + { + CommandCCError(SENSEKEY_MEDIUM_ERROR, NSE_NOT_AUDIO_TRACK); + return; + } + + cdda.CDDAReadPos = 588; + read_sec = read_sec_start = lba; + read_sec_end = read_sec_start + length; + + cdda.CDDAStatus = status; + cdda.PlayMode = mode; + + if(read_sec < toc.tracks[100].lba) + { + Cur_CDIF->HintReadSector(read_sec); //, read_sec_end, read_sec_start); + } + } + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* PC-FX CD Command 0xD8 - SAPSP * +* * +********************************************************/ +static void DoNEC_SAPSP(const uint8_t *cdb) +{ + uint32_t lba; + + switch (cdb[9] & 0xc0) + { + default: + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + break; + + case 0x00: + lba = MDFN_de24msb(&cdb[3]); + break; + + case 0x40: + { + uint8_t m, s, f; + + if(!BCD_to_U8_check(cdb[2], &m) || !BCD_to_U8_check(cdb[3], &s) || !BCD_to_U8_check(cdb[4], &f)) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + lba = AMSF_to_LBA(m, s, f); + } + break; + + case 0x80: + { + uint8_t track; + + if(!cdb[2] || !BCD_to_U8_check(cdb[2], &track)) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(track == toc.last_track + 1) + track = 100; + else if(track > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + lba = toc.tracks[track].lba; + } + break; + } + + if(cdb[1] & 0x01) + DoPABase(lba, toc.tracks[100].lba - lba, CDDASTATUS_PLAYING, PLAYMODE_NORMAL); + else + DoPABase(lba, toc.tracks[100].lba - lba, CDDASTATUS_PAUSED, PLAYMODE_SILENT); +} + + + +/******************************************************** +* * +* PC-FX CD Command 0xD9 - SAPEP * +* * +********************************************************/ +static void DoNEC_SAPEP(const uint8_t *cdb) +{ + uint32_t lba; + + if(cdda.CDDAStatus == CDDASTATUS_STOPPED) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_AUDIO_NOT_PLAYING); + return; + } + + switch (cdb[9] & 0xc0) + { + default: + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + break; + + case 0x00: + lba = MDFN_de24msb(&cdb[3]); + break; + + case 0x40: + { + uint8_t m, s, f; + + if(!BCD_to_U8_check(cdb[2], &m) || !BCD_to_U8_check(cdb[3], &s) || !BCD_to_U8_check(cdb[4], &f)) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + lba = AMSF_to_LBA(m, s, f); + } + break; + + case 0x80: + { + uint8_t track; + + if(!cdb[2] || !BCD_to_U8_check(cdb[2], &track)) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(track == toc.last_track + 1) + track = 100; + else if(track > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + lba = toc.tracks[track].lba; + } + break; + } + + switch(cdb[1] & 0x7) + { + case 0x00: cdda.PlayMode = PLAYMODE_SILENT; + break; + + case 0x04: cdda.PlayMode = PLAYMODE_LOOP; + break; + + default: cdda.PlayMode = PLAYMODE_NORMAL; + break; + } + cdda.CDDAStatus = CDDASTATUS_PLAYING; + + read_sec_end = lba; + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x45 - PLAY AUDIO(10) * +* * +********************************************************/ +static void DoPA10(const uint8_t *cdb) +{ + // Real PC-FX Bug: Error out on LBA >(not >=) leadout sector number + const uint32_t lba = MDFN_de32msb(cdb + 0x2); + const uint16 length = MDFN_de16msb(cdb + 0x7); + + DoPABase(lba, length); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0xA5 - PLAY AUDIO(12) * +* * +********************************************************/ +static void DoPA12(const uint8_t *cdb) +{ + // Real PC-FX Bug: Error out on LBA >(not >=) leadout sector number + const uint32_t lba = MDFN_de32msb(cdb + 0x2); + const uint32_t length = MDFN_de32msb(cdb + 0x6); + + DoPABase(lba, length); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x47 - PLAY AUDIO MSF * +* * +********************************************************/ +static void DoPAMSF(const uint8_t *cdb) +{ + int32_t lba_start, lba_end; + + lba_start = AMSF_to_LBA(cdb[3], cdb[4], cdb[5]); + lba_end = AMSF_to_LBA(cdb[6], cdb[7], cdb[8]); + + if(lba_start < 0 || lba_end < 0 || lba_start >= (int32_t)toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + + if(lba_start == lba_end) + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + else if(lba_start > lba_end) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_ADDRESS); + return; + } + + cdda.CDDAReadPos = 588; + read_sec = read_sec_start = lba_start; + read_sec_end = lba_end; + + cdda.CDDAStatus = CDDASTATUS_PLAYING; + cdda.PlayMode = PLAYMODE_NORMAL; + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +static void DoPATI(const uint8_t *cdb) +{ + // "Boundary Gate" uses this command. + // Problems: + // The index fields aren't handled. The ending index wouldn't be too bad, but the starting index would require a bit of work and code uglyfying(to scan for the index), and may be highly + // problematic when Mednafen is used with a physical CD. + int StartTrack = cdb[4]; + int EndTrack = cdb[7]; + //int StartIndex = cdb[5]; + //int EndIndex = cdb[8]; + + if(!StartTrack || StartTrack < toc.first_track || StartTrack > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + //printf("PATI: %d %d %d SI: %d, EI: %d\n", StartTrack, EndTrack, Cur_CDIF->GetTrackStartPositionLBA(StartTrack), StartIndex, EndIndex); + + DoPABase(toc.tracks[StartTrack].lba, toc.tracks[EndTrack].lba - toc.tracks[StartTrack].lba); +} + + +static void DoPATRBase(const uint32_t lba, const uint32_t length) +{ + if(lba >= toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(lba < toc.tracks[toc.first_track].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(!length) // FIXME to return good status in this case even if no CD is present + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + else + { + if(toc.tracks[toc.FindTrackByLBA(lba)].control & 0x04) + { + CommandCCError(SENSEKEY_MEDIUM_ERROR, NSE_NOT_AUDIO_TRACK); + return; + } + + cdda.CDDAReadPos = 588; + read_sec = read_sec_start = lba; + read_sec_end = read_sec_start + length; + + cdda.CDDAStatus = CDDASTATUS_PLAYING; + cdda.PlayMode = PLAYMODE_NORMAL; + } + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + +/******************************************************** +* * +* SCSI-2 CD Command 0x49 - PLAY AUDIO TRACK * +* RELATIVE(10) * +********************************************************/ +static void DoPATR10(const uint8_t *cdb) +{ + const int32_t rel_lba = MDFN_de32msb(cdb + 0x2); + const int StartTrack = cdb[6]; + const uint16 length = MDFN_de16msb(cdb + 0x7); + + if(!StartTrack || StartTrack < toc.first_track || StartTrack > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + DoPATRBase(toc.tracks[StartTrack].lba + rel_lba, length); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0xA9 - PLAY AUDIO TRACK * +* RELATIVE(12) * +********************************************************/ +static void DoPATR12(const uint8_t *cdb) +{ + const int32_t rel_lba = MDFN_de32msb(cdb + 0x2); + const int StartTrack = cdb[10]; + const uint32_t length = MDFN_de32msb(cdb + 0x6); + + if(!StartTrack || StartTrack < toc.first_track || StartTrack > toc.last_track) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + DoPATRBase(toc.tracks[StartTrack].lba + rel_lba, length); +} + +static void DoPAUSERESUME(const uint8_t *cdb) +{ + // Pause/resume + // "It shall not be considered an error to request a pause when a pause is already in effect, + // or to request a resume when a play operation is in progress." + + if(cdda.CDDAStatus == CDDASTATUS_STOPPED) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_AUDIO_NOT_PLAYING); + return; + } + + if(cdb[8] & 1) // Resume + cdda.CDDAStatus = CDDASTATUS_PLAYING; + else + cdda.CDDAStatus = CDDASTATUS_PAUSED; + + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + + + +static void DoREADBase(uint32_t sa, uint32_t sc) +{ + int track; + + if(sa > toc.tracks[100].lba) // Another one of those off-by-one PC-FX CD bugs. + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + + if((track = toc.FindTrackByLBA(sa)) == 0) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + + if(!(toc.tracks[track].control) & 0x4) + { + CommandCCError(SENSEKEY_MEDIUM_ERROR, NSE_NOT_DATA_TRACK); + return; + } + + // Case for READ(10) and READ(12) where sc == 0, and sa == toc.tracks[100].lba + if(!sc && sa == toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_MEDIUM_ERROR, NSE_HEADER_READ_ERROR); + return; + } + + if(SCSILog) + { + int Track = toc.FindTrackByLBA(sa); + uint32_t Offset = sa - toc.tracks[Track].lba; //Cur_CDIF->GetTrackStartPositionLBA(Track); + SCSILog("SCSI", "Read: start=0x%08x(track=%d, offs=0x%08x), cnt=0x%08x", sa, Track, Offset, sc); + } + + SectorAddr = sa; + SectorCount = sc; + if(SectorCount) + { + Cur_CDIF->HintReadSector(sa); //, sa + sc); + + CDReadTimer = (uint64_t)((WhichSystem == SCSICD_PCE) ? 3 : 1) * 2048 * System_Clock / CD_DATA_TRANSFER_RATE; + } + else + { + CDReadTimer = 0; + SendStatusAndMessage(STATUS_GOOD, 0x00); + } + cdda.CDDAStatus = CDDASTATUS_STOPPED; +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x08 - READ(6) * +* * +********************************************************/ +static void DoREAD6(const uint8_t *cdb) +{ + uint32_t sa = ((cdb[1] & 0x1F) << 16) | (cdb[2] << 8) | (cdb[3] << 0); + uint32_t sc = cdb[4]; + + // TODO: confirm real PCE does this(PC-FX does at least). + if(!sc) + { + //SCSIDBG("READ(6) with count == 0.\n"); + sc = 256; + } + + DoREADBase(sa, sc); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x28 - READ(10) * +* * +********************************************************/ +static void DoREAD10(const uint8_t *cdb) +{ + uint32_t sa = MDFN_de32msb(cdb + 0x2); + uint32_t sc = MDFN_de16msb(cdb + 0x7); + + DoREADBase(sa, sc); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0xA8 - READ(12) * +* * +********************************************************/ +static void DoREAD12(const uint8_t *cdb) +{ + uint32_t sa = MDFN_de32msb(cdb + 0x2); + uint32_t sc = MDFN_de32msb(cdb + 0x6); + + DoREADBase(sa, sc); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x34 - PREFETCH(10) * +* * +********************************************************/ +static void DoPREFETCH(const uint8_t *cdb) +{ + uint32_t lba = MDFN_de32msb(cdb + 0x2); + //uint32_t len = MDFN_de16msb(cdb + 0x7); + //bool reladdr = cdb[1] & 0x1; + //bool immed = cdb[1] & 0x2; + + // Note: This command appears to lock up the CD unit to some degree on a real PC-FX if the (lba + len) >= leadout_track_lba, + // more testing is needed if we ever try to fully emulate this command. + if(lba >= toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + + //printf("Prefetch: %08x %08x %d %d %d %d\n", lba, len, link, flag, reladdr, immed); + //SendStatusAndMessage(STATUS_GOOD, 0x00); + SendStatusAndMessage(STATUS_CONDITION_MET, 0x00); +} + + + + +// SEEK functions are mostly just stubs for now, until(if) we emulate seek delays. +static void DoSEEKBase(uint32_t lba) +{ + if(lba >= toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + return; + } + + cdda.CDDAStatus = CDDASTATUS_STOPPED; + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x0B - SEEK(6) * +* * +********************************************************/ +static void DoSEEK6(const uint8_t *cdb) +{ + uint32_t lba = ((cdb[1] & 0x1F) << 16) | (cdb[2] << 8) | cdb[3]; + + DoSEEKBase(lba); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x2B - SEEK(10) * +* * +********************************************************/ +static void DoSEEK10(const uint8_t *cdb) +{ + uint32_t lba = MDFN_de32msb(cdb + 0x2); + + DoSEEKBase(lba); +} + +// 353 +/******************************************************** +* * +* SCSI-2 CD Command 0x42 - READ SUB-CHANNEL(10) * +* * +********************************************************/ +static void DoREADSUBCHANNEL(const uint8_t *cdb) +{ + uint8_t data_in[8192]; + int DataFormat = cdb[3]; + int TrackNum = cdb[6]; + unsigned AllocSize = (cdb[7] << 8) | cdb[8]; + bool WantQ = cdb[2] & 0x40; + bool WantMSF = cdb[1] & 0x02; + uint32_t offset = 0; + + if(!AllocSize) + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + return; + } + + if(DataFormat > 0x3) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + if(DataFormat == 0x3 && (TrackNum < toc.first_track || TrackNum > toc.last_track)) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER); + return; + } + + data_in[offset++] = 0; + + // FIXME: Is this audio status code correct for scanning playback?? + if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) + data_in[offset++] = 0x11; // Audio play operation in progress + else if(cdda.CDDAStatus == CDDASTATUS_PAUSED) + data_in[offset++] = 0x12; // Audio play operation paused + else + data_in[offset++] = 0x13; // 0x13(audio play operation completed successfully) or 0x15(no current audio status to return)? :( + + + // Subchannel data length(at data_in[0x2], filled out at the end of the function) + data_in[offset++] = 0x00; + data_in[offset++] = 0x00; + + //printf("42Read SubChannel: %02x %02x %d %d %d\n", DataFormat, TrackNum, AllocSize, WantQ, WantMSF); + if(WantQ) + { + // Sub-channel format code + data_in[offset++] = DataFormat; + if(!DataFormat || DataFormat == 0x01) + { + uint8_t *SubQBuf = cd.SubQBuf[QMode_Time]; + + data_in[offset++] = ((SubQBuf[0] & 0x0F) << 4) | ((SubQBuf[0] & 0xF0) >> 4); // Control/adr + data_in[offset++] = SubQBuf[1]; // Track + data_in[offset++] = SubQBuf[2]; // Index + + // Absolute CD-ROM address + if(WantMSF) + { + data_in[offset++] = 0; + data_in[offset++] = BCD_to_U8(SubQBuf[7]); // M + data_in[offset++] = BCD_to_U8(SubQBuf[8]); // S + data_in[offset++] = BCD_to_U8(SubQBuf[9]); // F + } + else + { + uint32_t tmp_lba = BCD_to_U8(SubQBuf[7]) * 60 * 75 + BCD_to_U8(SubQBuf[8]) * 75 + BCD_to_U8(SubQBuf[9]) - 150; + + data_in[offset++] = tmp_lba >> 24; + data_in[offset++] = tmp_lba >> 16; + data_in[offset++] = tmp_lba >> 8; + data_in[offset++] = tmp_lba >> 0; + } + + // Relative CD-ROM address + if(WantMSF) + { + data_in[offset++] = 0; + data_in[offset++] = BCD_to_U8(SubQBuf[3]); // M + data_in[offset++] = BCD_to_U8(SubQBuf[4]); // S + data_in[offset++] = BCD_to_U8(SubQBuf[5]); // F + } + else + { + uint32_t tmp_lba = BCD_to_U8(SubQBuf[3]) * 60 * 75 + BCD_to_U8(SubQBuf[4]) * 75 + BCD_to_U8(SubQBuf[5]); // Don't subtract 150 in the conversion! + + data_in[offset++] = tmp_lba >> 24; + data_in[offset++] = tmp_lba >> 16; + data_in[offset++] = tmp_lba >> 8; + data_in[offset++] = tmp_lba >> 0; + } + } + + if(!DataFormat || DataFormat == 0x02) + { + if(DataFormat == 0x02) + { + data_in[offset++] = 0x00; + data_in[offset++] = 0x00; + data_in[offset++] = 0x00; + } + data_in[offset++] = 0x00; // MCVal and reserved. + for(int i = 0; i < 15; i++) + data_in[offset++] = 0x00; + } + + // Track ISRC + if(!DataFormat || DataFormat == 0x03) + { + if(DataFormat == 0x03) + { + uint8_t *SubQBuf = cd.SubQBuf[QMode_Time]; // FIXME + data_in[offset++] = ((SubQBuf[0] & 0x0F) << 4) | ((SubQBuf[0] & 0xF0) >> 4); // Control/adr + data_in[offset++] = TrackNum; // From sub Q or from parameter? + data_in[offset++] = 0x00; // Reserved. + } + data_in[offset++] = 0x00; // TCVal and reserved + for(int i = 0; i < 15; i++) + data_in[offset++] = 0x00; + } + } + + MDFN_en16msb(&data_in[0x2], offset - 0x4); + + DoSimpleDataIn(data_in, (AllocSize > offset) ? offset : AllocSize); +} + + + +/******************************************************** +* * +* PC-FX CD Command 0xDD - READ SUB Q * +* * +********************************************************/ +static void DoNEC_READSUBQ(const uint8_t *cdb) +{ + uint8_t *SubQBuf = cd.SubQBuf[QMode_Time]; + uint8_t data_in[10]; + const uint8_t alloc_size = (cdb[1] < 10) ? cdb[1] : 10; + + memset(data_in, 0x00, 10); + + if(cdda.CDDAStatus == CDDASTATUS_PAUSED) + data_in[0] = 2; // Pause + else if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) // FIXME: Is this the correct status code for scanning playback? + data_in[0] = 0; // Playing + else + data_in[0] = 3; // Stopped + + data_in[1] = SubQBuf[0]; // Control/adr + data_in[2] = SubQBuf[1]; // Track + data_in[3] = SubQBuf[2]; // Index + data_in[4] = SubQBuf[3]; // M(rel) + data_in[5] = SubQBuf[4]; // S(rel) + data_in[6] = SubQBuf[5]; // F(rel) + data_in[7] = SubQBuf[7]; // M(abs) + data_in[8] = SubQBuf[8]; // S(abs) + data_in[9] = SubQBuf[9]; // F(abs) + + DoSimpleDataIn(data_in, alloc_size); +} + +static void DoTESTUNITREADY(const uint8_t *cdb) +{ + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoNEC_PAUSE(const uint8_t *cdb) +{ + if(cdda.CDDAStatus != CDDASTATUS_STOPPED) // Hmm, should we give an error if it tries to pause and it's already paused? + { + cdda.CDDAStatus = CDDASTATUS_PAUSED; + SendStatusAndMessage(STATUS_GOOD, 0x00); + } + else // Definitely give an error if it tries to pause when no track is playing! + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_AUDIO_NOT_PLAYING); + } +} + +static void DoNEC_SCAN(const uint8_t *cdb) +{ + uint32_t sector_tmp = 0; + + // 0: 0xD2 + // 1: 0x03 = reverse scan, 0x02 = forward scan + // 2: End M + // 3: End S + // 4: End F + + switch (cdb[9] & 0xc0) + { + default: + //SCSIDBG("Unknown NECSCAN format"); + break; + + case 0x00: + sector_tmp = (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; + break; + + case 0x40: + sector_tmp = AMSF_to_LBA(BCD_to_U8(cdb[2]), BCD_to_U8(cdb[3]), BCD_to_U8(cdb[4])); + break; + + case 0x80: // FIXME: error on invalid track number??? + sector_tmp = toc.tracks[BCD_to_U8(cdb[2])].lba; + break; + } + + cdda.ScanMode = cdb[1] & 0x3; + cdda.scan_sec_end = sector_tmp; + + if(cdda.CDDAStatus != CDDASTATUS_STOPPED) + { + if(cdda.ScanMode) + { + cdda.CDDAStatus = CDDASTATUS_SCANNING; + } + } + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x1E - PREVENT/ALLOW MEDIUM * +* REMOVAL * +********************************************************/ +static void DoPREVENTALLOWREMOVAL(const uint8_t *cdb) +{ + //bool prevent = cdb[4] & 0x01; + //const int logical_unit = cdb[1] >> 5; + //SCSIDBG("PREVENT ALLOW MEDIUM REMOVAL: %d for %d\n", cdb[4] & 0x1, logical_unit); + //SendStatusAndMessage(STATUS_GOOD, 0x00); + + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_REQUEST_IN_CDB); +} + +// +// +// +#include "scsicd-pce-commands.inc" + + +#define SCF_REQUIRES_MEDIUM 0x0001 +#define SCF_INCOMPLETE 0x4000 +#define SCF_UNTESTED 0x8000 + +typedef struct +{ + uint8_t cmd; + uint32_t flags; + void (*func)(const uint8_t *cdb); + const char *pretty_name; + const char *format_string; +} SCSICH; + +static const int32_t RequiredCDBLen[16] = +{ + 6, // 0x0n + 6, // 0x1n + 10, // 0x2n + 10, // 0x3n + 10, // 0x4n + 10, // 0x5n + 10, // 0x6n + 10, // 0x7n + 10, // 0x8n + 10, // 0x9n + 12, // 0xAn + 12, // 0xBn + 10, // 0xCn + 10, // 0xDn + 10, // 0xEn + 10, // 0xFn +}; + +static SCSICH PCFXCommandDefs[] = +{ + { 0x00, SCF_REQUIRES_MEDIUM, DoTESTUNITREADY, "Test Unit Ready" }, + { 0x01, 0/* ? */, DoREZEROUNIT, "Rezero Unit" }, + { 0x03, 0, DoREQUESTSENSE, "Request Sense" }, + { 0x08, SCF_REQUIRES_MEDIUM, DoREAD6, "Read(6)" }, + { 0x0B, SCF_REQUIRES_MEDIUM, DoSEEK6, "Seek(6)" }, + { 0x0D, 0, DoNEC_NOP, "No Operation" }, + { 0x12, 0, DoINQUIRY, "Inquiry" }, + { 0x15, 0, DoMODESELECT6, "Mode Select(6)" }, + // TODO: { 0x16, 0 /* ? */, DoRESERVE, "Reserve" }, // 9.2.12 + // TODO: { 0x17, 0 /* ? */, DoRELEASE, "Release" }, // 9.2.11 + { 0x1A, 0, DoMODESENSE6, "Mode Sense(6)" }, + { 0x1B, SCF_REQUIRES_MEDIUM, DoSTARTSTOPUNIT6, "Start/Stop Unit" }, // 9.2.17 + // TODO: { 0x1D, , DoSENDDIAG, "Send Diagnostic" }, // 8.2.15 + { 0x1E, 0, DoPREVENTALLOWREMOVAL, "Prevent/Allow Media Removal" }, + + { 0x25, SCF_REQUIRES_MEDIUM, DoREADCDCAP10, "Read CD-ROM Capacity" }, // 14.2.8 + { 0x28, SCF_REQUIRES_MEDIUM, DoREAD10, "Read(10)" }, + { 0x2B, SCF_REQUIRES_MEDIUM, DoSEEK10, "Seek(10)" }, + + // TODO: { 0x2F, SCF_REQUIRES_MEDIUM, DoVERIFY10, "Verify(10)" }, // 16.2.11 + + { 0x34, SCF_REQUIRES_MEDIUM, DoPREFETCH, "Prefetch" }, + // TODO: { 0x3B, 0, 10, DoWRITEBUFFER, "Write Buffer" }, // 8.2.17 + // TODO: { 0x3C, 0, 10, DoREADBUFFER, "Read Buffer" }, // 8.2.12 + + { 0x42, SCF_REQUIRES_MEDIUM, DoREADSUBCHANNEL, "Read Subchannel" }, + { 0x43, SCF_REQUIRES_MEDIUM, DoREADTOC, "Read TOC" }, + { 0x44, SCF_REQUIRES_MEDIUM, DoREADHEADER10, "Read Header" }, + + { 0x45, SCF_REQUIRES_MEDIUM, DoPA10, "Play Audio(10)" }, + { 0x47, SCF_REQUIRES_MEDIUM, DoPAMSF, "Play Audio MSF" }, + { 0x48, SCF_REQUIRES_MEDIUM, DoPATI, "Play Audio Track Index" }, + { 0x49, SCF_REQUIRES_MEDIUM, DoPATR10, "Play Audio Track Relative(10)" }, + { 0x4B, SCF_REQUIRES_MEDIUM, DoPAUSERESUME, "Pause/Resume" }, + + { 0xA5, SCF_REQUIRES_MEDIUM, DoPA12, "Play Audio(12)" }, + { 0xA8, SCF_REQUIRES_MEDIUM, DoREAD12, "Read(12)" }, + { 0xA9, SCF_REQUIRES_MEDIUM, DoPATR12, "Play Audio Track Relative(12)" }, + + // TODO: { 0xAF, SCF_REQUIRES_MEDIUM, DoVERIFY12, "Verify(12)" }, // 16.2.12 + + { 0xD2, SCF_REQUIRES_MEDIUM, DoNEC_SCAN, "Scan" }, + { 0xD8, SCF_REQUIRES_MEDIUM, DoNEC_SAPSP, "Set Audio Playback Start Position" }, // "Audio track search" + { 0xD9, SCF_REQUIRES_MEDIUM, DoNEC_SAPEP, "Set Audio Playback End Position" }, // "Play" + { 0xDA, SCF_REQUIRES_MEDIUM, DoNEC_PAUSE, "Pause" }, // "Still" + { 0xDB, SCF_REQUIRES_MEDIUM | SCF_UNTESTED, DoNEC_SST, "Set Stop Time" }, + { 0xDC, SCF_REQUIRES_MEDIUM, DoNEC_EJECT, "Eject" }, + { 0xDD, SCF_REQUIRES_MEDIUM, DoNEC_READSUBQ, "Read Subchannel Q" }, + { 0xDE, SCF_REQUIRES_MEDIUM, DoNEC_GETDIRINFO, "Get Dir Info" }, + + { 0xFF, 0, 0, NULL, NULL }, +}; + +static SCSICH PCECommandDefs[] = +{ + { 0x00, SCF_REQUIRES_MEDIUM, DoTESTUNITREADY, "Test Unit Ready" }, + { 0x03, 0, DoREQUESTSENSE, "Request Sense" }, + { 0x08, SCF_REQUIRES_MEDIUM, DoREAD6, "Read(6)" }, + //{ 0x15, DoMODESELECT6, "Mode Select(6)" }, + { 0xD8, SCF_REQUIRES_MEDIUM, DoNEC_PCE_SAPSP, "Set Audio Playback Start Position" }, + { 0xD9, SCF_REQUIRES_MEDIUM, DoNEC_PCE_SAPEP, "Set Audio Playback End Position" }, + { 0xDA, SCF_REQUIRES_MEDIUM, DoNEC_PCE_PAUSE, "Pause" }, + { 0xDD, SCF_REQUIRES_MEDIUM, DoNEC_PCE_READSUBQ, "Read Subchannel Q" }, + { 0xDE, SCF_REQUIRES_MEDIUM, DoNEC_PCE_GETDIRINFO, "Get Dir Info" }, + + { 0xFF, 0, 0, NULL, NULL }, +}; + +void SCSICD_ResetTS(uint32_t ts_base) +{ + lastts = ts_base; +} + +void SCSICD_GetCDDAValues(int16 &left, int16 &right) +{ + if(cdda.CDDAStatus) + { + left = cdda.sr[0]; + right = cdda.sr[1]; + } + else + left = right = 0; +} + +#define CDDA_FILTER_NUMCONVOLUTIONS 7 +#define CDDA_FILTER_NUMCONVOLUTIONS_PADDED 8 + +#define CDDA_FILTER_NUMPHASES_SHIFT 6 +#define CDDA_FILTER_NUMPHASES (1 << CDDA_FILTER_NUMPHASES_SHIFT) + +alignas(16) static const int16 CDDA_Filter[1 + CDDA_FILTER_NUMPHASES + 1][CDDA_FILTER_NUMCONVOLUTIONS_PADDED] = +{ + #include "scsicd_cdda_filter.inc" +}; + +alignas(16) static const int16 OversampleFilter[2][0x10] = +{ + { -82, 217, -463, 877, -1562, 2783, -5661, 29464, 9724, -3844, 2074, -1176, 645, -323, 138, -43, }, /* sum=32768, sum_abs=59076 */ + { -43, 138, -323, 645, -1176, 2074, -3844, 9724, 29464, -5661, 2783, -1562, 877, -463, 217, -82, }, /* sum=32768, sum_abs=59076 */ +}; + +static INLINE void RunCDDA(uint32_t system_timestamp, int32_t run_time) +{ + if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) + { + cdda.CDDADiv -= (int64_t)run_time << 20; + + while(cdda.CDDADiv <= 0) + { + const uint32_t synthtime_ex = (((uint64_t)system_timestamp << 20) + (int64_t)cdda.CDDADiv) / cdda.CDDATimeDiv; + const int synthtime = (synthtime_ex >> 16) & 0xFFFF; // & 0xFFFF(or equivalent) to prevent overflowing HRBufs[] + const int synthtime_phase = (int)(synthtime_ex & 0xFFFF) - 0x80; + const int synthtime_phase_int = synthtime_phase >> (16 - CDDA_FILTER_NUMPHASES_SHIFT); + const int synthtime_phase_fract = synthtime_phase & ((1 << (16 - CDDA_FILTER_NUMPHASES_SHIFT)) - 1); + int32_t sample_va[2]; + + cdda.CDDADiv += cdda.CDDADivAcc; + + if(!(cdda.OversamplePos & 1)) + { + if(cdda.CDDAReadPos == 588) + { + if(read_sec >= read_sec_end || (cdda.CDDAStatus == CDDASTATUS_SCANNING && read_sec == cdda.scan_sec_end)) + { + switch(cdda.PlayMode) + { + case PLAYMODE_SILENT: + case PLAYMODE_NORMAL: + cdda.CDDAStatus = CDDASTATUS_STOPPED; + break; + + case PLAYMODE_INTERRUPT: + cdda.CDDAStatus = CDDASTATUS_STOPPED; + CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE); + break; + + case PLAYMODE_LOOP: + read_sec = read_sec_start; + break; + } + + // If CDDA playback is stopped, break out of our while(CDDADiv ...) loop and don't play any more sound! + if(cdda.CDDAStatus == CDDASTATUS_STOPPED) + break; + } + + // Don't play past the user area of the disc. + if(read_sec >= toc.tracks[100].lba) + { + cdda.CDDAStatus = CDDASTATUS_STOPPED; + break; + } + + if(TrayOpen || !Cur_CDIF) + { + cdda.CDDAStatus = CDDASTATUS_STOPPED; + + #if 0 + cd.data_transfer_done = FALSE; + cd.key_pending = SENSEKEY_NOT_READY; + cd.asc_pending = ASC_MEDIUM_NOT_PRESENT; + cd.ascq_pending = 0x00; + cd.fru_pending = 0x00; + SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00); + #endif + + break; + } + + + cdda.CDDAReadPos = 0; + + { + uint8_t tmpbuf[2352 + 96]; + + Cur_CDIF->ReadRawSector(tmpbuf, read_sec); //, read_sec_end, read_sec_start); + + for(int i = 0; i < 588 * 2; i++) + cdda.CDDASectorBuffer[i] = MDFN_de16lsb(&tmpbuf[i * 2]); + + memcpy(cd.SubPWBuf, tmpbuf + 2352, 96); + } + GenSubQFromSubPW(); + + if(!(cd.SubQBuf_Last[0] & 0x10)) + { + // Not using de-emphasis, so clear the de-emphasis filter state. + memset(cdda.DeemphState, 0, sizeof(cdda.DeemphState)); + } + + if(cdda.CDDAStatus == CDDASTATUS_SCANNING) + { + int64_t tmp_read_sec = read_sec; + + if(cdda.ScanMode & 1) + { + tmp_read_sec -= 24; + if(tmp_read_sec < cdda.scan_sec_end) + tmp_read_sec = cdda.scan_sec_end; + } + else + { + tmp_read_sec += 24; + if(tmp_read_sec > cdda.scan_sec_end) + tmp_read_sec = cdda.scan_sec_end; + } + read_sec = tmp_read_sec; + } + else + read_sec++; + } // End if(CDDAReadPos == 588) + + if(!(cdda.CDDAReadPos % 6)) + { + int subindex = cdda.CDDAReadPos / 6 - 2; + + if(subindex >= 0) + CDStuffSubchannels(cd.SubPWBuf[subindex], subindex); + else // The system-specific emulation code should handle what value the sync bytes are. + CDStuffSubchannels(0x00, subindex); + } + + // If the last valid sub-Q data decoded indicate that the corresponding sector is a data sector, don't output the + // current sector as audio. + if(!(cd.SubQBuf_Last[0] & 0x40) && cdda.PlayMode != PLAYMODE_SILENT) + { + cdda.sr[0] = cdda.CDDASectorBuffer[cdda.CDDAReadPos * 2 + cdda.OutPortChSelectCache[0]]; + cdda.sr[1] = cdda.CDDASectorBuffer[cdda.CDDAReadPos * 2 + cdda.OutPortChSelectCache[1]]; + } + +#if 0 + { + static int16 wv = 0x7FFF; //0x5000; + static unsigned counter = 0; + static double phase = 0; + static double phase_inc = 0; + static const double phase_inc_inc = 0.000003 / 2; + + cdda.sr[0] = 32767 * sin(phase); + cdda.sr[1] = 32767 * sin(phase); + + //cdda.sr[0] = wv; + //cdda.sr[1] = wv; + + if(counter == 0) + wv = -wv; + counter = (counter + 1) & 1; + phase += phase_inc; + phase_inc += phase_inc_inc; + } +#endif + + { + const unsigned obwp = cdda.OversamplePos >> 1; + cdda.OversampleBuffer[0][obwp] = cdda.OversampleBuffer[0][0x10 + obwp] = cdda.sr[0]; + cdda.OversampleBuffer[1][obwp] = cdda.OversampleBuffer[1][0x10 + obwp] = cdda.sr[1]; + } + + cdda.CDDAReadPos++; + } // End if(!(cdda.OversamplePos & 1)) + + { + const int16* f = OversampleFilter[cdda.OversamplePos & 1]; +#if defined(__SSE2__) + __m128i f0 = _mm_load_si128((__m128i *)&f[0]); + __m128i f1 = _mm_load_si128((__m128i *)&f[8]); +#endif + + for(unsigned lr = 0; lr < 2; lr++) + { + const int16* b = &cdda.OversampleBuffer[lr][((cdda.OversamplePos >> 1) + 1) & 0xF]; +#if defined(__SSE2__) + union + { + int32_t accum; + float accum_f; + //__m128i accum_m128; + }; + + { + __m128i b0; + __m128i b1; + __m128i sum; + + b0 = _mm_loadu_si128((__m128i *)&b[0]); + b1 = _mm_loadu_si128((__m128i *)&b[8]); + + sum = _mm_add_epi32(_mm_madd_epi16(f0, b0), _mm_madd_epi16(f1, b1)); + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, (1 << 0) | (0 << 2) | (3 << 4) | (2 << 6))); + _mm_store_ss(&accum_f, (__m128)sum); + //_mm_store_si128(&accum_m128, sum); + } +#else + int32_t accum = 0; + + for(unsigned i = 0; i < 0x10; i++) + accum += f[i] * b[i]; +#endif + // sum_abs * cdda_min = + // 59076 * -32768 = -1935802368 + // OPVC can have a maximum value of 65536. + // -1935802368 * 65536 = -126864743989248 + // + // -126864743989248 / 65536 = -1935802368 + sample_va[lr] = ((int64_t)accum * cdda.OutPortVolumeCache[lr]) >> 16; + // Output of this stage will be (approximate max ranges) -2147450880 through 2147385345. + } + } + + // + // This de-emphasis filter's frequency response isn't totally correct, but it's much better than nothing(and it's not like any known PCE CD/TG16 CD/PC-FX games + // utilize pre-emphasis anyway). + // + if(MDFN_UNLIKELY(cd.SubQBuf_Last[0] & 0x10)) + { + //puts("Deemph"); + for(unsigned lr = 0; lr < 2; lr++) + { + float inv = sample_va[lr] * 0.35971507338824012f; + + cdda.DeemphState[lr][1] = (cdda.DeemphState[lr][0] - 0.4316395666f * inv) + (0.7955522347f * cdda.DeemphState[lr][1]); + cdda.DeemphState[lr][0] = inv; + + sample_va[lr] = std::max(-2147483648.0, std::min(2147483647.0, cdda.DeemphState[lr][1])); + //printf("%u: %f, %d\n", lr, cdda.DeemphState[lr][1], sample_va[lr]); + } + } + + + if(HRBufs[0] && HRBufs[1]) + { + // + // FINAL_OUT_SHIFT should be 32 so we can take advantage of 32x32->64 multipliers on 32-bit CPUs. + // + #define FINAL_OUT_SHIFT 32 + #define MULT_SHIFT_ADJ (32 - (26 + (8 - CDDA_FILTER_NUMPHASES_SHIFT))) + + #if (((1 << (16 - CDDA_FILTER_NUMPHASES_SHIFT)) - 0) << MULT_SHIFT_ADJ) > 32767 + #error "COEFF MULT OVERFLOW" + #endif + + const int16 mult_a = ((1 << (16 - CDDA_FILTER_NUMPHASES_SHIFT)) - synthtime_phase_fract) << MULT_SHIFT_ADJ; + const int16 mult_b = synthtime_phase_fract << MULT_SHIFT_ADJ; + int32_t coeff[CDDA_FILTER_NUMCONVOLUTIONS]; + + //if(synthtime_phase_fract == 0) + // printf("%5d: %d %d\n", synthtime_phase_fract, mult_a, mult_b); + + for(unsigned c = 0; c < CDDA_FILTER_NUMCONVOLUTIONS; c++) + { + coeff[c] = (CDDA_Filter[1 + synthtime_phase_int + 0][c] * mult_a + + CDDA_Filter[1 + synthtime_phase_int + 1][c] * mult_b); + } + + int32_t* tb0 = &HRBufs[0][synthtime]; + int32_t* tb1 = &HRBufs[1][synthtime]; + + for(unsigned c = 0; c < CDDA_FILTER_NUMCONVOLUTIONS; c++) + { + tb0[c] += ((int64_t)coeff[c] * sample_va[0]) >> FINAL_OUT_SHIFT; + tb1[c] += ((int64_t)coeff[c] * sample_va[1]) >> FINAL_OUT_SHIFT; + } + #undef FINAL_OUT_SHIFT + #undef MULT_SHIFT_ADJ + } + + cdda.OversamplePos = (cdda.OversamplePos + 1) & 0x1F; + } // end while(cdda.CDDADiv <= 0) + } +} + +static INLINE void RunCDRead(uint32_t system_timestamp, int32_t run_time) +{ + if(CDReadTimer > 0) + { + CDReadTimer -= run_time; + + if(CDReadTimer <= 0) + { + if(din->CanWrite() < ((WhichSystem == SCSICD_PCFX) ? 2352 : 2048)) // +96 if we find out the PC-FX can read subchannel data along with raw data too. ;) + { + //printf("Carp: %d %d %d\n", din->CanWrite(), SectorCount, CDReadTimer); + //CDReadTimer = (cd.data_in_size - cd.data_in_pos) * 10; + + CDReadTimer += (uint64_t) 1 * 2048 * System_Clock / CD_DATA_TRANSFER_RATE; + + //CDReadTimer += (uint64_t) 1 * 128 * System_Clock / CD_DATA_TRANSFER_RATE; + } + else + { + uint8_t tmp_read_buf[2352 + 96]; + + if(TrayOpen) + { + din->Flush(); + cd.data_transfer_done = FALSE; + + CommandCCError(SENSEKEY_NOT_READY, NSE_TRAY_OPEN); + } + else if(!Cur_CDIF) + { + CommandCCError(SENSEKEY_NOT_READY, NSE_NO_DISC); + } + else if(SectorAddr >= toc.tracks[100].lba) + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_END_OF_VOLUME); + } + else if(!Cur_CDIF->ReadRawSector(tmp_read_buf, SectorAddr)) //, SectorAddr + SectorCount)) + { + cd.data_transfer_done = FALSE; + + CommandCCError(SENSEKEY_ILLEGAL_REQUEST); + } + else if(ValidateRawDataSector(tmp_read_buf, SectorAddr)) + { + memcpy(cd.SubPWBuf, tmp_read_buf + 2352, 96); + + if(tmp_read_buf[12 + 3] == 0x2) + din->Write(tmp_read_buf + 24, 2048); + else + din->Write(tmp_read_buf + 16, 2048); + + GenSubQFromSubPW(); + + CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_READY); + + SectorAddr++; + SectorCount--; + + if(CurrentPhase != PHASE_DATA_IN) + ChangePhase(PHASE_DATA_IN); + + if(SectorCount) + { + cd.data_transfer_done = FALSE; + CDReadTimer += (uint64_t) 1 * 2048 * System_Clock / CD_DATA_TRANSFER_RATE; + } + else + { + cd.data_transfer_done = TRUE; + } + } + } // end else to if(!Cur_CDIF->ReadSector + + } + } +} + + +uint32_t SCSICD_Run(scsicd_timestamp_t system_timestamp) +{ + int32_t run_time = system_timestamp - lastts; + + if(system_timestamp < lastts) + { + fprintf(stderr, "Meow: %d %d\n", system_timestamp, lastts); + assert(system_timestamp >= lastts); + } + + monotonic_timestamp += run_time; + + lastts = system_timestamp; + + RunCDRead(system_timestamp, run_time); + RunCDDA(system_timestamp, run_time); + + bool ResetNeeded = false; + + if(RST_signal && !cd.last_RST_signal) + ResetNeeded = true; + + cd.last_RST_signal = RST_signal; + + if(ResetNeeded) + { + //puts("RST"); + VirtualReset(); + } + else if(CurrentPhase == PHASE_BUS_FREE) + { + if(SEL_signal) + { + if(WhichSystem == SCSICD_PCFX) + { + //if(cd_bus.DB == 0x84) + { + ChangePhase(PHASE_COMMAND); + } + } + else // PCE + { + ChangePhase(PHASE_COMMAND); + } + } + } + else if(ATN_signal && !REQ_signal && !ACK_signal) + { + //printf("Yay: %d %d\n", REQ_signal, ACK_signal); + ChangePhase(PHASE_MESSAGE_OUT); + } + else switch(CurrentPhase) + { + case PHASE_COMMAND: + if(REQ_signal && ACK_signal) // Data bus is valid nowww + { + //printf("Command Phase Byte I->T: %02x, %d\n", cd_bus.DB, cd.command_buffer_pos); + cd.command_buffer[cd.command_buffer_pos++] = cd_bus.DB; + SetREQ(FALSE); + } + + if(!REQ_signal && !ACK_signal && cd.command_buffer_pos) // Received at least one byte, what should we do? + { + if(cd.command_buffer_pos == RequiredCDBLen[cd.command_buffer[0] >> 4]) + { + const SCSICH *cmd_info_ptr; + + if(WhichSystem == SCSICD_PCFX) + cmd_info_ptr = PCFXCommandDefs; + else + cmd_info_ptr = PCECommandDefs; + + while(cmd_info_ptr->pretty_name && cmd_info_ptr->cmd != cd.command_buffer[0]) + cmd_info_ptr++; + + if(SCSILog) + { + char log_buffer[1024]; + int lb_pos; + + log_buffer[0] = 0; + + lb_pos = trio_snprintf(log_buffer, 1024, "Command: %02x, %s%s ", cd.command_buffer[0], cmd_info_ptr->pretty_name ? cmd_info_ptr->pretty_name : "!!BAD COMMAND!!", + (cmd_info_ptr->flags & SCF_UNTESTED) ? "(UNTESTED)" : ""); + + for(int i = 0; i < RequiredCDBLen[cd.command_buffer[0] >> 4]; i++) + lb_pos += trio_snprintf(log_buffer + lb_pos, 1024 - lb_pos, "%02x ", cd.command_buffer[i]); + + SCSILog("SCSI", "%s", log_buffer); + //puts(log_buffer); + } + + + if(cmd_info_ptr->pretty_name == NULL) // Command not found! + { + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_COMMAND); + + //SCSIDBG("Bad Command: %02x\n", cd.command_buffer[0]); + + if(SCSILog) + SCSILog("SCSI", "Bad Command: %02x", cd.command_buffer[0]); + + cd.command_buffer_pos = 0; + } + else + { + if(cmd_info_ptr->flags & SCF_UNTESTED) + { + //SCSIDBG("Untested SCSI command: %02x, %s", cd.command_buffer[0], cmd_info_ptr->pretty_name); + } + + if(TrayOpen && (cmd_info_ptr->flags & SCF_REQUIRES_MEDIUM)) + { + CommandCCError(SENSEKEY_NOT_READY, NSE_TRAY_OPEN); + } + else if(!Cur_CDIF && (cmd_info_ptr->flags & SCF_REQUIRES_MEDIUM)) + { + CommandCCError(SENSEKEY_NOT_READY, NSE_NO_DISC); + } + else if(cd.DiscChanged && (cmd_info_ptr->flags & SCF_REQUIRES_MEDIUM)) + { + CommandCCError(SENSEKEY_UNIT_ATTENTION, NSE_DISC_CHANGED); + cd.DiscChanged = false; + } + else + { + bool prev_ps = (cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING); + + cmd_info_ptr->func(cd.command_buffer); + + bool new_ps = (cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING); + + // A bit kludgey, but ehhhh. + if(!prev_ps && new_ps) + { + memset(cdda.sr, 0, sizeof(cdda.sr)); + memset(cdda.OversampleBuffer, 0, sizeof(cdda.OversampleBuffer)); + memset(cdda.DeemphState, 0, sizeof(cdda.DeemphState)); + //printf("CLEAR BUFFERS LALALA\n"); + } + } + + cd.command_buffer_pos = 0; + } + } // end if(cd.command_buffer_pos == RequiredCDBLen[cd.command_buffer[0] >> 4]) + else // Otherwise, get more data for the command! + SetREQ(TRUE); + } + break; + + case PHASE_DATA_OUT: + if(REQ_signal && ACK_signal) // Data bus is valid nowww + { + //printf("DATAOUT-SCSIIN: %d %02x\n", cd.data_out_pos, cd_bus.DB); + cd.data_out[cd.data_out_pos++] = cd_bus.DB; + SetREQ(FALSE); + } + else if(!REQ_signal && !ACK_signal && cd.data_out_pos) + { + if(cd.data_out_pos == cd.data_out_want) + { + cd.data_out_pos = 0; + + if(cd.command_buffer[0] == 0x15) + FinishMODESELECT6(cd.data_out, cd.data_out_want); + else // Error out here? It shouldn't be reached: + SendStatusAndMessage(STATUS_GOOD, 0x00); + } + else + SetREQ(TRUE); + } + break; + + + case PHASE_MESSAGE_OUT: + //printf("%d %d, %02x\n", REQ_signal, ACK_signal, cd_bus.DB); + if(REQ_signal && ACK_signal) + { + SetREQ(FALSE); + + // ABORT message is 0x06, but the code isn't set up to be able to recover from a MESSAGE OUT phase back to the previous phase, so we treat any message as an ABORT. + // Real tests are needed on the PC-FX to determine its behavior. + // (Previously, ATN emulation was a bit broken, which resulted in the wrong data on the data bus in this code path in at least "Battle Heat", but it's fixed now and 0x06 is on the data bus). + //if(cd_bus.DB == 0x6) // ABORT message! + if(1) + { + //printf("[SCSICD] Abort Received(DB=0x%02x)\n", cd_bus.DB); + din->Flush(); + cd.data_out_pos = cd.data_out_want = 0; + + CDReadTimer = 0; + cdda.CDDAStatus = CDDASTATUS_STOPPED; + ChangePhase(PHASE_BUS_FREE); + } + //else + // printf("[SCSICD] Message to target: 0x%02x\n", cd_bus.DB); + } + break; + + + case PHASE_STATUS: + if(REQ_signal && ACK_signal) + { + SetREQ(FALSE); + cd.status_sent = TRUE; + } + + if(!REQ_signal && !ACK_signal && cd.status_sent) + { + // Status sent, so get ready to send the message! + cd.status_sent = FALSE; + cd_bus.DB = cd.message_pending; + + ChangePhase(PHASE_MESSAGE_IN); + } + break; + + case PHASE_DATA_IN: + if(!REQ_signal && !ACK_signal) + { + //puts("REQ and ACK false"); + if(din->CanRead() == 0) // aaand we're done! + { + CDIRQCallback(0x8000 | SCSICD_IRQ_DATA_TRANSFER_READY); + + if(cd.data_transfer_done) + { + SendStatusAndMessage(STATUS_GOOD, 0x00); + cd.data_transfer_done = FALSE; + CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE); + } + } + else + { + cd_bus.DB = din->ReadByte(); + SetREQ(TRUE); + } + } + if(REQ_signal && ACK_signal) + { + //puts("REQ and ACK true"); + SetREQ(FALSE); + } + break; + + case PHASE_MESSAGE_IN: + if(REQ_signal && ACK_signal) + { + SetREQ(FALSE); + cd.message_sent = TRUE; + } + + if(!REQ_signal && !ACK_signal && cd.message_sent) + { + cd.message_sent = FALSE; + ChangePhase(PHASE_BUS_FREE); + } + break; + } + + int32_t next_time = 0x7fffffff; + + if(CDReadTimer > 0 && CDReadTimer < next_time) + next_time = CDReadTimer; + + if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) + { + int32_t cdda_div_sexytime = (cdda.CDDADiv + (cdda.CDDADivAcc * (cdda.OversamplePos & 1)) + ((1 << 20) - 1)) >> 20; + if(cdda_div_sexytime > 0 && cdda_div_sexytime < next_time) + next_time = cdda_div_sexytime; + } + + assert(next_time >= 0); + + return(next_time); +} + +void SCSICD_SetLog(void (*logfunc)(const char *, const char *, ...)) +{ + SCSILog = logfunc; +} + +void SCSICD_SetTransferRate(uint32_t TransferRate) +{ + CD_DATA_TRANSFER_RATE = TransferRate; +} + +void SCSICD_Close(void) +{ + if(din) + { + delete din; + din = NULL; + } +} + +void SCSICD_Init(int type, int cdda_time_div, int32_t* left_hrbuf, int32_t* right_hrbuf, uint32_t TransferRate, uint32_t SystemClock, void (*IRQFunc)(int), void (*SSCFunc)(uint8_t, int)) +{ + Cur_CDIF = NULL; + TrayOpen = true; + + assert(SystemClock < 30000000); // 30 million, sanity check. + + monotonic_timestamp = 0; + lastts = 0; + + SCSILog = NULL; + + if(type == SCSICD_PCFX) + din = new SimpleFIFO(65536); //4096); + else + din = new SimpleFIFO(2048); //8192); //1024); /2048); + + WhichSystem = type; + + cdda.CDDADivAcc = (int64_t)System_Clock * (1024 * 1024) / 88200; + cdda.CDDADivAccVolFudge = 100; + cdda.CDDATimeDiv = cdda_time_div * (1 << (4 + 2)); + + cdda.CDDAVolume[0] = 65536; + cdda.CDDAVolume[1] = 65536; + + FixOPV(); + + HRBufs[0] = left_hrbuf; + HRBufs[1] = right_hrbuf; + + CD_DATA_TRANSFER_RATE = TransferRate; + System_Clock = SystemClock; + CDIRQCallback = IRQFunc; + CDStuffSubchannels = SSCFunc; +} + +void SCSICD_SetCDDAVolume(double left, double right) +{ + cdda.CDDAVolume[0] = 65536 * left; + cdda.CDDAVolume[1] = 65536 * right; + + for(int i = 0; i < 2; i++) + { + if(cdda.CDDAVolume[i] > 65536) + { + printf("[SCSICD] Debug Warning: CD-DA volume %d too large: %d\n", i, cdda.CDDAVolume[i]); + cdda.CDDAVolume[i] = 65536; + } + } + + FixOPV(); +} + +void SCSICD_StateAction(StateMem* sm, const unsigned load, const bool data_only, const char *sname) +{ + SFORMAT StateRegs[] = + { + SFVARN(cd_bus.DB, "DB"), + SFVARN(cd_bus.signals, "Signals"), + SFVAR(CurrentPhase), + + SFVARN(cd.last_RST_signal, "last_RST"), + SFVARN(cd.message_pending, "message_pending"), + SFVARN(cd.status_sent, "status_sent"), + SFVARN(cd.message_sent, "message_sent"), + SFVARN(cd.key_pending, "key_pending"), + SFVARN(cd.asc_pending, "asc_pending"), + SFVARN(cd.ascq_pending, "ascq_pending"), + SFVARN(cd.fru_pending, "fru_pending"), + + SFARRAYN(cd.command_buffer, 256, "command_buffer"), + SFVARN(cd.command_buffer_pos, "command_buffer_pos"), + SFVARN(cd.command_size_left, "command_size_left"), + + // Don't save the FIFO's write position, it will be reconstructed from read_pos and in_count + SFARRAYN(&din->data[0], din->data.size(), "din_fifo"), + SFVARN(din->read_pos, "din_read_pos"), + SFVARN(din->in_count, "din_in_count"), + SFVARN(cd.data_transfer_done, "data_transfer_done"), + + SFARRAYN(cd.data_out, sizeof(cd.data_out), "data_out"), + SFVARN(cd.data_out_pos, "data_out_pos"), + SFVARN(cd.data_out_want, "data_out_want"), + + SFVARN(cd.DiscChanged, "DiscChanged"), + + SFVAR(cdda.PlayMode), + SFARRAY16(cdda.CDDASectorBuffer, 1176), + SFVAR(cdda.CDDAReadPos), + SFVAR(cdda.CDDAStatus), + SFVAR(cdda.CDDADiv), + SFVAR(read_sec_start), + SFVAR(read_sec), + SFVAR(read_sec_end), + + SFVAR(CDReadTimer), + SFVAR(SectorAddr), + SFVAR(SectorCount), + + SFVAR(cdda.ScanMode), + SFVAR(cdda.scan_sec_end), + + SFVAR(cdda.OversamplePos), + SFARRAY16(&cdda.sr[0], sizeof(cdda.sr) / sizeof(cdda.sr[0])), + SFARRAY16(&cdda.OversampleBuffer[0][0], sizeof(cdda.OversampleBuffer) / sizeof(cdda.OversampleBuffer[0][0])), + + SFVAR(cdda.DeemphState[0][0]), + SFVAR(cdda.DeemphState[0][1]), + SFVAR(cdda.DeemphState[1][0]), + SFVAR(cdda.DeemphState[1][1]), + + SFARRAYN(&cd.SubQBuf[0][0], sizeof(cd.SubQBuf), "SubQBufs"), + SFARRAYN(cd.SubQBuf_Last, sizeof(cd.SubQBuf_Last), "SubQBufLast"), + SFARRAYN(cd.SubPWBuf, sizeof(cd.SubPWBuf), "SubPWBuf"), + + SFVAR(monotonic_timestamp), + SFVAR(pce_lastsapsp_timestamp), + + // + // + // + SFARRAY(ModePages[0].current_value, ModePages[0].param_length), + SFARRAY(ModePages[1].current_value, ModePages[1].param_length), + SFARRAY(ModePages[2].current_value, ModePages[2].param_length), + SFARRAY(ModePages[3].current_value, ModePages[3].param_length), + SFARRAY(ModePages[4].current_value, ModePages[4].param_length), + SFEND + }; + + MDFNSS_StateAction(sm, load, data_only, StateRegs, sname); + + if(load) + { + din->in_count &= din->size - 1; + din->read_pos &= din->size - 1; + din->write_pos = (din->read_pos + din->in_count) & (din->size - 1); + //printf("%d %d %d\n", din->in_count, din->read_pos, din->write_pos); + + if(load < 0x0935) + cdda.CDDADiv /= 2; + + if(cdda.CDDADiv <= 0) + cdda.CDDADiv = 1; + + cdda.OversamplePos &= 0x1F; + + for(int i = 0; i < NumModePages; i++) + UpdateMPCacheP(&ModePages[i]); + } +} diff --git a/mednafen/cdrom/scsicd.h b/mednafen/cdrom/scsicd.h new file mode 100644 index 0000000..0ce0287 --- /dev/null +++ b/mednafen/cdrom/scsicd.h @@ -0,0 +1,99 @@ +#ifndef __PCFX_SCSICD_H +#define __PCFX_SCSICD_H + +typedef int32_t scsicd_timestamp_t; + +typedef struct +{ + // Data bus(FIXME: we should have a variable for the target and the initiator, and OR them together to be truly accurate). + uint8_t DB; + + uint32_t signals; + + // Signals under our(the "target") control. + //bool BSY, MSG, CD, REQ, IO; + + // Signals under the control of the initiator(not us!) + //bool kingACK, kingRST, kingSEL, kingATN; +} scsicd_bus_t; + +extern scsicd_bus_t cd_bus; // Don't access this structure directly by name outside of scsicd.c, but use the macros below. + +// Signals under our(the "target") control. +#define SCSICD_IO_mask 0x001 +#define SCSICD_CD_mask 0x002 +#define SCSICD_MSG_mask 0x004 +#define SCSICD_REQ_mask 0x008 +#define SCSICD_BSY_mask 0x010 + +// Signals under the control of the initiator(not us!) +#define SCSICD_kingRST_mask 0x020 +#define SCSICD_kingACK_mask 0x040 +#define SCSICD_kingATN_mask 0x080 +#define SCSICD_kingSEL_mask 0x100 + +#define BSY_signal ((const bool)(cd_bus.signals & SCSICD_BSY_mask)) +#define ACK_signal ((const bool)(cd_bus.signals & SCSICD_kingACK_mask)) +#define RST_signal ((const bool)(cd_bus.signals & SCSICD_kingRST_mask)) +#define MSG_signal ((const bool)(cd_bus.signals & SCSICD_MSG_mask)) +#define SEL_signal ((const bool)(cd_bus.signals & SCSICD_kingSEL_mask)) +#define REQ_signal ((const bool)(cd_bus.signals & SCSICD_REQ_mask)) +#define IO_signal ((const bool)(cd_bus.signals & SCSICD_IO_mask)) +#define CD_signal ((const bool)(cd_bus.signals & SCSICD_CD_mask)) +#define ATN_signal ((const bool)(cd_bus.signals & SCSICD_kingATN_mask)) + +#define DB_signal ((const uint8_t)cd_bus.DB) + +#define SCSICD_GetDB() DB_signal +#define SCSICD_GetBSY() BSY_signal +#define SCSICD_GetIO() IO_signal +#define SCSICD_GetCD() CD_signal +#define SCSICD_GetMSG() MSG_signal +#define SCSICD_GetREQ() REQ_signal + +// Should we phase out getting these initiator-driven signals like this(the initiator really should keep track of them itself)? +#define SCSICD_GetACK() ACK_signal +#define SCSICD_GetRST() RST_signal +#define SCSICD_GetSEL() SEL_signal +#define SCSICD_GetATN() ATN_signal + +void SCSICD_Power(scsicd_timestamp_t system_timestamp); +void SCSICD_SetDB(uint8_t data); + +// These SCSICD_Set* functions are kind of misnomers, at least in comparison to the SCSICD_Get* functions... +// They will set/clear the bits corresponding to the KING's side of the bus. +void SCSICD_SetACK(bool set); +void SCSICD_SetSEL(bool set); +void SCSICD_SetRST(bool set); +void SCSICD_SetATN(bool set); + +uint32_t SCSICD_Run(scsicd_timestamp_t); +void SCSICD_ResetTS(uint32_t ts_base); + +enum +{ + SCSICD_PCE = 1, + SCSICD_PCFX +}; + +enum +{ + SCSICD_IRQ_DATA_TRANSFER_DONE = 1, + SCSICD_IRQ_DATA_TRANSFER_READY, + SCSICD_IRQ_MAGICAL_REQ, +}; + +void SCSICD_GetCDDAValues(int16 &left, int16 &right); + +void SCSICD_SetLog(void (*logfunc)(const char *, const char *, ...)); + +void SCSICD_Init(int type, int CDDATimeDiv, int32_t* left_hrbuf, int32_t* right_hrbuf, uint32_t TransferRate, uint32_t SystemClock, void (*IRQFunc)(int), void (*SSCFunc)(uint8_t, int)); +void SCSICD_Close(void); + +void SCSICD_SetTransferRate(uint32_t TransferRate); +void SCSICD_SetCDDAVolume(double left, double right); +void SCSICD_StateAction(StateMem *sm, const unsigned load, const bool data_only, const char *sname); + +void SCSICD_SetDisc(bool tray_open, CDIF *cdif, bool no_emu_side_effects = false); + +#endif diff --git a/mednafen/cdrom/scsicd_cdda_filter.inc b/mednafen/cdrom/scsicd_cdda_filter.inc new file mode 100644 index 0000000..bef5716 --- /dev/null +++ b/mednafen/cdrom/scsicd_cdda_filter.inc @@ -0,0 +1,69 @@ +// WARNING: Check resampling algorithm in scsicd.cpp for overflows if any value in here is negative. + + /* -1 */ { 1777, 12211, 27812, 27640, 11965, 1703, 9, 0 }, // 83117 83119.332059(diff = 2.332059) + /* 0 */ { 1702, 11965, 27640, 27811, 12211, 1777, 11, 0 }, // 83117 83121.547903(diff = 4.547903) + /* 1 */ { 1630, 11720, 27463, 27977, 12459, 1854, 14, 0 }, // 83117 83123.444392(diff = 6.444392) + /* 2 */ { 1560, 11478, 27282, 28139, 12708, 1933, 17, 0 }, // 83117 83125.036510(diff = 8.036510) + /* 3 */ { 1492, 11238, 27098, 28296, 12959, 2014, 20, 0 }, // 83117 83126.338722(diff = 9.338722) + /* 4 */ { 1427, 11000, 26909, 28448, 13212, 2098, 23, 0 }, // 83117 83127.364983(diff = 10.364983) + /* 5 */ { 1363, 10764, 26716, 28595, 13467, 2185, 27, 0 }, // 83117 83128.128743(diff = 11.128743) + /* 6 */ { 1302, 10530, 26519, 28738, 13723, 2274, 31, 0 }, // 83117 83128.642956(diff = 11.642956) + /* 7 */ { 1242, 10299, 26319, 28876, 13981, 2365, 35, 0 }, // 83117 83128.920096(diff = 11.920096) + /* 8 */ { 1185, 10071, 26115, 29009, 14239, 2459, 39, 0 }, // 83117 83128.972128(diff = 11.972128) + /* 9 */ { 1129, 9844, 25907, 29137, 14499, 2556, 45, 0 }, // 83117 83128.810568(diff = 11.810568) + /* 10 */ { 1076, 9620, 25695, 29260, 14761, 2655, 50, 0 }, // 83117 83128.446456(diff = 11.446456) + /* 11 */ { 1024, 9399, 25481, 29377, 15023, 2757, 56, 0 }, // 83117 83127.890369(diff = 10.890369) + /* 12 */ { 975, 9180, 25263, 29489, 15287, 2861, 62, 0 }, // 83117 83127.152431(diff = 10.152431) + /* 13 */ { 927, 8964, 25041, 29596, 15552, 2968, 69, 0 }, // 83117 83126.242312(diff = 9.242312) + /* 14 */ { 880, 8750, 24817, 29698, 15818, 3078, 76, 0 }, // 83117 83125.169251(diff = 8.169251) + /* 15 */ { 836, 8539, 24590, 29794, 16083, 3191, 84, 0 }, // 83117 83123.942037(diff = 6.942037) + /* 16 */ { 793, 8331, 24359, 29884, 16350, 3307, 93, 0 }, // 83117 83122.569034(diff = 5.569034) + /* 17 */ { 752, 8125, 24126, 29969, 16618, 3425, 102, 0 }, // 83117 83121.058175(diff = 4.058175) + /* 18 */ { 712, 7923, 23890, 30049, 16886, 3546, 111, 0 }, // 83117 83119.416975(diff = 2.416975) + /* 19 */ { 674, 7723, 23651, 30123, 17154, 3670, 122, 0 }, // 83117 83117.652622(diff = 0.652622) + /* 20 */ { 638, 7526, 23410, 30191, 17422, 3797, 133, 0 }, // 83117 83115.771622(diff = 1.228378) + /* 21 */ { 603, 7331, 23167, 30254, 17691, 3927, 144, 0 }, // 83117 83113.780335(diff = 3.219665) + /* 22 */ { 569, 7140, 22922, 30310, 17960, 4059, 157, 0 }, // 83117 83111.684630(diff = 5.315370) + /* 23 */ { 537, 6951, 22674, 30361, 18229, 4195, 170, 0 }, // 83117 83109.489972(diff = 7.510028) + /* 24 */ { 506, 6766, 22424, 30407, 18497, 4334, 183, 0 }, // 83117 83107.201429(diff = 9.798571) + /* 25 */ { 477, 6583, 22172, 30446, 18766, 4475, 198, 0 }, // 83117 83104.823668(diff = 12.176332) + /* 26 */ { 449, 6403, 21919, 30479, 19034, 4619, 214, 0 }, // 83117 83102.360963(diff = 14.639037) + /* 27 */ { 422, 6226, 21664, 30507, 19301, 4767, 230, 0 }, // 83117 83099.817193(diff = 17.182807) + /* 28 */ { 396, 6053, 21407, 30529, 19568, 4917, 247, 0 }, // 83117 83097.195820(diff = 19.804180) + /* 29 */ { 372, 5882, 21148, 30545, 19834, 5071, 265, 0 }, // 83117 83094.499993(diff = 22.500007) + /* 30 */ { 348, 5714, 20888, 30555, 20100, 5227, 285, 0 }, // 83117 83091.732389(diff = 25.267611) + /* 31 */ { 326, 5549, 20627, 30559, 20365, 5386, 305, 0 }, // 83117 83088.895321(diff = 28.104679) + /* 32 */ { 305, 5386, 20365, 30559, 20627, 5549, 326, 0 }, // 83117 83088.895321(diff = 28.104679) + /* 33 */ { 285, 5227, 20100, 30555, 20888, 5714, 348, 0 }, // 83117 83091.732389(diff = 25.267611) + /* 34 */ { 265, 5071, 19834, 30545, 21148, 5882, 372, 0 }, // 83117 83094.499993(diff = 22.500007) + /* 35 */ { 247, 4917, 19568, 30529, 21407, 6053, 396, 0 }, // 83117 83097.195820(diff = 19.804180) + /* 36 */ { 230, 4767, 19301, 30507, 21664, 6226, 422, 0 }, // 83117 83099.817193(diff = 17.182807) + /* 37 */ { 214, 4619, 19034, 30479, 21919, 6403, 449, 0 }, // 83117 83102.360963(diff = 14.639037) + /* 38 */ { 198, 4475, 18766, 30446, 22172, 6583, 477, 0 }, // 83117 83104.823668(diff = 12.176332) + /* 39 */ { 183, 4334, 18497, 30407, 22424, 6766, 506, 0 }, // 83117 83107.201429(diff = 9.798571) + /* 40 */ { 170, 4195, 18229, 30361, 22674, 6951, 537, 0 }, // 83117 83109.489972(diff = 7.510028) + /* 41 */ { 157, 4059, 17960, 30310, 22922, 7140, 569, 0 }, // 83117 83111.684630(diff = 5.315370) + /* 42 */ { 144, 3927, 17691, 30254, 23167, 7331, 603, 0 }, // 83117 83113.780335(diff = 3.219665) + /* 43 */ { 133, 3797, 17422, 30191, 23410, 7526, 638, 0 }, // 83117 83115.771622(diff = 1.228378) + /* 44 */ { 122, 3670, 17154, 30123, 23651, 7723, 674, 0 }, // 83117 83117.652622(diff = 0.652622) + /* 45 */ { 111, 3546, 16886, 30049, 23890, 7923, 712, 0 }, // 83117 83119.416975(diff = 2.416975) + /* 46 */ { 102, 3425, 16618, 29969, 24126, 8125, 752, 0 }, // 83117 83121.058175(diff = 4.058175) + /* 47 */ { 93, 3307, 16350, 29884, 24359, 8331, 793, 0 }, // 83117 83122.569034(diff = 5.569034) + /* 48 */ { 84, 3191, 16083, 29794, 24590, 8539, 836, 0 }, // 83117 83123.942037(diff = 6.942037) + /* 49 */ { 76, 3078, 15818, 29698, 24817, 8750, 880, 0 }, // 83117 83125.169251(diff = 8.169251) + /* 50 */ { 69, 2968, 15552, 29596, 25041, 8964, 927, 0 }, // 83117 83126.242312(diff = 9.242312) + /* 51 */ { 62, 2861, 15287, 29489, 25263, 9180, 975, 0 }, // 83117 83127.152431(diff = 10.152431) + /* 52 */ { 56, 2757, 15023, 29377, 25481, 9399, 1024, 0 }, // 83117 83127.890369(diff = 10.890369) + /* 53 */ { 50, 2655, 14761, 29260, 25695, 9620, 1076, 0 }, // 83117 83128.446456(diff = 11.446456) + /* 54 */ { 45, 2556, 14499, 29137, 25907, 9844, 1129, 0 }, // 83117 83128.810568(diff = 11.810568) + /* 55 */ { 39, 2459, 14239, 29009, 26115, 10071, 1185, 0 }, // 83117 83128.972128(diff = 11.972128) + /* 56 */ { 35, 2365, 13981, 28876, 26319, 10299, 1242, 0 }, // 83117 83128.920096(diff = 11.920096) + /* 57 */ { 31, 2274, 13723, 28738, 26519, 10530, 1302, 0 }, // 83117 83128.642956(diff = 11.642956) + /* 58 */ { 27, 2185, 13467, 28595, 26716, 10764, 1363, 0 }, // 83117 83128.128743(diff = 11.128743) + /* 59 */ { 23, 2098, 13212, 28448, 26909, 11000, 1427, 0 }, // 83117 83127.364983(diff = 10.364983) + /* 60 */ { 20, 2014, 12959, 28296, 27098, 11238, 1492, 0 }, // 83117 83126.338722(diff = 9.338722) + /* 61 */ { 17, 1933, 12708, 28139, 27282, 11478, 1560, 0 }, // 83117 83125.036510(diff = 8.036510) + /* 62 */ { 14, 1854, 12459, 27977, 27463, 11720, 1630, 0 }, // 83117 83123.444392(diff = 6.444392) + /* 63 */ { 11, 1777, 12211, 27811, 27640, 11965, 1702, 0 }, // 83117 83121.547903(diff = 4.547903) + /* 64 */ { 9, 1703, 11965, 27640, 27812, 12211, 1777, 0 }, // 83117 83119.332059(diff = 2.332059) + diff --git a/mednafen/git.h b/mednafen/git.h index 4f0fac0..a8c1b9d 100644 --- a/mednafen/git.h +++ b/mednafen/git.h @@ -4,6 +4,7 @@ #include #include #include +#include "../libretro.h" #include "video.h" #include "state.h" @@ -468,4 +469,6 @@ typedef struct int StateAction(StateMem *sm, int load, int data_only); +extern retro_log_printf_t log_cb; + #endif