From e80de55210335b7ed841433c18d80d9b7b928f28 Mon Sep 17 00:00:00 2001 From: zeromus Date: Thu, 25 Dec 2014 19:56:04 +0000 Subject: [PATCH] disc - add a project including mednafen's disc-loading code, for a/b testing bizhawk's disc loading on large disc sets, and add an example a/b test using it. --- .../BizHawk.Client.DiscoHawk.csproj | 1 + BizHawk.Client.DiscoHawk/DiscoHawk.cs | 2 + BizHawk.Client.DiscoHawk/MednadiscTester.cs | 121 + psx/mednadisc/FileStream.cpp | 349 ++ psx/mednadisc/FileStream.h | 89 + psx/mednadisc/Mednadisc.cpp | 38 + psx/mednadisc/Mednadisc.h | 9 + psx/mednadisc/MemoryStream.cpp | 330 ++ psx/mednadisc/MemoryStream.h | 77 + psx/mednadisc/Stream.cpp | 219 ++ psx/mednadisc/Stream.h | 223 ++ psx/mednadisc/audioreader.cpp | 608 +++ psx/mednadisc/audioreader.h | 43 + psx/mednadisc/bizhawk/mednadisc.filters | 248 ++ psx/mednadisc/bizhawk/mednadisc.sln | 24 + psx/mednadisc/bizhawk/mednadisc.vcxproj | 134 + .../bizhawk/mednadisc.vcxproj.filters | 104 + psx/mednadisc/cdrom/CDAccess.cpp | 58 + psx/mednadisc/cdrom/CDAccess.h | 32 + psx/mednadisc/cdrom/CDAccess_CCD.cpp | 435 +++ psx/mednadisc/cdrom/CDAccess_CCD.h | 50 + psx/mednadisc/cdrom/CDAccess_Image.cpp | 1241 +++++++ psx/mednadisc/cdrom/CDAccess_Image.h | 102 + psx/mednadisc/cdrom/CDAccess_Physical.cpp | 456 +++ psx/mednadisc/cdrom/CDAccess_Physical.h | 39 + psx/mednadisc/cdrom/CDUtility.cpp | 324 ++ psx/mednadisc/cdrom/CDUtility.h | 225 ++ psx/mednadisc/cdrom/Makefile.am.inc | 7 + psx/mednadisc/cdrom/SimpleFIFO.h | 140 + psx/mednadisc/cdrom/audioreader.cpp | 617 ++++ psx/mednadisc/cdrom/audioreader.h | 43 + psx/mednadisc/cdrom/audioreader_opus.cpp | 185 + psx/mednadisc/cdrom/audioreader_opus.h | 21 + psx/mednadisc/cdrom/cdromif.cpp | 434 +++ psx/mednadisc/cdrom/cdromif.h | 70 + psx/mednadisc/cdrom/crc32.cpp | 130 + psx/mednadisc/cdrom/dvdisaster.h | 173 + psx/mednadisc/cdrom/galois-inlines.h | 40 + psx/mednadisc/cdrom/galois.cpp | 156 + psx/mednadisc/cdrom/l-ec.cpp | 478 +++ psx/mednadisc/cdrom/lec.cpp | 691 ++++ psx/mednadisc/cdrom/lec.h | 77 + psx/mednadisc/cdrom/recover-raw.cpp | 203 ++ psx/mednadisc/cdrom/scsicd-pce-commands.inc | 259 ++ psx/mednadisc/cdrom/scsicd.cpp | 3246 +++++++++++++++++ psx/mednadisc/cdrom/scsicd.h | 99 + psx/mednadisc/cdrom/scsicd_cdda_filter.inc | 69 + psx/mednadisc/emuware/EW_state.cpp | 96 + psx/mednadisc/emuware/EW_state.h | 105 + psx/mednadisc/emuware/PACKED.h | 12 + psx/mednadisc/emuware/PACKED_END.h | 3 + psx/mednadisc/emuware/emuware.cpp | 3 + psx/mednadisc/emuware/emuware.h | 160 + psx/mednadisc/emuware/msvc/changelog.txt | 138 + psx/mednadisc/emuware/msvc/inttypes.h | 305 ++ psx/mednadisc/emuware/msvc/stdint.h | 247 ++ psx/mednadisc/endian.cpp | 151 + psx/mednadisc/endian.h | 296 ++ psx/mednadisc/error.cpp | 155 + psx/mednadisc/error.h | 75 + psx/mednadisc/general.cpp | 366 ++ psx/mednadisc/general.h | 52 + psx/mednadisc/math_ops.h | 87 + psx/mednadisc/string/ConvertUTF.cpp | 560 +++ psx/mednadisc/string/ConvertUTF.h | 149 + psx/mednadisc/string/Makefile.am.inc | 2 + psx/mednadisc/string/escape.cpp | 157 + psx/mednadisc/string/escape.h | 9 + psx/mednadisc/string/trim.cpp | 147 + psx/mednadisc/string/trim.h | 14 + 70 files changed, 16008 insertions(+) create mode 100644 BizHawk.Client.DiscoHawk/MednadiscTester.cs create mode 100644 psx/mednadisc/FileStream.cpp create mode 100644 psx/mednadisc/FileStream.h create mode 100644 psx/mednadisc/Mednadisc.cpp create mode 100644 psx/mednadisc/Mednadisc.h create mode 100644 psx/mednadisc/MemoryStream.cpp create mode 100644 psx/mednadisc/MemoryStream.h create mode 100644 psx/mednadisc/Stream.cpp create mode 100644 psx/mednadisc/Stream.h create mode 100644 psx/mednadisc/audioreader.cpp create mode 100644 psx/mednadisc/audioreader.h create mode 100644 psx/mednadisc/bizhawk/mednadisc.filters create mode 100644 psx/mednadisc/bizhawk/mednadisc.sln create mode 100644 psx/mednadisc/bizhawk/mednadisc.vcxproj create mode 100644 psx/mednadisc/bizhawk/mednadisc.vcxproj.filters create mode 100644 psx/mednadisc/cdrom/CDAccess.cpp create mode 100644 psx/mednadisc/cdrom/CDAccess.h create mode 100644 psx/mednadisc/cdrom/CDAccess_CCD.cpp create mode 100644 psx/mednadisc/cdrom/CDAccess_CCD.h create mode 100644 psx/mednadisc/cdrom/CDAccess_Image.cpp create mode 100644 psx/mednadisc/cdrom/CDAccess_Image.h create mode 100644 psx/mednadisc/cdrom/CDAccess_Physical.cpp create mode 100644 psx/mednadisc/cdrom/CDAccess_Physical.h create mode 100644 psx/mednadisc/cdrom/CDUtility.cpp create mode 100644 psx/mednadisc/cdrom/CDUtility.h create mode 100644 psx/mednadisc/cdrom/Makefile.am.inc create mode 100644 psx/mednadisc/cdrom/SimpleFIFO.h create mode 100644 psx/mednadisc/cdrom/audioreader.cpp create mode 100644 psx/mednadisc/cdrom/audioreader.h create mode 100644 psx/mednadisc/cdrom/audioreader_opus.cpp create mode 100644 psx/mednadisc/cdrom/audioreader_opus.h create mode 100644 psx/mednadisc/cdrom/cdromif.cpp create mode 100644 psx/mednadisc/cdrom/cdromif.h create mode 100644 psx/mednadisc/cdrom/crc32.cpp create mode 100644 psx/mednadisc/cdrom/dvdisaster.h create mode 100644 psx/mednadisc/cdrom/galois-inlines.h create mode 100644 psx/mednadisc/cdrom/galois.cpp create mode 100644 psx/mednadisc/cdrom/l-ec.cpp create mode 100644 psx/mednadisc/cdrom/lec.cpp create mode 100644 psx/mednadisc/cdrom/lec.h create mode 100644 psx/mednadisc/cdrom/recover-raw.cpp create mode 100644 psx/mednadisc/cdrom/scsicd-pce-commands.inc create mode 100644 psx/mednadisc/cdrom/scsicd.cpp create mode 100644 psx/mednadisc/cdrom/scsicd.h create mode 100644 psx/mednadisc/cdrom/scsicd_cdda_filter.inc create mode 100644 psx/mednadisc/emuware/EW_state.cpp create mode 100644 psx/mednadisc/emuware/EW_state.h create mode 100644 psx/mednadisc/emuware/PACKED.h create mode 100644 psx/mednadisc/emuware/PACKED_END.h create mode 100644 psx/mednadisc/emuware/emuware.cpp create mode 100644 psx/mednadisc/emuware/emuware.h create mode 100644 psx/mednadisc/emuware/msvc/changelog.txt create mode 100644 psx/mednadisc/emuware/msvc/inttypes.h create mode 100644 psx/mednadisc/emuware/msvc/stdint.h create mode 100644 psx/mednadisc/endian.cpp create mode 100644 psx/mednadisc/endian.h create mode 100644 psx/mednadisc/error.cpp create mode 100644 psx/mednadisc/error.h create mode 100644 psx/mednadisc/general.cpp create mode 100644 psx/mednadisc/general.h create mode 100644 psx/mednadisc/math_ops.h create mode 100644 psx/mednadisc/string/ConvertUTF.cpp create mode 100644 psx/mednadisc/string/ConvertUTF.h create mode 100644 psx/mednadisc/string/Makefile.am.inc create mode 100644 psx/mednadisc/string/escape.cpp create mode 100644 psx/mednadisc/string/escape.h create mode 100644 psx/mednadisc/string/trim.cpp create mode 100644 psx/mednadisc/string/trim.h diff --git a/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj b/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj index ccd3e21065..47f88a771b 100644 --- a/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj +++ b/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj @@ -100,6 +100,7 @@ MainDiscoForm.cs + Form diff --git a/BizHawk.Client.DiscoHawk/DiscoHawk.cs b/BizHawk.Client.DiscoHawk/DiscoHawk.cs index 67996cb323..51dd78e8b5 100644 --- a/BizHawk.Client.DiscoHawk/DiscoHawk.cs +++ b/BizHawk.Client.DiscoHawk/DiscoHawk.cs @@ -201,6 +201,8 @@ namespace BizHawk.Client.DiscoHawk else { //test stuff... + MednadiscTester tester = new MednadiscTester(); + tester.TestDirectory(@"c:\isos\psx"); } } } diff --git a/BizHawk.Client.DiscoHawk/MednadiscTester.cs b/BizHawk.Client.DiscoHawk/MednadiscTester.cs new file mode 100644 index 0000000000..fb224be89e --- /dev/null +++ b/BizHawk.Client.DiscoHawk/MednadiscTester.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +using BizHawk.Emulation.DiscSystem; + +class MednadiscTester +{ + [DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr mednadisc_LoadCD(string path); + + [DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int mednadisc_ReadSector(IntPtr disc, int lba, byte[] buf2448); + + [DllImport("mednadisc.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void mednadisc_CloseCD(IntPtr disc); + + + public class QuickSubcodeReader + { + public QuickSubcodeReader(byte[] buffer) + { + this.buffer = buffer; + } + + public void ReadLBA_SubchannelQ(int offset, ref SubchannelQ sq) + { + sq.q_status = buffer[offset + 0]; + sq.q_tno = buffer[offset + 1]; + sq.q_index = buffer[offset + 2]; + sq.min.BCDValue = buffer[offset + 3]; + sq.sec.BCDValue = buffer[offset + 4]; + sq.frame.BCDValue = buffer[offset + 5]; + //nothing in byte[6] + sq.ap_min.BCDValue = buffer[offset + 7]; + sq.ap_sec.BCDValue = buffer[offset + 8]; + sq.ap_frame.BCDValue = buffer[offset + 9]; + + //CRC is stored inverted and big endian.. so... do the opposite + byte hibyte = (byte)(~buffer[offset + 10]); + byte lobyte = (byte)(~buffer[offset + 11]); + sq.q_crc = (ushort)((hibyte << 8) | lobyte); + } + + byte[] buffer; + } + + public void TestDirectory(string dpTarget) + { + foreach (var fi in new DirectoryInfo(dpTarget).GetFiles()) + { + if (fi.Extension.ToLower() == ".cue") { } + else if (fi.Extension.ToLower() == ".ccd") { } + else continue; + + NewTest(fi.FullName); + } + } + + static bool NewTest(string path) + { + bool ret = false; + + Disc disc; + if (Path.GetExtension(path).ToLower() == ".cue") disc = Disc.FromCuePath(path, new CueBinPrefs()); + else disc = Disc.FromCCDPath(path); + IntPtr mednadisc = mednadisc_LoadCD(path); + + //TODO - test leadout a bit, or determine length some superior way + //TODO - check length against mednadisc + + int nSectors = (int)(disc.Structure.BinarySize / 2352) - 150; + var subbuf = new byte[96]; + var discbuf = new byte[2352 + 96]; + var monkeybuf = new byte[2352 + 96]; + var disc_qbuf = new byte[96]; + var monkey_qbuf = new byte[96]; + + for (int i = 0; i < nSectors; i++) + { + mednadisc_ReadSector(mednadisc, i, monkeybuf); + disc.ReadLBA_2352(i, discbuf, 0); + disc.ReadLBA_SectorEntry(i).SubcodeSector.ReadSubcodeDeinterleaved(subbuf, 0); + SubcodeUtils.Interleave(subbuf, 0, discbuf, 2352); + //remove P + for (int q = 2352; q < 2352 + 96; q++) + { + discbuf[q] &= 0x7F; + monkeybuf[q] &= 0x7F; + } + for (int q = 0; q < 2352 + 96; q++) + { + if (discbuf[q] != monkeybuf[q]) + { + Console.WriteLine("MISMATCH: " + Path.GetFileName(path)); + + //decode Q subchannels for manual investigation + SubcodeUtils.Deinterleave(discbuf, 2352, disc_qbuf, 0); + var asr = new QuickSubcodeReader(disc_qbuf); + SubchannelQ disc_q = new SubchannelQ(); + asr.ReadLBA_SubchannelQ(12, ref disc_q); + + SubcodeUtils.Deinterleave(monkeybuf, 2352, monkey_qbuf, 0); + asr = new QuickSubcodeReader(monkey_qbuf); + SubchannelQ monkey_q = new SubchannelQ(); + asr.ReadLBA_SubchannelQ(12, ref monkey_q); + + goto END; + } + } + } + + ret = true; + + END: + disc.Dispose(); + mednadisc_CloseCD(mednadisc); + + return ret; + } +} \ No newline at end of file diff --git a/psx/mednadisc/FileStream.cpp b/psx/mednadisc/FileStream.cpp new file mode 100644 index 0000000000..61b5d5ca32 --- /dev/null +++ b/psx/mednadisc/FileStream.cpp @@ -0,0 +1,349 @@ +/* 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 "emuware/emuware.h" +#include "FileStream.h" + +#include +#include +#include +#include +//#include +#include +#include + +//work around gettext +#define _(X) X + +#ifdef _MSC_VER +#include +#include +// These are not defined in MSVC version of stat.h +#define S_IWUSR S_IWRITE +#define S_IRUSR S_IREAD +#endif + +#ifdef HAVE_MMAP +#include +#include +#include +#include +#endif + +// Some really bad preprocessor abuse follows to handle platforms that don't have fseeko and ftello...and of course +// for largefile support on Windows: + +#ifndef HAVE_FSEEKO + #define fseeko fseek +#endif + +#ifndef HAVE_FTELLO + #define ftello ftell +#endif + +#define STRUCT_STAT struct stat + +#if SIZEOF_OFF_T == 4 + + #ifdef HAVE_FOPEN64 + #define fopen fopen64 + #endif + + #ifdef HAVE_FTELLO64 + #undef ftello + #define ftello ftello64 + #endif + + #ifdef HAVE_FSEEKO64 + #undef fseeko + #define fseeko fseeko64 + #endif + + #ifdef HAVE_FSTAT64 + #define fstat fstat64 + #define stat stat64 + #undef STRUCT_STAT + #define STRUCT_STAT struct stat64 + #endif + + #ifdef HAVE_FTRUNCATE64 + #define ftruncate ftruncate64 + #endif +#endif + +FileStream::FileStream(const std::string& path, const int mode) : OpenedMode(mode), mapping(NULL), mapping_size(0) +{ + path_save = path; + + if(mode == MODE_READ) + fp = fopen(path.c_str(), "rb"); + else if(mode == MODE_WRITE) + fp = fopen(path.c_str(), "wb"); + else if(mode == MODE_WRITE_SAFE || mode == MODE_WRITE_INPLACE) // SO ANNOYING + { + int open_flags = O_WRONLY | O_CREAT; + + if(mode == MODE_WRITE_SAFE) + open_flags |= O_EXCL; + + #ifdef O_BINARY + open_flags |= O_BINARY; + #elif defined(_O_BINARY) + open_flags |= _O_BINARY; + #endif + + #if defined(S_IRGRP) && defined(S_IROTH) + int tmpfd = open(path.c_str(), open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + #else + int tmpfd = open(path.c_str(), open_flags, S_IRUSR | S_IWUSR); + #endif + if(tmpfd == -1) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error opening file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } + fp = fdopen(tmpfd, "wb"); + } + else + abort(); + + if(!fp) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error opening file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } +} + +FileStream::~FileStream() +{ + try + { + close(); + } + catch(std::exception &e) + { + printf(e.what()); + } +} + +uint64 FileStream::attributes(void) +{ + uint64 ret = ATTRIBUTE_SEEKABLE; + + switch(OpenedMode) + { + case MODE_READ: + ret |= ATTRIBUTE_READABLE; + break; + + case MODE_WRITE_INPLACE: + case MODE_WRITE_SAFE: + case MODE_WRITE: + ret |= ATTRIBUTE_WRITEABLE; + break; + } + + return ret; +} + +uint8 *FileStream::map(void) noexcept +{ + if(!mapping) + { +#ifdef HAVE_MMAP + uint64 length = size(); + int prot = 0; + int flags = 0; + void* tptr; + + if(OpenedMode == MODE_READ) + { + prot |= PROT_READ; // | PROT_EXEC; + flags |= MAP_PRIVATE; + } + else + { + prot |= PROT_WRITE; + prot |= MAP_SHARED; + } + + if(length > SIZE_MAX) + return(NULL); + + tptr = mmap(NULL, length, prot, flags, fileno(fp), 0); + if(tptr != (void*)-1) + { + mapping = tptr; + mapping_size = length; + + #ifdef HAVE_MADVISE + // Should probably make this controllable via flag or somesuch. + madvise(mapping, mapping_size, MADV_SEQUENTIAL | MADV_WILLNEED); + #endif + } +#endif + } + + return((uint8*)mapping); +} + +uint64 FileStream::map_size(void) noexcept +{ + return mapping_size; +} + +void FileStream::unmap(void) noexcept +{ + if(mapping) + { +#ifdef HAVE_MMAP + munmap(mapping, mapping_size); +#endif + mapping = NULL; + mapping_size = 0; + } +} + + +uint64 FileStream::read(void *data, uint64 count, bool error_on_eos) +{ + uint64 read_count; + + clearerr(fp); + + read_count = fread(data, 1, count, fp); + + if(read_count != count) + { + ErrnoHolder ene(errno); + + if(ferror(fp)) + throw(MDFN_Error(ene.Errno(), _("Error reading from opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + + if(error_on_eos) + throw(MDFN_Error(0, _("Error reading from opened file \"%s\": %s"), path_save.c_str(), _("Unexpected EOF"))); + } + + return(read_count); +} + +void FileStream::write(const void *data, uint64 count) +{ + if(fwrite(data, 1, count, fp) != count) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error writing to opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } +} + +void FileStream::truncate(uint64 length) +{ + //not needed by mednadisc + //if(fflush(fp) == EOF || ftruncate(fileno(fp), length) != 0) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error truncating opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } +} + +void FileStream::seek(int64 offset, int whence) +{ + if(fseeko(fp, offset, whence) == -1) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error seeking in opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } +} + +void FileStream::flush(void) +{ + if(fflush(fp) == EOF) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error flushing to opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } +} + +uint64 FileStream::tell(void) +{ + auto offset = ftello(fp); + + if(offset == -1) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error getting position in opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } + + return (std::make_unsigned::type)offset; +} + +uint64 FileStream::size(void) +{ + STRUCT_STAT buf; + + if((OpenedMode != MODE_READ && fflush(fp) == EOF) || fstat(fileno(fp), &buf) == -1) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error getting the size of opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } + + return (std::make_unsigned::type)buf.st_size; +} + +void FileStream::close(void) +{ + if(fp) + { + FILE *tmp = fp; + + unmap(); + fp = NULL; + + if(fclose(tmp) == EOF) + { + ErrnoHolder ene(errno); + + throw(MDFN_Error(ene.Errno(), _("Error closing opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } + } +} + +int FileStream::get_line(std::string &str) +{ + int c; + + str.clear(); + + while((c = get_char()) >= 0) + { + if(c == '\r' || c == '\n' || c == 0) + return(c); + + str.push_back(c); + } + + return(str.length() ? 256 : -1); +} + diff --git a/psx/mednadisc/FileStream.h b/psx/mednadisc/FileStream.h new file mode 100644 index 0000000000..3066eb6994 --- /dev/null +++ b/psx/mednadisc/FileStream.h @@ -0,0 +1,89 @@ +/* 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 + */ + +#ifndef __MDFN_FILESTREAM_H +#define __MDFN_FILESTREAM_H + +#include "Stream.h" +#include "error.h" + +#include +#include + +class FileStream : public Stream +{ + public: + + enum + { + MODE_READ = 0, + MODE_WRITE, + MODE_WRITE_SAFE, // Will throw an exception instead of overwriting an existing file. + MODE_WRITE_INPLACE, // Like MODE_WRITE, but won't truncate the file if it already exists. + }; + + FileStream(const std::string& path, const int mode); + virtual ~FileStream() override; + + virtual uint64 attributes(void) override; + + virtual uint8 *map(void) noexcept override; + virtual uint64 map_size(void) noexcept override; + virtual void unmap(void) noexcept override; + + virtual uint64 read(void *data, uint64 count, bool error_on_eos = true) override; + virtual void write(const void *data, uint64 count) override; + virtual void truncate(uint64 length) override; + virtual void seek(int64 offset, int whence) override; + virtual uint64 tell(void) override; + virtual uint64 size(void) override; + virtual void flush(void) override; + virtual void close(void) override; + + virtual int get_line(std::string &str) override; + + INLINE int get_char(void) + { + int ret; + + errno = 0; + ret = fgetc(fp); + + if(MDFN_UNLIKELY(errno != 0)) + { + ErrnoHolder ene(errno); + throw(MDFN_Error(ene.Errno(), ("Error reading from opened file \"%s\": %s"), path_save.c_str(), ene.StrError())); + } + return(ret); + } + + private: + FileStream & operator=(const FileStream &); // Assignment operator + FileStream(const FileStream &); // Copy constructor + //FileStream(FileStream &); // Copy constructor + + FILE *fp; + std::string path_save; + const int OpenedMode; + + void* mapping; + uint64 mapping_size; +}; + + + +#endif diff --git a/psx/mednadisc/Mednadisc.cpp b/psx/mednadisc/Mednadisc.cpp new file mode 100644 index 0000000000..ca5c39469d --- /dev/null +++ b/psx/mednadisc/Mednadisc.cpp @@ -0,0 +1,38 @@ +#include "emuware/emuware.h" + +#include "Mednadisc.h" + +#include "error.h" + +#include "cdrom/CDAccess.h" +#include "cdrom/CDUtility.h" +#include "cdrom/cdromif.h" +#include "cdrom/CDAccess_Image.h" + +EW_EXPORT void* mednadisc_LoadCD(const char* fname) +{ + CDAccess* disc = NULL; + try { + disc = cdaccess_open_image(fname,false); + } + catch(MDFN_Error &e) { + return NULL; + } + return disc; +} + +EW_EXPORT int32 mednadisc_ReadSector(CDAccess* disc, int lba, void* buf2448) +{ + try { + disc->Read_Raw_Sector((uint8*)buf2448,lba); + } + catch(MDFN_Error &e) { + return 0; + } + return 1; +} + +EW_EXPORT void mednadisc_CloseCD(CDAccess* disc) +{ + delete disc; +} diff --git a/psx/mednadisc/Mednadisc.h b/psx/mednadisc/Mednadisc.h new file mode 100644 index 0000000000..e82dd5c4eb --- /dev/null +++ b/psx/mednadisc/Mednadisc.h @@ -0,0 +1,9 @@ +#pragma once + +#include "emuware/emuware.h" + +class CDAccess; + +EW_EXPORT void* mednadisc_LoadCD(const char* fname); +EW_EXPORT int32 mednadisc_ReadSector(CDAccess* disc, int lba, void* buf2448); +EW_EXPORT void mednadisc_CloseCD(CDAccess* disc); \ No newline at end of file diff --git a/psx/mednadisc/MemoryStream.cpp b/psx/mednadisc/MemoryStream.cpp new file mode 100644 index 0000000000..4dd932f046 --- /dev/null +++ b/psx/mednadisc/MemoryStream.cpp @@ -0,0 +1,330 @@ +/* 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 "MemoryStream.h" +#include "math_ops.h" +#include "error.h" + +//work around gettext +#define _(X) X + +/* + TODO: Copy and assignment constructor fixes. + + Proper negative position behavior? +*/ + +MemoryStream::MemoryStream() : data_buffer(NULL), data_buffer_size(0), data_buffer_alloced(0), position(0) +{ + data_buffer_size = 0; + data_buffer_alloced = 64; + if(!(data_buffer = (uint8*)realloc(data_buffer, data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); +} + +MemoryStream::MemoryStream(uint64 alloc_hint, int alloc_hint_is_size) : data_buffer(NULL), data_buffer_size(0), data_buffer_alloced(0), position(0) +{ + if(alloc_hint_is_size != 0) + { + data_buffer_size = alloc_hint; + data_buffer_alloced = alloc_hint; + + if(alloc_hint > SIZE_MAX) + throw MDFN_Error(ErrnoHolder(ENOMEM)); + } + else + { + data_buffer_size = 0; + data_buffer_alloced = (alloc_hint > SIZE_MAX) ? SIZE_MAX : alloc_hint; + } + + if(!(data_buffer = (uint8*)realloc(data_buffer, data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + + if(alloc_hint_is_size > 0) + memset(data_buffer, 0, data_buffer_size); +} + +MemoryStream::MemoryStream(Stream *stream, uint64 size_limit) : data_buffer(NULL), data_buffer_size(0), data_buffer_alloced(0), position(0) +{ + try + { + if((position = stream->tell()) != 0) + stream->seek(0, SEEK_SET); + + void* tp; + data_buffer_size = data_buffer_alloced = stream->alloc_and_read(&tp, size_limit); + data_buffer = (uint8*)tp; + stream->close(); + } + catch(...) + { + if(data_buffer) + { + free(data_buffer); + data_buffer = NULL; + } + + delete stream; + throw; + } + delete stream; +} + +MemoryStream::MemoryStream(const MemoryStream &zs) +{ + data_buffer_size = zs.data_buffer_size; + data_buffer_alloced = zs.data_buffer_alloced; + if(!(data_buffer = (uint8*)malloc(data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + + memcpy(data_buffer, zs.data_buffer, data_buffer_size); + + position = zs.position; +} + +#if 0 +MemoryStream & MemoryStream::operator=(const MemoryStream &zs) +{ + if(this != &zs) + { + if(data_buffer) + { + free(data_buffer); + data_buffer = NULL; + } + + data_buffer_size = zs.data_buffer_size; + data_buffer_alloced = zs.data_buffer_alloced; + + if(!(data_buffer = (uint8*)malloc(data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + + memcpy(data_buffer, zs.data_buffer, data_buffer_size); + + position = zs.position; + } + return(*this); +} +#endif + +MemoryStream::~MemoryStream() +{ + if(data_buffer) + { + free(data_buffer); + data_buffer = NULL; + } +} + +uint64 MemoryStream::attributes(void) +{ + return (ATTRIBUTE_READABLE | ATTRIBUTE_WRITEABLE | ATTRIBUTE_SEEKABLE); +} + + +uint8 *MemoryStream::map(void) noexcept +{ + return data_buffer; +} + +uint64 MemoryStream::map_size(void) noexcept +{ + return data_buffer_size; +} + +void MemoryStream::unmap(void) noexcept +{ + +} + + +INLINE void MemoryStream::grow_if_necessary(uint64 new_required_size, uint64 hole_end) +{ + if(new_required_size > data_buffer_size) + { + const uint64 old_data_buffer_size = data_buffer_size; + + if(new_required_size > data_buffer_alloced) + { + uint64 new_required_alloced = round_up_pow2(new_required_size); + uint8 *new_data_buffer; + + // first condition will happen at new_required_size > (1ULL << 63) due to round_up_pow2() "wrapping". + // second condition can occur when running on a 32-bit system. + if(new_required_alloced < new_required_size || new_required_alloced > SIZE_MAX) + new_required_alloced = SIZE_MAX; + + // If constrained alloc size isn't enough, throw an out-of-memory/address-space type error. + if(new_required_alloced < new_required_size) + throw MDFN_Error(ErrnoHolder(ENOMEM)); + + if(!(new_data_buffer = (uint8*)realloc(data_buffer, new_required_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + + // + // Assign all in one go after the realloc() so we don't leave our object in an inconsistent state if the realloc() fails. + // + data_buffer = new_data_buffer; + data_buffer_size = new_required_size; + data_buffer_alloced = new_required_alloced; + } + else + data_buffer_size = new_required_size; + + if(hole_end > old_data_buffer_size) + memset(data_buffer + old_data_buffer_size, 0, hole_end - old_data_buffer_size); + } +} + +void MemoryStream::shrink_to_fit(void) noexcept +{ + if(data_buffer_alloced > data_buffer_size) + { + uint8 *new_data_buffer; + + new_data_buffer = (uint8*)realloc(data_buffer, data_buffer_size); + + if(new_data_buffer != NULL) + { + data_buffer = new_data_buffer; + data_buffer_alloced = data_buffer_size; + } + } +} + +uint64 MemoryStream::read(void *data, uint64 count, bool error_on_eos) +{ + //printf("%llu %llu %llu\n", position, count, data_buffer_size); + + if(count > data_buffer_size) + { + if(error_on_eos) + throw MDFN_Error(0, _("Unexpected EOF")); + + count = data_buffer_size; + } + + if(position > (data_buffer_size - count)) + { + if(error_on_eos) + throw MDFN_Error(0, _("Unexpected EOF")); + + if(data_buffer_size > position) + count = data_buffer_size - position; + else + count = 0; + } + + memmove(data, &data_buffer[position], count); + position += count; + + return count; +} + +void MemoryStream::write(const void *data, uint64 count) +{ + uint64 nrs = position + count; + + if(nrs < position) + throw MDFN_Error(ErrnoHolder(EFBIG)); + + grow_if_necessary(nrs, position); + + memmove(&data_buffer[position], data, count); + position += count; +} + +// +// Don't add code to reduce the amount of memory allocated(when possible) without providing a +// per-stream setting to disable that behavior. +// +void MemoryStream::truncate(uint64 length) +{ + grow_if_necessary(length, length); + + data_buffer_size = length; +} + +void MemoryStream::seek(int64 offset, int whence) +{ + uint64 new_position; + + switch(whence) + { + default: + throw MDFN_Error(ErrnoHolder(EINVAL)); + break; + + case SEEK_SET: + new_position = offset; + break; + + case SEEK_CUR: + new_position = position + offset; + break; + + case SEEK_END: + new_position = data_buffer_size + offset; + break; + } + + if(new_position < 0) + throw MDFN_Error(ErrnoHolder(EINVAL)); + + position = new_position; +} + +uint64 MemoryStream::tell(void) +{ + return position; +} + +uint64 MemoryStream::size(void) +{ + return data_buffer_size; +} + +void MemoryStream::flush(void) +{ + +} + +void MemoryStream::close(void) +{ + +} + + +int MemoryStream::get_line(std::string &str) +{ + str.clear(); // or str.resize(0)?? + + while((uint64)position < data_buffer_size) + { + uint8 c = data_buffer[position++]; + + if(c == '\r' || c == '\n' || c == 0) + return(c); + + str.push_back(c); // Should be faster than str.append(1, c) + } + + return(str.length() ? 256 : -1); +} + diff --git a/psx/mednadisc/MemoryStream.h b/psx/mednadisc/MemoryStream.h new file mode 100644 index 0000000000..0b245482e5 --- /dev/null +++ b/psx/mednadisc/MemoryStream.h @@ -0,0 +1,77 @@ +/* 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 + */ + +/* + Notes: + For performance reasons(like in the state rewinding code), we should try to make sure map() + returns a pointer that is aligned to at least what malloc()/realloc() provides. + (And maybe forcefully align it to at least 16 bytes in the future) +*/ + +#ifndef __MDFN_MEMORYSTREAM_H +#define __MDFN_MEMORYSTREAM_H + +#include "Stream.h" + +class MemoryStream : public Stream +{ + public: + + MemoryStream(); + MemoryStream(uint64 alloc_hint, int alloc_hint_is_size = false); // Pass -1 instead of 1 for alloc_hint_is_size to skip initialization of the memory. + MemoryStream(Stream *stream, uint64 size_limit = ~(uint64)0); + // Will create a MemoryStream equivalent of the contents of "stream", and then "delete stream". + // Will only work if stream->tell() == 0, or if "stream" is seekable. + // stream will be deleted even if this constructor throws. + // + // Will throw an exception if the initial size() of the MemoryStream would be greater than size_limit(useful for when passing + // in GZFileStream streams). + + MemoryStream(const MemoryStream &zs); + MemoryStream & operator=(const MemoryStream &zs); + + virtual ~MemoryStream() override; + + virtual uint64 attributes(void) override; + + virtual uint8 *map(void) noexcept override; + virtual uint64 map_size(void) noexcept override; + virtual void unmap(void) noexcept override; + + virtual uint64 read(void *data, uint64 count, bool error_on_eos = true) override; + virtual void write(const void *data, uint64 count) override; + virtual void truncate(uint64 length) override; + virtual void seek(int64 offset, int whence) override; + virtual uint64 tell(void) override; + virtual uint64 size(void) override; + virtual void flush(void) override; + virtual void close(void) override; + + virtual int get_line(std::string &str) override; + + void shrink_to_fit(void) noexcept; // Minimizes alloced memory. + + private: + uint8 *data_buffer; + uint64 data_buffer_size; + uint64 data_buffer_alloced; + + uint64 position; + + void grow_if_necessary(uint64 new_required_size, uint64 hole_end); +}; +#endif diff --git a/psx/mednadisc/Stream.cpp b/psx/mednadisc/Stream.cpp new file mode 100644 index 0000000000..67389e86d5 --- /dev/null +++ b/psx/mednadisc/Stream.cpp @@ -0,0 +1,219 @@ +/* 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 "emuware/emuware.h" +#include "Stream.h" +#include "error.h" + +//work around gettext stuff +#define _(X) X + + +Stream::Stream() +{ + +} + +Stream::~Stream() +{ + +} + +uint64 Stream::read_discard(uint64 count) +{ + uint8 buf[1024]; + uint64 tmp; + uint64 ret = 0; + + do + { + tmp = read(buf, std::min(count, sizeof(buf)), false); + count -= tmp; + ret += tmp; + } while(tmp == sizeof(buf)); + + return ret; +} + +uint64 Stream::alloc_and_read(void** data_out, uint64 size_limit) +{ + uint8 *data_buffer = NULL; + uint64 data_buffer_size = 0; + uint64 data_buffer_alloced = 0; + + try + { + if(attributes() & ATTRIBUTE_SLOW_SIZE) + { + uint64 rti; + + data_buffer_size = 0; + data_buffer_alloced = 65536; + + if(!(data_buffer = (uint8*)realloc(data_buffer, data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + + while((rti = read(data_buffer + data_buffer_size, data_buffer_alloced - data_buffer_size, false)) > 0) + { + uint8* new_data_buffer; + + data_buffer_size += rti; + + if(data_buffer_size == data_buffer_alloced) + { + data_buffer_alloced <<= 1; + + if(data_buffer_alloced > size_limit) // So we can test against our size limit without going far far over it in temporary memory allocations. + data_buffer_alloced = size_limit + 1; + + if(data_buffer_size > size_limit) + throw MDFN_Error(0, _("Size limit of %llu bytes would be exceeded."), (unsigned long long)size_limit); + + if(!(new_data_buffer = (uint8 *)realloc(data_buffer, data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + data_buffer = new_data_buffer; + } + else // EOS + break; + } + + if(data_buffer_alloced > data_buffer_size) + { + uint8 *new_data_buffer; + + new_data_buffer = (uint8*)realloc(data_buffer, data_buffer_size); + + if(new_data_buffer != NULL) + { + data_buffer = new_data_buffer; + data_buffer_alloced = data_buffer_size; + } + } + } + else + { + data_buffer_size = size(); + data_buffer_alloced = data_buffer_size; + + if(data_buffer_size > size_limit) + throw MDFN_Error(0, _("Size limit of %llu bytes would be exceeded."), (unsigned long long)size_limit); + + if(data_buffer_alloced > SIZE_MAX) + throw MDFN_Error(ErrnoHolder(ENOMEM)); + + if(!(data_buffer = (uint8*)realloc(data_buffer, data_buffer_alloced))) + throw MDFN_Error(ErrnoHolder(errno)); + + read(data_buffer, data_buffer_size); + } + } + catch(...) + { + if(data_buffer) + { + free(data_buffer); + data_buffer = NULL; + } + throw; + } + + *data_out = data_buffer; + return data_buffer_size; +} + +uint8* Stream::map(void) noexcept +{ + return(NULL); +} + +uint64 Stream::map_size(void) noexcept +{ + return 0; +} + +void Stream::unmap(void) noexcept +{ + +} + +void Stream::put_line(const std::string& str) +{ + char l = '\n'; + + write(&str[0], str.size()); + write(&l, sizeof(l)); +} + + +void Stream::print_format(const char *format, ...) +{ + char *str = NULL; + int rc; + + va_list ap; + + int size = 128; + for(;;) { + va_list ap; + va_start(ap, format); + str = (char*)malloc(size); + size *= 2; + int ret = vsprintf(str, format, ap); + va_end(ap); + if(ret>=0) + break; + free(str); + } + + if(rc < 0) + throw MDFN_Error(0, "Error in trio_vasprintf()"); + else + { + try // Bleck + { + write(str, rc); + } + catch(...) + { + free(str); + throw; + } + free(str); + } +} + +int Stream::get_line(std::string &str) +{ + uint8 c; + + str.clear(); // or str.resize(0)?? + + while(read(&c, sizeof(c), false) > 0) + { + if(c == '\r' || c == '\n' || c == 0) + return(c); + + str.push_back(c); + } + + return(str.length() ? 256 : -1); +} + diff --git a/psx/mednadisc/Stream.h b/psx/mednadisc/Stream.h new file mode 100644 index 0000000000..3830f5c6e6 --- /dev/null +++ b/psx/mednadisc/Stream.h @@ -0,0 +1,223 @@ +#ifndef __MDFN_STREAM_H +#define __MDFN_STREAM_H + +// TODO?: BufferedStream, no virtual functions, yes inline functions, constructor takes a Stream* argument. + +#include "emuware/emuware.h" + +#include + +#include // For SEEK_* defines, which we will use in Stream out of FORCE OF HABIT. +#include + +#include + +class Stream +{ + public: + + Stream(); + virtual ~Stream(); + + enum + { + ATTRIBUTE_READABLE = 1U << 0, + ATTRIBUTE_WRITEABLE = 1U << 1, + ATTRIBUTE_SEEKABLE = 1U << 2, + ATTRIBUTE_SLOW_SEEK = 1U << 3, + ATTRIBUTE_SLOW_SIZE = 1U << 4 + }; + virtual uint64 attributes(void) = 0; + + virtual uint8 *map(void) noexcept; + // Map the entirety of the stream data into the address space of the process, if possible, and return a pointer. + // (the returned pointer must be cached, and returned on any subsequent calls to map() without an unmap() + // in-between, to facilitate a sort of "feature-testing", to determine if an alternative like "MemoryStream" + // should be used). + // + // If the mapping fails for whatever reason, return NULL rather than throwing an exception. + // + // For code using this functionality, ensure usage of map_size() instead of size(), unless you're only using a specific derived + // class like MemoryStream() where the value returned by size() won't change unexpectedly due to outside factors. + + virtual uint64 map_size(void) noexcept; + // The size of the memory mapping area, point to which returned by map(). + // + // Returns 0 on supported, or if no mapping currently exists. + + virtual void unmap(void) noexcept; + // Unmap the stream data from the address space. (Possibly invalidating the pointer returned from map()). + // (must automatically be called, if necessary, from the destructor). + // + // If the data can't be "unmapped" as such because it was never mmap()'d or similar in the first place(such as with MemoryStream), + // then this will be a nop. + + virtual uint64 read(void *data, uint64 count, bool error_on_eos = true) = 0; + virtual void write(const void *data, uint64 count) = 0; + + virtual void truncate(uint64 length) = 0; // Should have ftruncate()-like semantics; but avoid using it to extend files. + + virtual void seek(int64 offset, int whence = SEEK_SET) = 0; + inline void rewind(void) + { + seek(0, SEEK_SET); + } + virtual uint64 tell(void) = 0; + virtual uint64 size(void) = 0; // May implicitly call flush() if the stream is writeable. + virtual void flush(void) = 0; + virtual void close(void) = 0; // Flushes(in the case of writeable streams) and closes the stream. + // Necessary since this operation can fail(running out of disk space, for instance), + // and throw an exception in the destructor would be a Bad Idea(TM). + // + // Manually calling this function isn't strictly necessary, but recommended when the + // stream is writeable; it will be called automatically from the destructor, with any + // exceptions thrown caught and logged. + + // + // Utility functions(TODO): + // + INLINE uint8 get_u8(void) + { + uint8 ret; + + read(&ret, sizeof(ret)); + + return ret; + } + + INLINE void put_u8(uint8 c) + { + write(&c, sizeof(c)); + } + + + template + INLINE T get_NE(void) + { + T ret; + + read(&ret, sizeof(ret)); + + return ret; + } + + + template + INLINE T get_RE(void) + { + uint8 tmp[sizeof(T)]; + union + { + T ret; + uint8 ret_u8[sizeof(T)]; + }; + + read(tmp, sizeof(tmp)); + + for(unsigned i = 0; i < sizeof(T); i++) + ret_u8[i] = tmp[sizeof(T) - 1 - i]; + + return ret; + } + + template + INLINE void put_NE(T c) + { + write(&c, sizeof(c)); + } + + template + INLINE void put_RE(T c) + { + uint8 tmp[sizeof(T)]; + + for(unsigned i = 0; i < sizeof(T); i++) + tmp[i] = ((uint8 *)&c)[sizeof(T) - 1 - i]; + + write(tmp, sizeof(tmp)); + } + + template + INLINE T get_LE(void) + { + #ifdef LSB_FIRST + return get_NE(); + #else + return get_RE(); + #endif + } + + template + INLINE void put_LE(T c) + { + #ifdef LSB_FIRST + return put_NE(c); + #else + return put_RE(c); + #endif + } + + template + INLINE T get_BE(void) + { + #ifndef LSB_FIRST + return get_NE(); + #else + return get_RE(); + #endif + } + + template + INLINE void put_BE(T c) + { + #ifndef LSB_FIRST + return put_NE(c); + #else + return put_RE(c); + #endif + } + + INLINE void put_string(const char* str) + { + write(str, strlen(str)); + } + + // Reads a line into "str", overwriting its contents; returns the line-end char('\n' or '\r' or '\0'), or 256 on EOF and + // data has been read into "str", and -1 on EOF when no data has been read into "str". + // The line-end char won't be added to "str". + // It's up to the caller to handle extraneous empty lines caused by DOS-format text lines(\r\n). + // ("str" is passed by reference for the possibility of improved performance by reusing alloced memory for the std::string, though part + // of it would be up to the STL implementation). + // Implemented as virtual so that a higher-performance version can be implemented if possible(IE with MemoryStream) + virtual int get_line(std::string &str); + + virtual void put_line(const std::string& str); + + virtual void print_format(const char *format, ...) MDFN_FORMATSTR(gnu_printf, 2, 3); + +#if 0 + int scanf(const char *format, ...) MDFN_FORMATSTR(gnu_scanf, 2, 3); + void put_string(const char *str); + void put_string(const std::string &str); +#endif + + // + // Read until end-of-stream(or count), discarding any read data, and returns the amount of data "read". + // (Useful for detecting and printing warnings about extra garbage data without needing to call size(), + // which can be problematic for some types of Streams). + uint64 read_discard(uint64 count = ~(uint64)0); + + // + // Reads stream starting at the current stream position(as returned by tell()), into memory allocated with malloc() and realloc(), and + // sets *data_out to a pointer to the memory(which the caller will need to free() at some point). + // + // *data_out is only an output. + // + // If size_limit is/will be exceeded, an exception will be thrown, and *data_out will not be written to. + // + // Will return the amount of data read(and the size of the alloced memory). + // + uint64 alloc_and_read(void** data_out, uint64 size_limit = ~(uint64)0); +}; + +#endif diff --git a/psx/mednadisc/audioreader.cpp b/psx/mednadisc/audioreader.cpp new file mode 100644 index 0000000000..20d1fe23ca --- /dev/null +++ b/psx/mednadisc/audioreader.cpp @@ -0,0 +1,608 @@ +/* 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 +#include + +#include "../tremor/ivorbisfile.h" +#include "../mpcdec/mpcdec.h" + +#ifdef HAVE_LIBSNDFILE +#include +#endif + +#ifdef HAVE_OPUSFILE +#include "audioreader_opus.h" +#endif + +#include +#include +#include + +#include "../general.h" +#include "../endian.h" + +AudioReader::AudioReader() : LastReadPos(0) +{ + +} + +AudioReader::~AudioReader() +{ + +} + +int64 AudioReader::Read_(int16 *buffer, int64 frames) +{ + abort(); + return(false); +} + +bool AudioReader::Seek_(int64 frame_offset) +{ + abort(); + return(false); +} + +int64 AudioReader::FrameCount(void) +{ + abort(); + return(0); +} + +/* +** +** +** +** +** +** +** +** +** +*/ + +class OggVorbisReader : public AudioReader +{ + public: + OggVorbisReader(Stream *fp); + ~OggVorbisReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + 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); + + try + { + return fw->read(ptr, size * nmemb, false) / size; + } + catch(...) + { + return(0); + } +} + +static int iov_seek_func(void *user_data, ogg_int64_t offset, int whence) +{ + Stream *fw = (Stream*)user_data; + + try + { + fw->seek(offset, whence); + return(0); + } + catch(...) + { + return(-1); + } +} + +static int iov_close_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + fw->close(); + return(0); + } + catch(...) + { + return EOF; + } +} + +static long iov_tell_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->tell(); + } + catch(...) + { + return(-1); + } +} + +OggVorbisReader::OggVorbisReader(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; + + fp->seek(0, SEEK_SET); + if(ov_open_callbacks(fp, &ovfile, NULL, 0, cb)) + throw(0); +} + +OggVorbisReader::~OggVorbisReader() +{ + ov_clear(&ovfile); +} + +int64 OggVorbisReader::Read_(int16 *buffer, int64 frames) +{ + uint8 *tw_buf = (uint8 *)buffer; + int cursection = 0; + long toread = frames * sizeof(int16) * 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) / 2); +} + +bool OggVorbisReader::Seek_(int64 frame_offset) +{ + ov_pcm_seek(&ovfile, frame_offset); + return(true); +} + +int64 OggVorbisReader::FrameCount(void) +{ + return(ov_pcm_total(&ovfile, -1)); +} + +class MPCReader : public AudioReader +{ + public: + MPCReader(Stream *fp); + ~MPCReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + private: + mpc_reader reader; + mpc_demux *demux; + mpc_streaminfo si; + + MPC_SAMPLE_FORMAT MPCBuffer[MPC_DECODER_BUFFER_LENGTH]; + + uint32 MPCBufferIn; + uint32 MPCBufferOffs; + Stream *fw; +}; + + +/// Reads size bytes of data into buffer at ptr. +static mpc_int32_t impc_read(mpc_reader *p_reader, void *ptr, mpc_int32_t size) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + return fw->read(ptr, size, false); + } + catch(...) + { + return(MPC_STATUS_FAIL); + } +} + +/// Seeks to byte position offset. +static mpc_bool_t impc_seek(mpc_reader *p_reader, mpc_int32_t offset) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + fw->seek(offset, SEEK_SET); + return(MPC_TRUE); + } + catch(...) + { + return(MPC_FALSE); + } +} + +/// Returns the current byte offset in the stream. +static mpc_int32_t impc_tell(mpc_reader *p_reader) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + return fw->tell(); + } + catch(...) + { + return(MPC_STATUS_FAIL); + } +} + +/// Returns the total length of the source stream, in bytes. +static mpc_int32_t impc_get_size(mpc_reader *p_reader) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + return fw->size(); + } + catch(...) + { + return(MPC_STATUS_FAIL); + } +} + +/// True if the stream is a seekable stream. +static mpc_bool_t impc_canseek(mpc_reader *p_reader) +{ + return(MPC_TRUE); +} + +MPCReader::MPCReader(Stream *fp) : fw(fp) +{ + fp->seek(0, SEEK_SET); + + 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); + } +} + +MPCReader::~MPCReader() +{ + if(demux) + { + mpc_demux_exit(demux); + demux = NULL; + } +} + +int64 MPCReader::Read_(int16 *buffer, int64 frames) +{ + mpc_status err; + int16 *cowbuf = (int16 *)buffer; + int32 toread = frames * 2; + + while(toread > 0) + { + int32 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++) + { +#ifdef MPC_FIXED_POINT + int32 samp = MPCBuffer[MPCBufferOffs + x] >> MPC_FIXED_POINT_FRACTPART; +#else + #warning Floating-point MPC decoding path not tested. + int32 samp = (int32)(MPCBuffer[MPCBufferOffs + x] * 32767); +#endif + 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 MPCReader::Seek_(int64 frame_offset) +{ + MPCBufferOffs = 0; + MPCBufferIn = 0; + + if(mpc_demux_seek_sample(demux, frame_offset) < 0) + return(false); + + return(true); +} + +int64 MPCReader::FrameCount(void) +{ + return(mpc_streaminfo_get_length_samples(&si)); +} + +/* +** +** +** +** +** +** +** +** +** +*/ + +#ifdef HAVE_LIBSNDFILE +class SFReader : public AudioReader +{ + public: + + SFReader(Stream *fp); + ~SFReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + private: + SNDFILE *sf; + SF_INFO sfinfo; + SF_VIRTUAL_IO sfvf; + + Stream *fw; +}; + +static sf_count_t isf_get_filelen(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->size(); + } + catch(...) + { + return(-1); + } +} + +static sf_count_t isf_seek(sf_count_t offset, int whence, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + //printf("Seek: offset=%lld, whence=%lld\n", (long long)offset, (long long)whence); + + fw->seek(offset, whence); + return fw->tell(); + } + catch(...) + { + //printf(" SEEK FAILED\n"); + return(-1); + } +} + +static sf_count_t isf_read(void *ptr, sf_count_t count, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + sf_count_t ret = fw->read(ptr, count, false); + + //printf("Read: count=%lld, ret=%lld\n", (long long)count, (long long)ret); + + return ret; + } + catch(...) + { + //printf(" READ FAILED\n"); + return(0); + } +} + +static sf_count_t isf_write(const void *ptr, sf_count_t count, void *user_data) +{ + return(0); +} + +static sf_count_t isf_tell(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->tell(); + } + catch(...) + { + return(-1); + } +} + +SFReader::SFReader(Stream *fp) : fw(fp) +{ + fp->seek(0, SEEK_SET); + + memset(&sfvf, 0, sizeof(sfvf)); + sfvf.get_filelen = isf_get_filelen; + sfvf.seek = isf_seek; + sfvf.read = isf_read; + sfvf.write = isf_write; + sfvf.tell = isf_tell; + + memset(&sfinfo, 0, sizeof(sfinfo)); + if(!(sf = sf_open_virtual(&sfvf, SFM_READ, &sfinfo, (void*)fp))) + throw(0); +} + +SFReader::~SFReader() +{ + sf_close(sf); +} + +int64 SFReader::Read_(int16 *buffer, int64 frames) +{ + return(sf_read_short(sf, (short*)buffer, frames * 2) / 2); +} + +bool SFReader::Seek_(int64 frame_offset) +{ + // FIXME error condition + if(sf_seek(sf, frame_offset, SEEK_SET) != frame_offset) + return(false); + return(true); +} + +int64 SFReader::FrameCount(void) +{ + return(sfinfo.frames); +} + +#endif + + +AudioReader *AR_Open(Stream *fp) +{ + try + { + return new MPCReader(fp); + } + catch(int i) + { + } + +#ifdef HAVE_OPUSFILE + try + { + return new OpusReader(fp); + } + catch(int i) + { + } +#endif + + try + { + return new OggVorbisReader(fp); + } + catch(int i) + { + } + +#ifdef HAVE_LIBSNDFILE + try + { + return new SFReader(fp); + } + catch(int i) + { + } +#endif + + return(NULL); +} + diff --git a/psx/mednadisc/audioreader.h b/psx/mednadisc/audioreader.h new file mode 100644 index 0000000000..014a3e48d3 --- /dev/null +++ b/psx/mednadisc/audioreader.h @@ -0,0 +1,43 @@ +#ifndef __MDFN_AUDIOREADER_H +#define __MDFN_AUDIOREADER_H + +#include "../Stream.h" + +class AudioReader +{ + public: + AudioReader(); + virtual ~AudioReader(); + + virtual int64 FrameCount(void); + INLINE int64 Read(int64 frame_offset, int16 *buffer, int64 frames) + { + int64 ret; + + //if(frame_offset >= 0) + { + 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 Read_(int16 *buffer, int64 frames); + virtual bool Seek_(int64 frame_offset); + + int64 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/psx/mednadisc/bizhawk/mednadisc.filters b/psx/mednadisc/bizhawk/mednadisc.filters new file mode 100644 index 0000000000..72d05a57b3 --- /dev/null +++ b/psx/mednadisc/bizhawk/mednadisc.filters @@ -0,0 +1,248 @@ + + + + + {00f73db4-1182-4bf7-b891-66bf860d3742} + + + {f69cc8f2-7480-44d6-9a32-9dca789d2bf6} + + + {57a8e6ec-9225-410d-b38f-ba209abae070} + + + {76abb796-5411-4d33-b3e0-f1f3873f138e} + + + {cb700979-4dce-4b10-8521-3ab71a313271} + + + {d1f71901-17a5-441a-8b4f-f7da34a057c1} + + + + + psx + + + psx + + + psx + + + psx + + + psx + + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + + emuware + + + video + + + video + + + emuware + + + psx + + + psx + + + psx + + + psx + + + cdrom + + + + + psx + + + psx + + + emuware + + + psx + + + psx + + + + + + psx + + + + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + psx\input + + + emuware\msvc + + + emuware\msvc + + + video + + + video + + + emuware + + + cdrom + + + psx + + + psx + + + cdrom + + + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + psx + + + \ No newline at end of file diff --git a/psx/mednadisc/bizhawk/mednadisc.sln b/psx/mednadisc/bizhawk/mednadisc.sln new file mode 100644 index 0000000000..948842cfcc --- /dev/null +++ b/psx/mednadisc/bizhawk/mednadisc.sln @@ -0,0 +1,24 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mednadisc", "mednadisc.vcxproj", "{5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|Win32.ActiveCfg = Debug|Win32 + {5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Debug|Win32.Build.0 = Debug|Win32 + {5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|Win32.ActiveCfg = Release|Win32 + {5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC}.Release|Win32.Build.0 = Release|Win32 + {5A0DAC84-1170-4B1A-B9A9-F566A1D97790}.Debug|Win32.ActiveCfg = Debug|Win32 + {5A0DAC84-1170-4B1A-B9A9-F566A1D97790}.Debug|Win32.Build.0 = Debug|Win32 + {5A0DAC84-1170-4B1A-B9A9-F566A1D97790}.Release|Win32.ActiveCfg = Release|Win32 + {5A0DAC84-1170-4B1A-B9A9-F566A1D97790}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/psx/mednadisc/bizhawk/mednadisc.vcxproj b/psx/mednadisc/bizhawk/mednadisc.vcxproj new file mode 100644 index 0000000000..1dfd3512b7 --- /dev/null +++ b/psx/mednadisc/bizhawk/mednadisc.vcxproj @@ -0,0 +1,134 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC} + Win32Proj + mednadisc + + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + + + + true + $(ProjectDir)\..\..\..\output\dll\ + + + false + $(ProjectDir)\..\..\..\output\dll\ + + + + NotUsing + Level3 + Disabled + EW_EXPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;OCTOSHOCK_EXPORTS;%(PreprocessorDefinitions) + ../emuware/msvc;.. + + + + + true + false + + + Windows + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;EW_EXPORT;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + true + ../emuware/msvc;.. + + + Windows + true + true + true + + + + + + \ No newline at end of file diff --git a/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters b/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters new file mode 100644 index 0000000000..9aaf63d447 --- /dev/null +++ b/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters @@ -0,0 +1,104 @@ + + + + + {a3ffd332-9644-473f-b3b6-d31b08be5256} + + + {99e57b88-966c-4695-8f5c-1db6345b1b26} + + + {798fa5bd-6381-487a-99d2-35a15a6da439} + + + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + + + + + + string + + + + cdrom + + + + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + cdrom + + + emuware + + + + + + + + string + + + + cdrom + + + + \ No newline at end of file diff --git a/psx/mednadisc/cdrom/CDAccess.cpp b/psx/mednadisc/cdrom/CDAccess.cpp new file mode 100644 index 0000000000..641fc4c4ae --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess.cpp @@ -0,0 +1,58 @@ +/* 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 "emuware/emuware.h" +#include "CDAccess.h" +#include "CDAccess_Image.h" +#include "CDAccess_CCD.h" + +#ifdef HAVE_LIBCDIO +#include "CDAccess_Physical.h" +#endif + +using namespace CDUtility; + +CDAccess::CDAccess() +{ + +} + +CDAccess::~CDAccess() +{ + +} + +CDAccess *cdaccess_open_image(const std::string& path, bool 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; +} + +CDAccess *cdaccess_open_phys(const std::string& devicename) +{ + #ifdef HAVE_LIBCDIO + return new CDAccess_Physical(devicename); + #else + throw MDFN_Error(0, ("Physical CD access support not compiled in.")); + #endif +} diff --git a/psx/mednadisc/cdrom/CDAccess.h b/psx/mednadisc/cdrom/CDAccess.h new file mode 100644 index 0000000000..667c6b8b66 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess.h @@ -0,0 +1,32 @@ +#ifndef __MDFN_CDROMFILE_H +#define __MDFN_CDROMFILE_H + +#include +#include + +#include "CDUtility.h" + +class CDAccess +{ + public: + + CDAccess(); + virtual ~CDAccess(); + + virtual void Read_Raw_Sector(uint8 *buf, int32 lba) = 0; + + virtual void Read_TOC(CDUtility::TOC *toc) = 0; + + virtual bool Is_Physical(void) throw() = 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 + + private: + CDAccess(const CDAccess&); // No copy constructor. + CDAccess& operator=(const CDAccess&); // No assignment operator. +}; + +CDAccess *cdaccess_open_image(const std::string& path, bool image_memcache); +CDAccess *cdaccess_open_phys(const std::string& devicename); + +#endif diff --git a/psx/mednadisc/cdrom/CDAccess_CCD.cpp b/psx/mednadisc/cdrom/CDAccess_CCD.cpp new file mode 100644 index 0000000000..6817a20d03 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess_CCD.cpp @@ -0,0 +1,435 @@ +/* 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 "emuware/emuware.h" +#include "../general.h" +#include "../string/trim.h" +#include "CDAccess_CCD.h" +//#include + +//wrapper to repair gettext stuff +#define _(X) X + +#include +#include +#include + +using namespace CDUtility; + +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 +static T CCD_ReadInt(CCD_Section &s, const std::string &propname, const bool have_defval = false, const int defval = 0) +{ + CCD_Section::iterator zit = s.find(propname); + + if(zit == s.end()) + { + if(have_defval) + return defval; + 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') + { + scan_base = 16; + scan_offset = 2; + } + + const char *vp = v.c_str() + scan_offset; + char *ep = NULL; + + if(std::numeric_limits::is_signed) + ret = strtol(vp, &ep, scan_base); + else + 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()); + } + + //if(ret < minv || ret > maxv) + //{ + // throw MDFN_Error(0, _("Property %s: Integer %ld out of range(accepted: %d through %d)."), propname.c_str(), ret, minv, maxv); + //} + + return ret; +} + + +CDAccess_CCD::CDAccess_CCD(const std::string& path, bool image_memcache) : img_stream(NULL), sub_stream(NULL), img_numsectors(0) +{ + try + { + Load(path, image_memcache); + } + catch(...) + { + Cleanup(); + throw; + } +} + +void CDAccess_CCD::Load(const std::string& path, bool image_memcache) +{ + FileStream cf(path, FileStream::MODE_READ); + std::map Sections; + std::string linebuf; + std::string cur_section_name; + std::string dir_path, file_base, file_ext; + char img_extsd[4] = { 'i', 'm', 'g', 0 }; + char sub_extsd[4] = { 's', 'u', 'b', 0 }; + + MDFN_GetFilePathComponents(path, &dir_path, &file_base, &file_ext); + + if(file_ext.length() == 4 && file_ext[0] == '.') + { + signed char extupt[3] = { -1, -1, -1 }; + + for(int i = 1; i < 4; i++) + { + if(file_ext[i] >= 'A' && file_ext[i] <= 'Z') + extupt[i - 1] = 'A' - 'a'; + else if(file_ext[i] >= 'a' && file_ext[i] <= 'z') + extupt[i - 1] = 0; + } + + signed char av = -1; + for(int i = 0; i < 3; i++) + { + if(extupt[i] != -1) + av = extupt[i]; + else + extupt[i] = av; + } + + if(av == -1) + av = 0; + + for(int i = 0; i < 3; i++) + { + if(extupt[i] == -1) + extupt[i] = av; + } + + for(int i = 0; i < 3; i++) + { + img_extsd[i] += extupt[i]; + sub_extsd[i] += extupt[i]; + } + } + + //printf("%s %d %d %d\n", file_ext.c_str(), extupt[0], extupt[1], extupt[2]); + + linebuf.reserve(256); + + while(cf.get_line(linebuf) >= 0) + { + MDFN_trim(linebuf); + + if(linebuf.length() == 0) // Skip blank lines. + continue; + + if(linebuf[0] == '[') + { + if(linebuf.length() < 3 || linebuf[linebuf.length() - 1] != ']') + throw MDFN_Error(0, _("Malformed section specifier: %s"), linebuf.c_str()); + + cur_section_name = linebuf.substr(1, linebuf.length() - 2); + MDFN_strtoupper(cur_section_name); + } + else + { + const size_t feqpos = linebuf.find('='); + const size_t leqpos = linebuf.rfind('='); + std::string k, v; + + if(feqpos == std::string::npos || feqpos != leqpos) + throw MDFN_Error(0, _("Malformed value pair specifier: %s"), linebuf.c_str()); + + k = linebuf.substr(0, feqpos); + v = linebuf.substr(feqpos + 1); + + MDFN_trim(k); + MDFN_trim(v); + + MDFN_strtoupper(k); + + Sections[cur_section_name][k] = v; + } + } + + { + 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) + throw MDFN_Error(0, _("Unsupported number of sessions: %u"), num_sessions); + + if(data_tracks_scrambled) + throw MDFN_Error(0, _("Scrambled CCD data tracks currently not supported.")); + + //printf("MOO: %d\n", toc_entries); + + 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)]; + unsigned session = CCD_ReadInt(ts, "SESSION"); + uint8 point = CCD_ReadInt(ts, "POINT"); + uint8 adr = CCD_ReadInt(ts, "ADR"); + uint8 control = CCD_ReadInt(ts, "CONTROL"); + uint8 pmin = CCD_ReadInt(ts, "PMIN"); + uint8 psec = CCD_ReadInt(ts, "PSEC"); + //uint8 pframe = CCD_ReadInt(ts, "PFRAME"); + signed plba = CCD_ReadInt(ts, "PLBA"); + + if(session != 1) + throw MDFN_Error(0, "Unsupported TOC entry Session value: %u", session); + + // Reference: ECMA-394, page 5-14 + if(point >= 1 && point <= 99) + { + tocd.tracks[point].adr = adr; + tocd.tracks[point].control = control; + tocd.tracks[point].lba = plba; + } + else + switch(point) + { + default: + throw MDFN_Error(0, "Unsupported TOC entry Point value: %u", point); + break; + + case 0xA0: + tocd.first_track = pmin; + tocd.disc_type = psec; + 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; + + + break; + } + } + } + + // Convenience leadout track duplication. + if(tocd.last_track < 99) + tocd.tracks[tocd.last_track + 1] = tocd.tracks[100]; + + // + // Open image stream. + { + std::string image_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(img_extsd), true); + + if(image_memcache) + { + img_stream = new MemoryStream(new FileStream(image_path, FileStream::MODE_READ)); + } + else + { + img_stream = new FileStream(image_path, FileStream::MODE_READ); + } + + int64 ss = img_stream->size(); + + if(ss % 2352) + throw MDFN_Error(0, _("CCD image size is not evenly divisible by 2352.")); + + img_numsectors = ss / 2352; + } + + // + // Open subchannel stream + { + std::string sub_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(sub_extsd), true); + + if(image_memcache) + sub_stream = new MemoryStream(new FileStream(sub_path, FileStream::MODE_READ)); + else + sub_stream = new FileStream(sub_path, FileStream::MODE_READ); + + if(sub_stream->size() != (uint64)img_numsectors * 96) + throw MDFN_Error(0, _("CCD SUB file size mismatch.")); + } + + CheckSubQSanity(); +} + +// +// Checks for Q subchannel mode 1(current time) data that has a correct checksum, but the data is nonsensical or corrupted nonetheless; this is the +// case for some bad rips floating around on the Internet. Allowing these bad rips to be used will cause all sorts of problems during emulation, so we +// error out here if a bad rip is detected. +// +// This check is not as aggressive or exhaustive as it could be, and will not detect all potential Q subchannel rip errors; as such, it should definitely NOT be +// used in an effort to "repair" a broken rip. +// +void CDAccess_CCD::CheckSubQSanity(void) +{ + size_t checksum_pass_counter = 0; + int prev_lba = INT_MAX; + uint8 prev_track = 0; + + for(size_t s = 0; s < img_numsectors; s++) + { + union + { + uint8 full[96]; + struct + { + uint8 pbuf[12]; + uint8 qbuf[12]; + }; + } buf; + + sub_stream->seek(s * 96, SEEK_SET); + sub_stream->read(buf.full, 96); + + if(subq_check_checksum(buf.qbuf)) + { + uint8 adr = buf.qbuf[0] & 0xF; + + if(adr == 0x01) + { + uint8 track_bcd = buf.qbuf[1]; + uint8 index_bcd = buf.qbuf[2]; + uint8 rm_bcd = buf.qbuf[3]; + uint8 rs_bcd = buf.qbuf[4]; + uint8 rf_bcd = buf.qbuf[5]; + uint8 am_bcd = buf.qbuf[7]; + uint8 as_bcd = buf.qbuf[8]; + uint8 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) + { + throw 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); + } + else + { + int lba = ((BCD_to_U8(am_bcd) * 60 + BCD_to_U8(as_bcd)) * 75 + BCD_to_U8(af_bcd)) - 150; + uint8 track = BCD_to_U8(track_bcd); + + if(prev_lba != INT_MAX && abs(lba - prev_lba) > 100) + throw MDFN_Error(0, _("Garbage subchannel Q data detected(excessively large jump in AMSF)")); + + if(abs(lba - (int)s) > 100) + throw MDFN_Error(0, _("Garbage subchannel Q data detected(AMSF value is out of tolerance)")); + + prev_lba = lba; + + if(track < prev_track) + throw MDFN_Error(0, _("Garbage subchannel Q data detected(bad track number)")); + //else if(prev_track && track - pre + + prev_track = track; + } + checksum_pass_counter++; + } + } + } + + //printf("%u/%u\n", checksum_pass_counter, img_numsectors); +} + +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(); +} + +void CDAccess_CCD::Read_Raw_Sector(uint8 *buf, int32 lba) +{ + if(lba < 0 || (size_t)lba >= img_numsectors) + throw(MDFN_Error(0, _("LBA out of range."))); + + uint8 sub_buf[96]; + + 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_buf, buf + 2352); +} + + +void CDAccess_CCD::Read_TOC(CDUtility::TOC *toc) +{ + *toc = tocd; +} + +bool CDAccess_CCD::Is_Physical(void) throw() +{ + return false; +} + +void CDAccess_CCD::Eject(bool eject_status) +{ + +} + diff --git a/psx/mednadisc/cdrom/CDAccess_CCD.h b/psx/mednadisc/cdrom/CDAccess_CCD.h new file mode 100644 index 0000000000..23a07f6aaf --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess_CCD.h @@ -0,0 +1,50 @@ +/* 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 "../FileStream.h" +#include "../MemoryStream.h" +#include "CDAccess.h" + +#include + +class CDAccess_CCD : public CDAccess +{ + public: + + CDAccess_CCD(const std::string& path, bool image_memcache); + virtual ~CDAccess_CCD(); + + virtual void Read_Raw_Sector(uint8 *buf, int32 lba); + + virtual void Read_TOC(CDUtility::TOC *toc); + + virtual bool Is_Physical(void) throw(); + + virtual void Eject(bool eject_status); + + private: + + void Load(const std::string& path, bool image_memcache); + void Cleanup(void); + + void CheckSubQSanity(void); + + Stream* img_stream; + Stream* sub_stream; + size_t img_numsectors; + CDUtility::TOC tocd; +}; diff --git a/psx/mednadisc/cdrom/CDAccess_Image.cpp b/psx/mednadisc/cdrom/CDAccess_Image.cpp new file mode 100644 index 0000000000..e908124344 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess_Image.cpp @@ -0,0 +1,1241 @@ +/* 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 + */ + +/* + Notes and TODO: + + POSTGAP in CUE sheets may not be handled properly, should the directive automatically increment the index number? + + INDEX nn where 02 <= nn <= 99 is not supported in CUE sheets. + + TOC reading code is extremely barebones, leaving out support for more esoteric features. + + 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 "emuware/emuware.h" + +//undo gettext stuff +#define _(X) (X) + +#include + +#include +#include +#include +#include + +#include "general.h" +#include "string/trim.h" +#include "endian.h" +#include "FileStream.h" +#include "MemoryStream.h" + +#include "CDAccess.h" +#include "CDAccess_Image.h" + +#include "audioreader.h" + +#include + +using namespace CDUtility; + +enum +{ + CDRF_SUBM_NONE = 0, + CDRF_SUBM_RW = 1, + CDRF_SUBM_RW_RAW = 2 +}; + +// Disk-image(rip) track/sector formats +enum +{ + DI_FORMAT_AUDIO = 0x00, + DI_FORMAT_MODE1 = 0x01, + DI_FORMAT_MODE1_RAW = 0x02, + DI_FORMAT_MODE2 = 0x03, + DI_FORMAT_MODE2_FORM1 = 0x04, + DI_FORMAT_MODE2_FORM2 = 0x05, + DI_FORMAT_MODE2_RAW = 0x06, + _DI_FORMAT_COUNT +}; + +static const int32 DI_Size_Table[7] = +{ + 2352, // Audio + 2048, // MODE1 + 2352, // MODE1 RAW + 2336, // MODE2 + 2048, // MODE2 Form 1 + 2324, // Mode 2 Form 2 + 2352 +}; + +static const char *DI_CDRDAO_Strings[7] = +{ + "AUDIO", + "MODE1", + "MODE1_RAW", + "MODE2", + "MODE2_FORM1", + "MODE2_FORM2", + "MODE2_RAW" +}; + +static const char *DI_CUE_Strings[7] = +{ + "AUDIO", + "MODE1/2048", + "MODE1/2352", + + // FIXME: These are just guesses: + "MODE2/2336", + "MODE2/2048", + "MODE2/2324", + "MODE2/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) +{ + const size_t source_len = src.length(); + bool in_quote = 0; + bool already_normal = 0; + + dest.clear(); + + while(source_offset < source_len) + { + if(src[source_offset] == ' ' || src[source_offset] == '\t') + { + if(!in_quote) + { + if(already_normal) // Trailing whitespace(IE we're done with this argument) + break; + else // Leading whitespace, ignore it. + { + source_offset++; + continue; + } + } + } + + if(src[source_offset] == '"' && parse_quotes) + { + if(in_quote) + { + source_offset++; +// Not sure which behavior is most useful(or correct :b). +#if 0 + in_quote = false; + already_normal = true; +#else + break; +#endif + } + else + in_quote = 1; + } + else + { + dest.push_back(src[source_offset]); + already_normal = 1; + } + source_offset++; + } + + while(source_offset < source_len) + { + if(src[source_offset] != ' ' && src[source_offset] != '\t') + break; + + source_offset++; + } + + return source_offset; +} + +uint32 CDAccess_Image::GetSectorCount(CDRFILE_TRACK_INFO *track) +{ + if(track->DIFormat == DI_FORMAT_AUDIO) + { + if(track->AReader) + return(((track->AReader->FrameCount() * 4) - track->FileOffset) / 2352); + else + { + const int64 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)); + else + return((size - track->FileOffset) / 2352); + } + } + else + { + const int64 size = track->fp->size(); + + return((size - track->FileOffset) / DI_Size_Table[track->DIFormat]); + } + + return(0); +} + +void 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; + long sectors; + std::map::iterator ribbit; + + ribbit = toc_streamcache.find(filename); + + if(ribbit != toc_streamcache.end()) + { + track->FirstFileInstance = 0; + + track->fp = ribbit->second; + } + else + { + std::string efn; + + track->FirstFileInstance = 1; + + efn = MDFN_EvalFIP(base_dir, filename); + + if(image_memcache) + track->fp = new MemoryStream(new FileStream(efn, FileStream::MODE_READ)); + else + track->fp = new FileStream(efn, FileStream::MODE_READ); + + toc_streamcache[filename] = track->fp; + } + + if(filename.length() >= 4 && !strcasecmp(filename.c_str() + filename.length() - 4, ".wav")) + { + track->AReader = AR_Open(track->fp); + + if(!track->AReader) + throw MDFN_Error(0, "TODO ERROR"); + } + + sector_mult = DI_Size_Table[track->DIFormat]; + + if(track->SubchannelMode) + sector_mult += 96; + + if(binoffset && sscanf(binoffset, "%ld", &tmp_long) == 1) + { + offset += tmp_long; + } + + if(msfoffset && sscanf(msfoffset, "%d:%d:%d", &m, &s, &f) == 3) + { + offset += ((m * 60 + s) * 75 + f) * sector_mult; + } + + track->FileOffset = offset; // Make sure this is set before calling GetSectorCount()! + sectors = GetSectorCount(track); + //printf("Track: %d, offset: %ld, %ld\n", tracknum, offset, sectors); + + if(length) + { + tmp_long = sectors; + + if(sscanf(length, "%d:%d:%d", &m, &s, &f) == 3) + tmp_long = (m * 60 + s) * 75 + f; + else if(track->DIFormat == DI_FORMAT_AUDIO) + { + char *endptr = NULL; + + tmp_long = strtol(length, &endptr, 10); + + // Error? + if(endptr == length) + { + tmp_long = sectors; + } + else + tmp_long /= 588; + + } + + if(tmp_long > sectors) + { + throw MDFN_Error(0, _("Length specified in TOC file for track %d is too large by %ld sectors!\n"), tracknum, (long)(tmp_long - sectors)); + } + sectors = tmp_long; + } + + track->sectors = sectors; +} + +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'; + } + } +} + +#if 0 +std::string MDFN_toupper(const std::string &str) +{ + const size_t len = str.length(); + std::string new_str; + + new_str.reserve(len); + + for(size_t x = 0; x < len; x++) + { + int c = str[x]; + + if(c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + + new_str.push_back(c); + } +} +#endif + +void CDAccess_Image::LoadSBI(const std::string& sbi_path) +{ + printf(_("Loading SBI file \"%s\"...\n"), sbi_path.c_str()); + { + //MDFN_AutoIndent aind(1); + + try + { + FileStream sbis(sbi_path, FileStream::MODE_READ); + uint8 header[4]; + uint8 ed[4 + 10]; + uint8 tmpq[12]; + + sbis.read(header, 4); + + if(memcmp(header, "SBI\0", 4)) + throw MDFN_Error(0, _("Not recognized a valid SBI file.")); + + while(sbis.read(ed, sizeof(ed), false) == sizeof(ed)) + { + if(!BCD_is_valid(ed[0]) || !BCD_is_valid(ed[1]) || !BCD_is_valid(ed[2])) + throw MDFN_Error(0, _("Bad BCD MSF offset in SBI file: %02x:%02x:%02x"), ed[0], ed[1], ed[2]); + + if(ed[3] != 0x01) + throw MDFN_Error(0, _("Unrecognized boogly oogly in SBI file: %02x"), ed[3]); + + memcpy(tmpq, &ed[4], 10); + + // + subq_generate_checksum(tmpq); + tmpq[10] ^= 0xFF; + tmpq[11] ^= 0xFF; + // + + //printf("%02x:%02x:%02x --- ", ed[0], ed[1], ed[2]); + //for(unsigned i = 0; i < 12; i++) + // printf("%02x ", tmpq[i]); + //printf("\n"); + + uint32 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); + } + printf(_("Loaded Q subchannel replacements for %zu sectors.\n"), SubQReplaceMap.size()); + } + catch(MDFN_Error &e) + { + if(e.GetErrno() != ENOENT) + throw; + else + printf(_("Error: %s\n"), e.what()); + } + catch(std::exception &e) + { + throw; + } + } +} + + +void CDAccess_Image::ImageOpen(const std::string& path, bool image_memcache) +{ + MemoryStream fp(new FileStream(path, FileStream::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 + CDRFILE_TRACK_INFO TmpTrack; + std::string file_base, file_ext; + std::map toc_streamcache; + + disc_type = DISC_TYPE_CDDA_OR_M1; + memset(&TmpTrack, 0, sizeof(TmpTrack)); + + MDFN_GetFilePathComponents(path, &base_dir, &file_base, &file_ext); + + if(!strcasecmp(file_ext.c_str(), ".toc")) + { + printf(_("TOC file detected.\n")); + IsTOC = true; + } + + // Check for annoying UTF-8 BOM. + if(!IsTOC) + { + uint8 bom_tmp[3]; + + if(fp.read(bom_tmp, 3, false) == 3 && bom_tmp[0] == 0xEF && bom_tmp[1] == 0xBB && bom_tmp[2] == 0xBF) + { + // Print an annoying error message, but don't actually error out. + printf(_("UTF-8 BOM detected at start of CUE sheet.")); + } + else + fp.seek(0, SEEK_SET); + } + + + // Assign opposite maximum values so our tests will work! + FirstTrack = 99; + LastTrack = 0; + + linebuf.reserve(1024); + while(fp.get_line(linebuf) >= 0) + { + unsigned argcount = 0; + + if(IsTOC) + { + // Handle TOC format comments + size_t ss_loc = linebuf.find("//"); + + if(ss_loc != std::string::npos) + linebuf.resize(ss_loc); + } + + // 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); + + if(linebuf.length() == 0) // Skip blank lines. + continue; + + // Grab command and arguments. + { + size_t offs = 0; + + offs = UnQuotify(linebuf, offs, cmdbuf, false); + for(argcount = 0; argcount < max_args && offs < linebuf.length(); argcount++) + offs = UnQuotify(linebuf, offs, args[argcount]); + + // Make sure unused arguments are cleared out so we don't have inter-line leaks! + for(unsigned x = argcount; x < max_args; x++) + args[x].clear(); + + MDFN_strtoupper(cmdbuf); + } + + //printf("%s\n", cmdbuf.c_str()); //: %s %s %s %s\n", cmdbuf.c_str(), args[0].c_str(), args[1].c_str(), args[2].c_str(), args[3].c_str()); + + if(IsTOC) + { + if(cmdbuf == "TRACK") + { + if(active_track >= 0) + { + memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack)); + memset(&TmpTrack, 0, sizeof(TmpTrack)); + active_track = -1; + } + + if(AutoTrackInc > 99) + { + throw(MDFN_Error(0, _("Invalid track number: %d"), AutoTrackInc)); + } + + active_track = AutoTrackInc++; + if(active_track < FirstTrack) + FirstTrack = active_track; + if(active_track > LastTrack) + LastTrack = active_track; + + int format_lookup; + for(format_lookup = 0; format_lookup < _DI_FORMAT_COUNT; format_lookup++) + { + if(!strcasecmp(args[0].c_str(), DI_CDRDAO_Strings[format_lookup])) + { + TmpTrack.DIFormat = format_lookup; + break; + } + } + + if(format_lookup == _DI_FORMAT_COUNT) + { + throw(MDFN_Error(0, _("Invalid track format: %s"), args[0].c_str())); + } + + if(TmpTrack.DIFormat == DI_FORMAT_AUDIO) + TmpTrack.RawAudioMSBFirst = true; // Silly cdrdao... + + if(!strcasecmp(args[1].c_str(), "RW")) + { + TmpTrack.SubchannelMode = CDRF_SUBM_RW; + throw(MDFN_Error(0, _("\"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; + + } // end to TRACK + else if(cmdbuf == "SILENCE") + { + //throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); + } + else if(cmdbuf == "ZERO") + { + //throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); + } + else if(cmdbuf == "FIFO") + { + throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); + } + else if(cmdbuf == "FILE" || cmdbuf == "AUDIOFILE") + { + const char *binoffset = NULL; + const char *msfoffset = NULL; + const char *length = NULL; + + if(args[1].c_str()[0] == '#') + { + binoffset = args[1].c_str() + 1; + msfoffset = args[2].c_str(); + length = args[3].c_str(); + } + else + { + msfoffset = args[1].c_str(); + 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); + } + else if(cmdbuf == "DATAFILE") + { + const char *binoffset = NULL; + const char *length = NULL; + + if(args[1].c_str()[0] == '#') + { + binoffset = args[1].c_str() + 1; + length = args[2].c_str(); + } + else + length = args[1].c_str(); + + ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, NULL, length, image_memcache, toc_streamcache); + } + else if(cmdbuf == "INDEX") + { + + } + else if(cmdbuf == "PREGAP") + { + if(active_track < 0) + { + throw(MDFN_Error(0, _("Command %s is outside of a TRACK definition!\n"), cmdbuf.c_str())); + } + int m,s,f; + sscanf(args[0].c_str(), "%d:%d:%d", &m, &s, &f); + TmpTrack.pregap = (m * 60 + s) * 75 + f; + } // end to PREGAP + else if(cmdbuf == "START") + { + if(active_track < 0) + { + throw(MDFN_Error(0, _("Command %s is outside of a TRACK definition!\n"), cmdbuf.c_str())); + } + int m,s,f; + sscanf(args[0].c_str(), "%d:%d:%d", &m, &s, &f); + TmpTrack.pregap = (m * 60 + s) * 75 + f; + } + else if(cmdbuf == "TWO_CHANNEL_AUDIO") + { + TmpTrack.subq_control &= ~SUBQ_CTRLF_4CH; + } + else if(cmdbuf == "FOUR_CHANNEL_AUDIO") + { + TmpTrack.subq_control |= SUBQ_CTRLF_4CH; + } + else if(cmdbuf == "NO") + { + MDFN_strtoupper(args[0]); + + if(args[0] == "COPY") + { + TmpTrack.subq_control &= ~SUBQ_CTRLF_DCP; + } + else if(args[0] == "PRE_EMPHASIS") + { + TmpTrack.subq_control &= ~SUBQ_CTRLF_PRE; + } + else + { + throw MDFN_Error(0, _("Unsupported argument to \"NO\" directive: %s"), args[0].c_str()); + } + } + else if(cmdbuf == "COPY") + { + TmpTrack.subq_control |= SUBQ_CTRLF_DCP; + } + else if(cmdbuf == "PRE_EMPHASIS") + { + TmpTrack.subq_control |= SUBQ_CTRLF_PRE; + } + // TODO: Confirm that these are taken from the TOC of the disc, and not synthesized by cdrdao. + else if(cmdbuf == "CD_DA") + disc_type = DISC_TYPE_CDDA_OR_M1; + else if(cmdbuf == "CD_ROM") + disc_type = DISC_TYPE_CDDA_OR_M1; + else if(cmdbuf == "CD_ROM_XA") + disc_type = DISC_TYPE_CD_XA; + else + { + //throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str()); + } + // TODO: CATALOG + + } /*********** END TOC HANDLING ************/ + else // now for CUE sheet handling + { + if(cmdbuf == "FILE") + { + if(active_track >= 0) + { + memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack)); + memset(&TmpTrack, 0, sizeof(TmpTrack)); + active_track = -1; + } + + std::string efn = MDFN_EvalFIP(base_dir, args[0]); + TmpTrack.fp = new FileStream(efn, FileStream::MODE_READ); + TmpTrack.FirstFileInstance = 1; + + if(image_memcache) + TmpTrack.fp = new MemoryStream(TmpTrack.fp); + + if(!strcasecmp(args[1].c_str(), "BINARY")) + { + //TmpTrack.Format = TRACK_FORMAT_DATA; + //struct stat stat_buf; + //fstat(fileno(TmpTrack.fp), &stat_buf); + //TmpTrack.sectors = stat_buf.st_size; // / 2048; + } + 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); + if(!TmpTrack.AReader) + { + throw(MDFN_Error(0, _("Unsupported audio track file format: %s\n"), args[0].c_str())); + } + } + else + { + throw(MDFN_Error(0, _("Unsupported track format: %s\n"), args[1].c_str())); + } + } + else if(cmdbuf == "TRACK") + { + if(active_track >= 0) + { + memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack)); + TmpTrack.FirstFileInstance = 0; + TmpTrack.pregap = 0; + TmpTrack.pregap_dv = 0; + TmpTrack.postgap = 0; + TmpTrack.index[0] = -1; + TmpTrack.index[1] = 0; + } + active_track = atoi(args[0].c_str()); + + if(active_track < FirstTrack) + FirstTrack = active_track; + if(active_track > LastTrack) + LastTrack = active_track; + + int format_lookup; + for(format_lookup = 0; format_lookup < _DI_FORMAT_COUNT; format_lookup++) + { + if(!strcasecmp(args[1].c_str(), DI_CUE_Strings[format_lookup])) + { + TmpTrack.DIFormat = format_lookup; + break; + } + } + + if(format_lookup == _DI_FORMAT_COUNT) + { + throw(MDFN_Error(0, _("Invalid track format: %s\n"), args[1].c_str())); + } + + if(active_track < 0 || active_track > 99) + { + throw(MDFN_Error(0, _("Invalid track number: %d\n"), active_track)); + } + } + else if(cmdbuf == "INDEX") + { + if(active_track >= 0) + { + unsigned int m,s,f; + + if(sscanf(args[1].c_str(), "%u:%u:%u", &m, &s, &f) != 3) + { + throw MDFN_Error(0, _("Malformed m:s:f time in \"%s\" directive: %s"), cmdbuf.c_str(), args[0].c_str()); + } + + 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") + { + if(active_track >= 0) + { + unsigned int m,s,f; + + if(sscanf(args[0].c_str(), "%u:%u:%u", &m, &s, &f) != 3) + { + throw MDFN_Error(0, _("Malformed m:s:f time in \"%s\" directive: %s"), cmdbuf.c_str(), args[0].c_str()); + } + + TmpTrack.pregap = (m * 60 + s) * 75 + f; + } + } + else if(cmdbuf == "POSTGAP") + { + if(active_track >= 0) + { + unsigned int m,s,f; + + if(sscanf(args[0].c_str(), "%u:%u:%u", &m, &s, &f) != 3) + { + throw MDFN_Error(0, _("Malformed m:s:f time in \"%s\" directive: %s"), cmdbuf.c_str(), args[0].c_str()); + } + + TmpTrack.postgap = (m * 60 + s) * 75 + f; + } + } + else if(cmdbuf == "REM") + { + + } + else if(cmdbuf == "FLAGS") + { + TmpTrack.subq_control &= ~(SUBQ_CTRLF_PRE | SUBQ_CTRLF_DCP | SUBQ_CTRLF_4CH); + for(unsigned i = 0; i < argcount; i++) + { + if(args[i] == "DCP") + { + TmpTrack.subq_control |= SUBQ_CTRLF_DCP; + } + else if(args[i] == "4CH") + { + TmpTrack.subq_control |= SUBQ_CTRLF_4CH; + } + else if(args[i] == "PRE") + { + TmpTrack.subq_control |= SUBQ_CTRLF_PRE; + } + else if(args[i] == "SCMS") + { + // Not implemented, likely pointless. PROBABLY indicates that the copy bit of the subchannel Q control field is supposed to + // alternate between 1 and 0 at 9.375 Hz(four 1, four 0, four 1, four 0, etc.). + } + else + { + throw MDFN_Error(0, _("Unknown CUE sheet \"FLAGS\" directive flag \"%s\".\n"), args[i].c_str()); + } + } + } + else if(cmdbuf == "CDTEXTFILE" || cmdbuf == "CATALOG" || cmdbuf == "ISRC" || + cmdbuf == "TITLE" || cmdbuf == "PERFORMER" || cmdbuf == "SONGWRITER") + { + printf(_("Unsupported CUE sheet directive: \"%s\".\n"), cmdbuf.c_str()); // FIXME, generic logger passed by pointer to constructor + } + else + { + throw MDFN_Error(0, _("Unknown CUE sheet directive \"%s\".\n"), cmdbuf.c_str()); + } + } // end of CUE sheet handling + } // end of fgets() loop + + if(active_track >= 0) + memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack)); + + if(FirstTrack > LastTrack) + { + throw(MDFN_Error(0, _("No tracks found!\n"))); + } + + FirstTrack = FirstTrack; + NumTracks = 1 + LastTrack - FirstTrack; + + int32 RunningLBA = 0; + int32 LastIndex = 0; + long FileOffset = 0; + + for(int x = FirstTrack; x < (FirstTrack + NumTracks); x++) + { + if(Tracks[x].DIFormat == DI_FORMAT_AUDIO) + Tracks[x].subq_control &= ~SUBQ_CTRLF_DATA; + else + Tracks[x].subq_control |= SUBQ_CTRLF_DATA; + + if(!IsTOC) // TOC-format disc_type calculation is handled differently. + { + 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; + } + } + + if(IsTOC) + { + RunningLBA += Tracks[x].pregap; + Tracks[x].LBA = RunningLBA; + RunningLBA += Tracks[x].sectors; + RunningLBA += Tracks[x].postgap; + } + else // else handle CUE sheet... + { + if(Tracks[x].FirstFileInstance) + { + LastIndex = 0; + FileOffset = 0; + } + + RunningLBA += Tracks[x].pregap; + + Tracks[x].pregap_dv = 0; + + if(Tracks[x].index[0] != -1) + Tracks[x].pregap_dv = Tracks[x].index[1] - Tracks[x].index[0]; + + FileOffset += Tracks[x].pregap_dv * DI_Size_Table[Tracks[x].DIFormat]; + + RunningLBA += Tracks[x].pregap_dv; + + Tracks[x].LBA = RunningLBA; + + // Make sure FileOffset this is set before the call to GetSectorCount() + Tracks[x].FileOffset = FileOffset; + Tracks[x].sectors = GetSectorCount(&Tracks[x]); + + if((x + 1) >= (FirstTrack + NumTracks) || Tracks[x+1].FirstFileInstance) + { + + } + else + { + // Fix the sector count if we have multiple tracks per one binary image file. + if(Tracks[x + 1].index[0] == -1) + Tracks[x].sectors = Tracks[x + 1].index[1] - Tracks[x].index[1]; + else + Tracks[x].sectors = Tracks[x + 1].index[0] - Tracks[x].index[1]; //Tracks[x + 1].index - Tracks[x].index; + } + + //printf("Poo: %d %d\n", x, Tracks[x].sectors); + RunningLBA += Tracks[x].sectors; + RunningLBA += Tracks[x].postgap; + + //printf("%d, %ld %d %d %d %d\n", x, FileOffset, Tracks[x].index, Tracks[x].pregap, Tracks[x].sectors, Tracks[x].LBA); + + FileOffset += Tracks[x].sectors * DI_Size_Table[Tracks[x].DIFormat]; + } // end to cue sheet handling + } // end to track loop + + total_sectors = RunningLBA; + + // + // Load SBI file, if present + // + if(!IsTOC) + { + char sbi_ext[4] = { 's', 'b', 'i', 0 }; + + if(file_ext.length() == 4 && file_ext[0] == '.') + { + for(unsigned i = 0; i < 3; i++) + { + if(file_ext[1 + i] >= 'A' && file_ext[1 + i] <= 'Z') + sbi_ext[i] += 'A' - 'a'; + } + } + + LoadSBI(MDFN_EvalFIP(base_dir, file_base + std::string(".") + std::string(sbi_ext), true).c_str()); + } +} + +void CDAccess_Image::Cleanup(void) +{ + for(int32 track = 0; track < 100; track++) + { + CDRFILE_TRACK_INFO *this_track = &Tracks[track]; + + if(this_track->FirstFileInstance) + { + if(Tracks[track].AReader) + { + delete Tracks[track].AReader; + Tracks[track].AReader = NULL; + } + + if(this_track->fp) + { + delete this_track->fp; + this_track->fp = NULL; + } + } + } +} + +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)); + + try + { + ImageOpen(path, image_memcache); + } + catch(...) + { + Cleanup(); + throw; + } +} + +CDAccess_Image::~CDAccess_Image() +{ + Cleanup(); +} + +void CDAccess_Image::Read_Raw_Sector(uint8 *buf, int32 lba) +{ + bool TrackFound = FALSE_0; + uint8 SimuQ[0xC]; + + memset(buf + 2352, 0, 96); + + MakeSubPQ(lba, buf + 2352); + + subq_deinterleave(buf + 2352, SimuQ); + + for(int32 track = FirstTrack; track < (FirstTrack + NumTracks); track++) + { + CDRFILE_TRACK_INFO *ct = &Tracks[track]; + + if(lba >= (ct->LBA - ct->pregap_dv - ct->pregap) && lba < (ct->LBA + ct->sectors + ct->postgap)) + { + TrackFound = TRUE_1; + + // 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) + { + throw(MDFN_Error(0, _("Could not find track for sector %u!"), lba)); + } + +#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(any_mismatch) + puts("\n"); + } +#endif + + //subq_deinterleave(buf + 2352, qbuf); + //printf("%02x\n", qbuf[0]); + //printf("%02x\n", buf[12 + 3]); +} + +// +// Note: this function makes use of the current contents(as in |=) in SubPWBuf. +// +void CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) +{ + uint8 buf[0xC]; + int32 track; + uint32 lba_relative; + uint32 ma, sa, fa; + uint32 m, s, f; + uint8 pause_or = 0x00; + bool track_found = false; + + 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)) + { + 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; + } + + lba_relative = abs((int32)lba - Tracks[track].LBA); + + 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 adr = 0x1; // Q channel data encodes position + uint8 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)) + { + //printf("pause_or = 0x80 --- %d\n", lba); + pause_or = 0x80; + } + + // Handle pregap between audio->data track + { + int32 pg_offset = (int32)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). + // + // TODO: Look into how we're supposed to handle subq control field in the four combinations of track types(data/audio). + // + if(pg_offset < -150) + { + if((Tracks[track].subq_control & SUBQ_CTRLF_DATA) && (FirstTrack < track) && !(Tracks[track - 1].subq_control & SUBQ_CTRLF_DATA)) + { + //printf("Pregap part 1 audio->data: lba=%d track_lba=%d\n", lba, Tracks[track].LBA); + control = Tracks[track - 1].subq_control; + } + } + } + + + 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); + + // 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); + + if(!SubQReplaceMap.empty()) + { + //printf("%d\n", lba); + auto it = SubQReplaceMap.find(LBA_to_ABA(lba)); + + if(it != SubQReplaceMap.end()) + { + //printf("Replace: %d\n", lba); + memcpy(buf, it->second.data(), 12); + } + } + + for(int i = 0; i < 96; i++) + SubPWBuf[i] |= (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | pause_or; +} + +void CDAccess_Image::Read_TOC(TOC *toc) +{ + toc->Clear(); + + toc->first_track = FirstTrack; + toc->last_track = FirstTrack + NumTracks - 1; + toc->disc_type = disc_type; + + for(int i = toc->first_track; i <= toc->last_track; i++) + { + toc->tracks[i].lba = Tracks[i].LBA; + toc->tracks[i].adr = ADR_CURPOS; + toc->tracks[i].control = Tracks[i].subq_control; + } + + 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]; +} + +bool CDAccess_Image::Is_Physical(void) throw() +{ + return(false); +} + +void CDAccess_Image::Eject(bool eject_status) +{ + +} + diff --git a/psx/mednadisc/cdrom/CDAccess_Image.h b/psx/mednadisc/cdrom/CDAccess_Image.h new file mode 100644 index 0000000000..9f3108c4f8 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess_Image.h @@ -0,0 +1,102 @@ +#ifndef __MDFN_CDACCESS_IMAGE_H +#define __MDFN_CDACCESS_IMAGE_H + +#include +#include + +class Stream; +class AudioReader; + +struct CDRFILE_TRACK_INFO +{ + int32 LBA; + + uint32 DIFormat; + uint8 subq_control; + + int32 pregap; + int32 pregap_dv; + + int32 postgap; + + int32 index[2]; + + int32 sectors; // Not including pregap sectors! + Stream *fp; + bool FirstFileInstance; + bool RawAudioMSBFirst; + long FileOffset; + unsigned int SubchannelMode; + + uint32 LastSamplePos; + + AudioReader *AReader; +}; +#if 0 +struct Medium_Chunk +{ + int64 Offset; // Offset in [..TODO..] + uint32 DIFormat; + + FILE *fp; + bool FirstFileInstance; + bool RawAudioMSBFirst; + unsigned int SubchannelMode; + + uint32 LastSamplePos; + AudioReader *AReader; +}; + +struct CD_Chunk +{ + int32 LBA; + int32 Track; + int32 Index; + bool DataType; + + Medium_Chunk Medium; +}; + +static std::vector Chunks; +#endif + +class CDAccess_Image : public CDAccess +{ + public: + + CDAccess_Image(const std::string& path, bool image_memcache); + virtual ~CDAccess_Image(); + + virtual void Read_Raw_Sector(uint8 *buf, int32 lba); + + virtual void Read_TOC(CDUtility::TOC *toc); + + virtual bool Is_Physical(void) throw(); + + virtual void Eject(bool eject_status); + private: + + int32 NumTracks; + int32 FirstTrack; + int32 LastTrack; + int32 total_sectors; + uint8 disc_type; + CDRFILE_TRACK_INFO Tracks[100]; // Track #0(HMM?) through 99 + + std::map> SubQReplaceMap; + + std::string base_dir; + + void ImageOpen(const std::string& path, bool image_memcache); + void LoadSBI(const std::string& sbi_path); + void Cleanup(void); + + // MakeSubPQ will OR the simulated P and Q subchannel data into SubPWBuf. + void MakeSubPQ(int32 lba, uint8 *SubPWBuf); + + void 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 GetSectorCount(CDRFILE_TRACK_INFO *track); +}; + + +#endif diff --git a/psx/mednadisc/cdrom/CDAccess_Physical.cpp b/psx/mednadisc/cdrom/CDAccess_Physical.cpp new file mode 100644 index 0000000000..b8af20bac9 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess_Physical.cpp @@ -0,0 +1,456 @@ +/* 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 + */ + +#define EXTERNAL_LIBCDIO_CONFIG_H 1 + +#include "../mednafen.h" +#include "../general.h" + +#include "CDAccess.h" +#include "CDAccess_Physical.h" + +#include +#include +#include +#include + +#include +#include +#include + +#if LIBCDIO_VERSION_NUM >= 83 +#include +#endif + +using namespace CDUtility; + +static bool Logging = false; +static std::string LogMessage; +static void LogHandler(cdio_log_level_t level, const char message[]) +{ + if(!Logging) + return; + + try + { + if(LogMessage.size() > 0) + LogMessage.append(" - "); + + LogMessage.append(message); + } + catch(...) // Don't throw exceptions through libcdio's code. + { + LogMessage.clear(); + } +} + +static INLINE void StartLogging(void) +{ + Logging = true; + LogMessage.clear(); +} + +static INLINE void ClearLogging(void) +{ + LogMessage.clear(); +} + +static INLINE std::string StopLogging(void) +{ + std::string ret = LogMessage; + + Logging = false; + LogMessage.clear(); + + return(ret); +} + +void CDAccess_Physical::DetermineFeatures(void) +{ + uint8 buf[256]; + + mmc_cdb_t cdb = {{0, }}; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_MODE_SENSE_10); + + memset(buf, 0, sizeof(buf)); + + cdb.field[2] = 0x2A; + + cdb.field[7] = sizeof(buf) >> 8; + cdb.field[8] = sizeof(buf) & 0xFF; + + StartLogging(); + if(mmc_run_cmd ((CdIo *)p_cdio, MMC_TIMEOUT_DEFAULT, + &cdb, + SCSI_MMC_DATA_READ, + sizeof(buf), + buf)) + { + throw(MDFN_Error(0, _("MMC [MODE SENSE 10] command failed: %s"), StopLogging().c_str())); + } + else + { + const uint8 *pd = &buf[8]; + + StopLogging(); + + if(pd[0] != 0x2A || pd[1] < 0x14) + { + throw(MDFN_Error(0, _("MMC [MODE SENSE 10] command returned bogus data for mode page 0x2A."))); + } + + if(!(pd[4] & 0x10)) + { + throw(MDFN_Error(0, _("Drive does not support reading Mode 2 Form 1 sectors."))); + } + + if(!(pd[4] & 0x20)) + { + throw(MDFN_Error(0, _("Drive does not support reading Mode 2 Form 2 sectors."))); + } + + if(!(pd[5] & 0x01)) + { + throw(MDFN_Error(0, _("Reading CD-DA sectors via \"READ CD\" is not supported."))); + } + + if(!(pd[5] & 0x02)) + { + throw(MDFN_Error(0, _("Read CD-DA sectors via \"READ CD\" are not positionally-accurate."))); + } + + if(!(pd[5] & 0x04)) + { + throw(MDFN_Error(0, _("Reading raw subchannel data via \"READ CD\" is not supported."))); + } + } +} + +void CDAccess_Physical::PreventAllowMediumRemoval(bool prevent) +{ +#if 0 + mmc_cdb_t cdb = {{0, }}; + uint8 buf[8]; + + cdb.field[0] = 0x1E; + cdb.field[1] = 0x00; + cdb.field[2] = 0x00; + cdb.field[3] = 0x00; + cdb.field[4] = 0x00; //prevent; + cdb.field[5] = 0x00; + + printf("%d\n", mmc_run_cmd_len (p_cdio, MMC_TIMEOUT_DEFAULT, + &cdb, 6, + SCSI_MMC_DATA_READ, 0, buf)); + assert(0); +#endif +} + + +// To be used in the future for constructing semi-raw TOC data. +#if 0 +static uint8 cond_hex_to_bcd(uint8 val) +{ + if( ((val & 0xF) > 0x9) || ((val & 0xF0) > 0x90) ) + return val; + + return U8_to_BCD(val); +} +#endif + +void CDAccess_Physical::ReadPhysDiscInfo(unsigned retry) +{ + mmc_cdb_t cdb = {{0, }}; + std::vector toc_buffer; + int64 start_time = time(NULL); + int cdio_rc; + + toc_buffer.resize(0x3FFF); // (2**(8 * 2 - 1 - 1)) - 1, in case the drive has buggy firmware which chops upper bits off or overflows with values near + // the max of a 16-bit signed value + + cdb.field[0] = 0x43; // Read TOC + cdb.field[1] = 0x00; + cdb.field[2] = 0x02; // Format 0010b + cdb.field[3] = 0x00; + cdb.field[4] = 0x00; + cdb.field[5] = 0x00; + cdb.field[6] = 0x01; // First session number + cdb.field[7] = toc_buffer.size() >> 8; + cdb.field[8] = toc_buffer.size() & 0xFF; + cdb.field[9] = 0x00; + + StartLogging(); + while((cdio_rc = mmc_run_cmd ((CdIo *)p_cdio, MMC_TIMEOUT_DEFAULT, + &cdb, + SCSI_MMC_DATA_READ, + toc_buffer.size(), + &toc_buffer[0]))) + { + if(!retry || time(NULL) >= (start_time + retry)) + { + throw(MDFN_Error(0, _("Error reading disc TOC: %s"), StopLogging().c_str())); + } + else + ClearLogging(); + } + StopLogging(); + + PhysTOC.Clear(); + + { + int32 len_counter = MDFN_de16msb(&toc_buffer[0]) - 2; + uint8 *tbi = &toc_buffer[4]; + + if(len_counter < 0 || (len_counter % 11) != 0) + throw MDFN_Error(0, _("READ TOC command response data is of an invalid length.")); + + while(len_counter) + { + // Ref: MMC-3 draft revision 10g, page 221 + uint8 sess MDFN_NOWARN_UNUSED = tbi[0]; + uint8 adr_ctrl = tbi[1]; + uint8 tno MDFN_NOWARN_UNUSED = tbi[2]; + uint8 point = tbi[3]; + uint8 min MDFN_NOWARN_UNUSED = tbi[4]; + uint8 sec MDFN_NOWARN_UNUSED = tbi[5]; + uint8 frame MDFN_NOWARN_UNUSED = tbi[6]; + uint8 hour_phour MDFN_NOWARN_UNUSED = tbi[7]; + uint8 pmin = tbi[8]; + uint8 psec = tbi[9]; + uint8 pframe = tbi[10]; + + if((adr_ctrl >> 4) == 1) + { + switch(((adr_ctrl >> 4) << 8) | point) + { + case 0x101 ... 0x163: + PhysTOC.tracks[point].adr = adr_ctrl >> 4; + PhysTOC.tracks[point].control = adr_ctrl & 0xF; + PhysTOC.tracks[point].lba = AMSF_to_LBA(pmin, psec, pframe); + break; + + case 0x1A0: + PhysTOC.first_track = pmin; + PhysTOC.disc_type = psec; + break; + + case 0x1A1: + PhysTOC.last_track = pmin; + break; + + case 0x1A2: + PhysTOC.tracks[100].adr = adr_ctrl >> 4; + PhysTOC.tracks[100].control = adr_ctrl & 0xF; + PhysTOC.tracks[100].lba = AMSF_to_LBA(pmin, psec, pframe); + break; + + default: + //MDFN_printf("%02x %02x\n", adr_ctrl >> 4, point); + break; + } + } + + tbi += 11; + len_counter -= 11; + } + } + + + if(PhysTOC.first_track < 1 || PhysTOC.first_track > 99) + { + throw(MDFN_Error(0, _("Invalid first track: %d\n"), PhysTOC.first_track)); + } + + if(PhysTOC.last_track > 99 || PhysTOC.last_track < PhysTOC.first_track) + { + throw(MDFN_Error(0, _("Invalid last track: %d\n"), PhysTOC.last_track)); + } + + // Convenience leadout track duplication. + if(PhysTOC.last_track < 99) + PhysTOC.tracks[PhysTOC.last_track + 1] = PhysTOC.tracks[100]; +} + +void CDAccess_Physical::Read_TOC(TOC *toc) +{ + *toc = PhysTOC; +} + +void CDAccess_Physical::Read_Raw_Sector(uint8 *buf, int32 lba) +{ + mmc_cdb_t cdb = {{0, }}; + int cdio_rc; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_READ_CD); + CDIO_MMC_SET_READ_TYPE (cdb.field, CDIO_MMC_READ_TYPE_ANY); + CDIO_MMC_SET_READ_LBA (cdb.field, lba); + CDIO_MMC_SET_READ_LENGTH24(cdb.field, 1); + + StartLogging(); + if(SkipSectorRead[(lba >> 3) & 0xFFFF] & (1 << (lba & 7))) + { + printf("Read(skipped): %d\n", lba); + memset(buf, 0, 2352); + + cdb.field[9] = 0x00; + cdb.field[10] = 0x01; + + if((cdio_rc = mmc_run_cmd ((CdIo *)p_cdio, MMC_TIMEOUT_DEFAULT, + &cdb, + SCSI_MMC_DATA_READ, + 96, + buf + 2352))) + { + throw(MDFN_Error(0, _("MMC Read Error: %s"), StopLogging().c_str())); + } + } + else + { + cdb.field[9] = 0xF8; + cdb.field[10] = 0x01; + + if((cdio_rc = mmc_run_cmd ((CdIo *)p_cdio, MMC_TIMEOUT_DEFAULT, + &cdb, + SCSI_MMC_DATA_READ, + 2352 + 96, + buf))) + { + throw(MDFN_Error(0, _("MMC Read Error: %s"), StopLogging().c_str())); + } + } + StopLogging(); +} + +CDAccess_Physical::CDAccess_Physical(const std::string& path) +{ + char **devices = NULL; + char **parseit = NULL; + + p_cdio = NULL; + + cdio_init(); + cdio_log_set_handler(LogHandler); + +// +// +// + try + { + devices = cdio_get_devices(DRIVER_DEVICE); + parseit = devices; + if(parseit) + { + MDFN_printf(_("Connected physical devices:\n")); + MDFN_indent(1); + while(*parseit) + { + MDFN_printf("%s\n", *parseit); + parseit++; + } + MDFN_indent(-1); + } + + if(!parseit || parseit == devices) + { + throw(MDFN_Error(0, _("No CDROM drives detected(or no disc present)."))); + } + + if(devices) + { + cdio_free_device_list(devices); + devices = NULL; + } + + StartLogging(); + p_cdio = cdio_open_cd(path.c_str()); + if(!p_cdio) + { + throw(MDFN_Error(0, _("Error opening physical CD: %s"), StopLogging().c_str())); + } + StopLogging(); + + //PreventAllowMediumRemoval(true); + ReadPhysDiscInfo(0); + + // + // Determine how we can read this CD. + // + DetermineFeatures(); + + memset(SkipSectorRead, 0, sizeof(SkipSectorRead)); + } + catch(std::exception &e) + { + if(devices) + cdio_free_device_list(devices); + + if(p_cdio) + cdio_destroy((CdIo *)p_cdio); + + throw; + } +} + +CDAccess_Physical::~CDAccess_Physical() +{ + cdio_destroy((CdIo *)p_cdio); +} + +bool CDAccess_Physical::Is_Physical(void) throw() +{ + return(true); +} + +void CDAccess_Physical::Eject(bool eject_status) +{ + int cdio_rc; + + StartLogging(); +#if LIBCDIO_VERSION_NUM >= 83 + if((cdio_rc = mmc_start_stop_unit((CdIo *)p_cdio, eject_status, false, 0, 0)) != 0) + { + if(cdio_rc != DRIVER_OP_UNSUPPORTED) // Don't error out if it's just an unsupported operation. + throw(MDFN_Error(0, _("Error ejecting medium: %s"), StopLogging().c_str())); + } +#else + if((cdio_rc = mmc_start_stop_media((CdIo *)p_cdio, eject_status, false, 0)) != 0) + { + if(cdio_rc != DRIVER_OP_UNSUPPORTED) // Don't error out if it's just an unsupported operation. + throw(MDFN_Error(0, _("Error ejecting medium: %s"), StopLogging().c_str())); + } +#endif + StopLogging(); + + if(!eject_status) + { + try + { + ReadPhysDiscInfo(10); + } + catch(std::exception &e) + { +#if LIBCDIO_VERSION_NUM >= 83 + mmc_start_stop_unit((CdIo *)p_cdio, true, false, 0, 0); // Eject disc, if possible. +#else + mmc_start_stop_media((CdIo *)p_cdio, true, false, 0); // Eject disc, if possible. +#endif + throw; + } + } +} + diff --git a/psx/mednadisc/cdrom/CDAccess_Physical.h b/psx/mednadisc/cdrom/CDAccess_Physical.h new file mode 100644 index 0000000000..562b79f479 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAccess_Physical.h @@ -0,0 +1,39 @@ +#ifndef __MDFN_CDACCESS_PHYSICAL_H +#define __MDFN_CDACCESS_PHYSICAL_H + +// +// This class's methods are NOT re-entrant! +// + +// Don't include here, else it will pollute with its #define's. + +class CDAccess_Physical : public CDAccess +{ + public: + + CDAccess_Physical(const std::string& path); + virtual ~CDAccess_Physical(); + + virtual void Read_Raw_Sector(uint8 *buf, int32 lba); + + virtual void Read_TOC(CDUtility::TOC *toc); + + virtual bool Is_Physical(void) throw(); + + virtual void Eject(bool eject_status); + private: + + void *p_cdio; + + void DetermineFeatures(void); + void ReadPhysDiscInfo(unsigned retry); + + void PreventAllowMediumRemoval(bool prevent); + + CDUtility::TOC PhysTOC; + + // TODO: 1-bit per sector on the physical CD. If set, don't read that sector. + uint8 SkipSectorRead[65536]; +}; + +#endif diff --git a/psx/mednadisc/cdrom/CDUtility.cpp b/psx/mednadisc/cdrom/CDUtility.cpp new file mode 100644 index 0000000000..c027535237 --- /dev/null +++ b/psx/mednadisc/cdrom/CDUtility.cpp @@ -0,0 +1,324 @@ +/* Mednafen - Multi-system Emulator + * + * Subchannel Q CRC Code: Copyright (C) 1998 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include "emuware/emuware.h" +#include "CDUtility.h" +#include "dvdisaster.h" +#include "lec.h" + +// Kill_LEC_Correct(); + + +namespace CDUtility +{ + +// lookup table for crc calculation +static uint16 subq_crctab[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, + 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, + 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, + 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, + 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, + 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, + 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, + 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, + 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, + 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, + 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, + 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, + 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, + 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, + 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, + 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, + 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, + 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, + 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, + 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, + 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, + 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, + 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, + 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, + 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + + +static uint8 scramble_table[2352 - 12]; + +static bool CDUtility_Inited = false; + +static void InitScrambleTable(void) +{ + unsigned cv = 1; + + for(unsigned i = 12; i < 2352; i++) + { + unsigned char z = 0; + + for(int b = 0; b < 8; b++) + { + z |= (cv & 1) << b; + + int feedback = ((cv >> 1) & 1) ^ (cv & 1); + cv = (cv >> 1) | (feedback << 14); + } + + 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(); + + InitScrambleTable(); + + CDUtility_Inited = true; + } +} + +void encode_mode0_sector(uint32 aba, uint8 *sector_data) +{ + CDUtility_Init(); + + lec_encode_mode0_sector(aba, sector_data); +} + +void encode_mode1_sector(uint32 aba, uint8 *sector_data) +{ + CDUtility_Init(); + + lec_encode_mode1_sector(aba, sector_data); +} + +void encode_mode2_sector(uint32 aba, uint8 *sector_data) +{ + CDUtility_Init(); + + lec_encode_mode2_sector(aba, sector_data); +} + +void encode_mode2_form1_sector(uint32 aba, uint8 *sector_data) +{ + CDUtility_Init(); + + lec_encode_mode2_form1_sector(aba, sector_data); +} + +void encode_mode2_form2_sector(uint32 aba, uint8 *sector_data) +{ + CDUtility_Init(); + + lec_encode_mode2_form2_sector(aba, sector_data); +} + +bool edc_check(const uint8 *sector_data, bool xa) +{ + CDUtility_Init(); + + return(CheckEDC(sector_data, xa)); +} + +bool edc_lec_check_and_correct(uint8 *sector_data, bool xa) +{ + CDUtility_Init(); + + return(ValidateRawSector(sector_data, xa)); +} + + +bool subq_check_checksum(const uint8 *SubQBuf) +{ + uint16 crc = 0; + uint16 stored_crc = 0; + + stored_crc = SubQBuf[0xA] << 8; + stored_crc |= SubQBuf[0xB]; + + for(int i = 0; i < 0xA; i++) + crc = subq_crctab[(crc >> 8) ^ SubQBuf[i]] ^ (crc << 8); + + crc = ~crc; + + return(crc == stored_crc); +} + +void subq_generate_checksum(uint8 *buf) +{ + uint16 crc = 0; + + for(int i = 0; i < 0xA; i++) + crc = subq_crctab[(crc >> 8) ^ buf[i]] ^ (crc << 8); + + // Checksum + buf[0xa] = ~(crc >> 8); + buf[0xb] = ~(crc); +} + +void subq_deinterleave(const uint8 *SubPWBuf, uint8 *qbuf) +{ + memset(qbuf, 0, 0xC); + + for(int i = 0; i < 96; i++) + { + qbuf[i >> 3] |= ((SubPWBuf[i] >> 6) & 0x1) << (7 - (i & 0x7)); + } +} + + +// Deinterleaves 96 bytes of subchannel P-W data from 96 bytes of interleaved subchannel PW data. +void subpw_deinterleave(const uint8 *in_buf, uint8 *out_buf) +{ + assert(in_buf != out_buf); + + memset(out_buf, 0, 96); + + for(unsigned ch = 0; ch < 8; ch++) + { + for(unsigned 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 *in_buf, uint8 *out_buf) +{ + assert(in_buf != out_buf); + + for(unsigned d = 0; d < 12; d++) + { + for(unsigned bitpoodle = 0; bitpoodle < 8; bitpoodle++) + { + uint8 rawb = 0; + + for(unsigned ch = 0; ch < 8; ch++) + { + rawb |= ((in_buf[ch * 12 + d] >> (7 - bitpoodle)) & 1) << (7 - ch); + } + out_buf[(d << 3) + bitpoodle] = rawb; + } + } +} + +// NOTES ON LEADOUT AREA SYNTHESIS +// +// I'm not trusting that the "control" field for the TOC leadout entry will always be set properly, so | the control fields for the last track entry +// 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 TOC& toc, const int32 lba, uint8* SubPWBuf) +{ + uint8 buf[0xC]; + uint32 lba_relative; + uint32 ma, sa, fa; + uint32 m, s, f; + + lba_relative = lba - toc.tracks[100].lba; + + 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 adr = 0x1; // Q channel data encodes position + uint8 control = (toc.tracks[toc.last_track].control & 0x4) | toc.tracks[100].control; + + memset(buf, 0, 0xC); + buf[0] = (adr << 0) | (control << 4); + buf[1] = 0xAA; + buf[2] = 0x01; + + // 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_leadout_sector_lba(const uint8 mode, const TOC& toc, const int32 lba, uint8* out_buf) +{ + memset(out_buf, 0, 2352 + 96); + subpw_synth_leadout_lba(toc, lba, out_buf + 2352); + + if((toc.tracks[toc.last_track].control | toc.tracks[100].control) & 0x4) + { + 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[18] = 0x20; + encode_mode2_form2_sector(LBA_to_ABA(lba), out_buf); + break; + } + } +} + +#if 0 +bool subq_extrapolate(const uint8 *subq_input, int32 position_delta, uint8 *subq_output) +{ + assert(subq_check_checksum(subq_input)); + + + subq_generate_checksum(subq_output); +} +#endif + +void scrambleize_data_sector(uint8 *sector_data) +{ + for(unsigned i = 12; i < 2352; i++) + sector_data[i] ^= scramble_table[i - 12]; +} + +} diff --git a/psx/mednadisc/cdrom/CDUtility.h b/psx/mednadisc/cdrom/CDUtility.h new file mode 100644 index 0000000000..8570fd24d3 --- /dev/null +++ b/psx/mednadisc/cdrom/CDUtility.h @@ -0,0 +1,225 @@ +#ifndef __MDFN_CDROM_CDUTILITY_H +#define __MDFN_CDROM_CDUTILITY_H + +namespace CDUtility +{ + // 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); + + // 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 + + + enum + { + ADR_NOQINFO = 0x00, + ADR_CURPOS = 0x01, + ADR_MCN = 0x02, + ADR_ISRC = 0x03 + }; + + + struct TOC_Track + { + uint8 adr; + uint8 control; + uint32 lba; + }; + + // 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. + }; + + enum + { + DISC_TYPE_CDDA_OR_M1 = 0x00, + DISC_TYPE_CD_I = 0x10, + DISC_TYPE_CD_XA = 0x20 + }; + + struct TOC + { + INLINE TOC() + { + Clear(); + } + + INLINE void Clear(void) + { + first_track = last_track = 0; + disc_type = 0; + + memset(tracks, 0, sizeof(tracks)); // FIXME if we change TOC_Track to non-POD type. + } + + INLINE int FindTrackByLBA(uint32 LBA) + { + for(int32 track = first_track; track <= (last_track + 1); track++) + { + if(track == (last_track + 1)) + { + if(LBA < tracks[100].lba) + return(track - 1); + } + else + { + if(LBA < tracks[track].lba) + return(track - 1); + } + } + return(0); + } + + uint8 first_track; + uint8 last_track; + uint8 disc_type; + TOC_Track tracks[100 + 1]; // [0] is unused, [100] is for the leadout track. + // Also, for convenience, tracks[last_track + 1] will always refer + // to the leadout track(even if last_track < 99, IE the leadout track details are duplicated). + }; + + // + // Address conversion functions. + // + static INLINE uint32 AMSF_to_ABA(int32 m_a, int32 s_a, int32 f_a) + { + return(f_a + 75 * s_a + 75 * 60 * m_a); + } + + static INLINE void ABA_to_AMSF(uint32 aba, uint8 *m_a, uint8 *s_a, uint8 *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 int32 ABA_to_LBA(uint32 aba) + { + return(aba - 150); + } + + static INLINE uint32 LBA_to_ABA(int32 lba) + { + return(lba + 150); + } + + static INLINE int32 AMSF_to_LBA(uint8 m_a, uint8 s_a, uint8 f_a) + { + return(ABA_to_LBA(AMSF_to_ABA(m_a, s_a, f_a))); + } + + static INLINE void LBA_to_AMSF(int32 lba, uint8 *m_a, uint8 *s_a, uint8 *f_a) + { + ABA_to_AMSF(LBA_to_ABA(lba), m_a, s_a, f_a); + } + + // + // BCD conversion functions + // + static INLINE bool BCD_is_valid(uint8 bcd_number) + { + if((bcd_number & 0xF0) >= 0xA0) + return(false); + + if((bcd_number & 0x0F) >= 0x0A) + return(false); + + return(true); + } + + static INLINE uint8 BCD_to_U8(uint8 bcd_number) + { + return( ((bcd_number >> 4) * 10) + (bcd_number & 0x0F) ); + } + + static INLINE uint8 U8_to_BCD(uint8 num) + { + return( ((num / 10) << 4) + (num % 10) ); + } + + // should always perform the conversion, even if the bcd number is invalid. + static INLINE bool BCD_to_U8_check(uint8 bcd_number, uint8 *out_number) + { + *out_number = BCD_to_U8(bcd_number); + + if(!BCD_is_valid(bcd_number)) + return(false); + + 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 aba, uint8 *sector_data); + void encode_mode1_sector(uint32 aba, uint8 *sector_data); // 2048 bytes of user data at offset 16 + void encode_mode2_sector(uint32 aba, uint8 *sector_data); // 2336 bytes of user data at offset 16 + void encode_mode2_form1_sector(uint32 aba, uint8 *sector_data); // 2048+8 bytes of user data at offset 16 + void encode_mode2_form2_sector(uint32 aba, uint8 *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(const uint8 mode, const TOC& toc, const int32 lba, uint8* out_buf); + + // + // User data error detection and correction + // + + // 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 *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 *sector_data, bool xa); + + // + // Subchannel(Q in particular) functions + // + + // Returns false on checksum mismatch, true on match. + bool subq_check_checksum(const uint8 *subq_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 *subq_buf); + + // Deinterleaves 12 bytes of subchannel Q data from 96 bytes of interleaved subchannel PW data. + void subq_deinterleave(const uint8 *subpw_buf, uint8 *subq_buf); + + // Deinterleaves 96 bytes of subchannel P-W data from 96 bytes of interleaved subchannel PW data. + void subpw_deinterleave(const uint8 *in_buf, uint8 *out_buf); + + // Interleaves 96 bytes of subchannel P-W data from 96 bytes of uninterleaved subchannel PW data. + void subpw_interleave(const uint8 *in_buf, uint8 *out_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 *subq_input, int32 position_delta, uint8 *subq_output); + + // (De)Scrambles data sector. + void scrambleize_data_sector(uint8 *sector_data); +} + +#endif diff --git a/psx/mednadisc/cdrom/Makefile.am.inc b/psx/mednadisc/cdrom/Makefile.am.inc new file mode 100644 index 0000000000..daf38931ee --- /dev/null +++ b/psx/mednadisc/cdrom/Makefile.am.inc @@ -0,0 +1,7 @@ +mednafen_SOURCES += cdrom/audioreader.cpp cdrom/cdromif.cpp cdrom/scsicd.cpp +mednafen_SOURCES += cdrom/CDUtility.cpp cdrom/crc32.cpp cdrom/galois.cpp cdrom/l-ec.cpp cdrom/recover-raw.cpp +mednafen_SOURCES += cdrom/lec.cpp cdrom/CDAccess.cpp cdrom/CDAccess_Image.cpp cdrom/CDAccess_CCD.cpp + +if HAVE_LIBCDIO +mednafen_SOURCES += cdrom/CDAccess_Physical.cpp +endif diff --git a/psx/mednadisc/cdrom/SimpleFIFO.h b/psx/mednadisc/cdrom/SimpleFIFO.h new file mode 100644 index 0000000000..edc8f03e36 --- /dev/null +++ b/psx/mednadisc/cdrom/SimpleFIFO.h @@ -0,0 +1,140 @@ +#ifndef __MDFN_SIMPLEFIFO_H +#define __MDFN_SIMPLEFIFO_H + +#include +#include + +#include "../math_ops.h" + +template +class SimpleFIFO +{ + public: + + // Constructor + SimpleFIFO(uint32 the_size) // Size should be a power of 2! + { + data.resize(round_up_pow2(the_size)); + size = the_size; + read_pos = 0; + write_pos = 0; + in_count = 0; + } + + // Destructor + INLINE ~SimpleFIFO() + { + + } + + 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); + } + + INLINE uint32 CanWrite(void) + { + return(size - in_count); + } + + INLINE T ReadUnit(bool peek = false) + { + T ret; + + assert(in_count > 0); + + ret = data[read_pos]; + + if(!peek) + { + read_pos = (read_pos + 1) & (data.size() - 1); + in_count--; + } + + return(ret); + } + + INLINE uint8 ReadByte(bool peek = false) + { + assert(sizeof(T) == 1); + + return(ReadUnit(peek)); + } + + INLINE void Write(const T *happy_data, uint32 happy_count) + { + assert(CanWrite() >= happy_count); + + while(happy_count) + { + data[write_pos] = *happy_data; + + write_pos = (write_pos + 1) & (data.size() - 1); + in_count++; + happy_data++; + happy_count--; + } + } + + INLINE void WriteUnit(const T& wr_data) + { + Write(&wr_data, 1); + } + + INLINE void WriteByte(const T& wr_data) + { + assert(sizeof(T) == 1); + Write(&wr_data, 1); + } + + + INLINE void Flush(void) + { + read_pos = 0; + write_pos = 0; + in_count = 0; + } + + //private: + std::vector data; + uint32 size; + uint32 read_pos; // Read position + uint32 write_pos; // Write position + uint32 in_count; // Number of units in the FIFO +}; + + +#endif diff --git a/psx/mednadisc/cdrom/audioreader.cpp b/psx/mednadisc/cdrom/audioreader.cpp new file mode 100644 index 0000000000..86b544417b --- /dev/null +++ b/psx/mednadisc/cdrom/audioreader.cpp @@ -0,0 +1,617 @@ +/* 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 "emuware/emuware.h" +#include "audioreader.h" + +#include +#include + +#ifdef HAVE_AUDIOREADER +#include "../tremor/ivorbisfile.h" +#include "../mpcdec/mpcdec.h" +#endif + +#ifdef HAVE_LIBSNDFILE +#include +#endif + +#ifdef HAVE_OPUSFILE +#include "audioreader_opus.h" +#endif + +#include +#include +#include + +#include "../general.h" +#include "../endian.h" + +AudioReader::AudioReader() : LastReadPos(0) +{ + +} + +AudioReader::~AudioReader() +{ + +} + +int64 AudioReader::Read_(int16 *buffer, int64 frames) +{ + abort(); + return(false); +} + +bool AudioReader::Seek_(int64 frame_offset) +{ + abort(); + return(false); +} + +int64 AudioReader::FrameCount(void) +{ + abort(); + return(0); +} + +/* +** +** +** +** +** +** +** +** +** +*/ + +#ifdef HAVE_AUDIOREADER + +class OggVorbisReader : public AudioReader +{ + public: + OggVorbisReader(Stream *fp); + ~OggVorbisReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + 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); + + try + { + return fw->read(ptr, size * nmemb, false) / size; + } + catch(...) + { + return(0); + } +} + +static int iov_seek_func(void *user_data, ogg_int64_t offset, int whence) +{ + Stream *fw = (Stream*)user_data; + + try + { + fw->seek(offset, whence); + return(0); + } + catch(...) + { + return(-1); + } +} + +static int iov_close_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + fw->close(); + return(0); + } + catch(...) + { + return EOF; + } +} + +static long iov_tell_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->tell(); + } + catch(...) + { + return(-1); + } +} + +OggVorbisReader::OggVorbisReader(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; + + fp->seek(0, SEEK_SET); + if(ov_open_callbacks(fp, &ovfile, NULL, 0, cb)) + throw(0); +} + +OggVorbisReader::~OggVorbisReader() +{ + ov_clear(&ovfile); +} + +int64 OggVorbisReader::Read_(int16 *buffer, int64 frames) +{ + uint8 *tw_buf = (uint8 *)buffer; + int cursection = 0; + long toread = frames * sizeof(int16) * 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) / 2); +} + +bool OggVorbisReader::Seek_(int64 frame_offset) +{ + ov_pcm_seek(&ovfile, frame_offset); + return(true); +} + +int64 OggVorbisReader::FrameCount(void) +{ + return(ov_pcm_total(&ovfile, -1)); +} + +class MPCReader : public AudioReader +{ + public: + MPCReader(Stream *fp); + ~MPCReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + private: + mpc_reader reader; + mpc_demux *demux; + mpc_streaminfo si; + + MPC_SAMPLE_FORMAT MPCBuffer[MPC_DECODER_BUFFER_LENGTH]; + + uint32 MPCBufferIn; + uint32 MPCBufferOffs; + Stream *fw; +}; + + +/// Reads size bytes of data into buffer at ptr. +static mpc_int32_t impc_read(mpc_reader *p_reader, void *ptr, mpc_int32_t size) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + return fw->read(ptr, size, false); + } + catch(...) + { + return(MPC_STATUS_FAIL); + } +} + +/// Seeks to byte position offset. +static mpc_bool_t impc_seek(mpc_reader *p_reader, mpc_int32_t offset) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + fw->seek(offset, SEEK_SET); + return(MPC_TRUE); + } + catch(...) + { + return(MPC_FALSE); + } +} + +/// Returns the current byte offset in the stream. +static mpc_int32_t impc_tell(mpc_reader *p_reader) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + return fw->tell(); + } + catch(...) + { + return(MPC_STATUS_FAIL); + } +} + +/// Returns the total length of the source stream, in bytes. +static mpc_int32_t impc_get_size(mpc_reader *p_reader) +{ + Stream *fw = (Stream*)(p_reader->data); + + try + { + return fw->size(); + } + catch(...) + { + return(MPC_STATUS_FAIL); + } +} + +/// True if the stream is a seekable stream. +static mpc_bool_t impc_canseek(mpc_reader *p_reader) +{ + return(MPC_TRUE); +} + +MPCReader::MPCReader(Stream *fp) : fw(fp) +{ + fp->seek(0, SEEK_SET); + + 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); + } +} + +MPCReader::~MPCReader() +{ + if(demux) + { + mpc_demux_exit(demux); + demux = NULL; + } +} + +int64 MPCReader::Read_(int16 *buffer, int64 frames) +{ + mpc_status err; + int16 *cowbuf = (int16 *)buffer; + int32 toread = frames * 2; + + while(toread > 0) + { + int32 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++) + { +#ifdef MPC_FIXED_POINT + int32 samp = MPCBuffer[MPCBufferOffs + x] >> MPC_FIXED_POINT_FRACTPART; +#else + #warning Floating-point MPC decoding path not tested. + int32 samp = (int32)(MPCBuffer[MPCBufferOffs + x] * 32767); +#endif + 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 MPCReader::Seek_(int64 frame_offset) +{ + MPCBufferOffs = 0; + MPCBufferIn = 0; + + if(mpc_demux_seek_sample(demux, frame_offset) < 0) + return(false); + + return(true); +} + +int64 MPCReader::FrameCount(void) +{ + return(mpc_streaminfo_get_length_samples(&si)); +} + +#endif //HAVE_AUDIOREADER +/* +** +** +** +** +** +** +** +** +** +*/ + +#ifdef HAVE_LIBSNDFILE +class SFReader : public AudioReader +{ + public: + + SFReader(Stream *fp); + ~SFReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + private: + SNDFILE *sf; + SF_INFO sfinfo; + SF_VIRTUAL_IO sfvf; + + Stream *fw; +}; + +static sf_count_t isf_get_filelen(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->size(); + } + catch(...) + { + return(-1); + } +} + +static sf_count_t isf_seek(sf_count_t offset, int whence, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + //printf("Seek: offset=%lld, whence=%lld\n", (long long)offset, (long long)whence); + + fw->seek(offset, whence); + return fw->tell(); + } + catch(...) + { + //printf(" SEEK FAILED\n"); + return(-1); + } +} + +static sf_count_t isf_read(void *ptr, sf_count_t count, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + sf_count_t ret = fw->read(ptr, count, false); + + //printf("Read: count=%lld, ret=%lld\n", (long long)count, (long long)ret); + + return ret; + } + catch(...) + { + //printf(" READ FAILED\n"); + return(0); + } +} + +static sf_count_t isf_write(const void *ptr, sf_count_t count, void *user_data) +{ + return(0); +} + +static sf_count_t isf_tell(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->tell(); + } + catch(...) + { + return(-1); + } +} + +SFReader::SFReader(Stream *fp) : fw(fp) +{ + fp->seek(0, SEEK_SET); + + memset(&sfvf, 0, sizeof(sfvf)); + sfvf.get_filelen = isf_get_filelen; + sfvf.seek = isf_seek; + sfvf.read = isf_read; + sfvf.write = isf_write; + sfvf.tell = isf_tell; + + memset(&sfinfo, 0, sizeof(sfinfo)); + if(!(sf = sf_open_virtual(&sfvf, SFM_READ, &sfinfo, (void*)fp))) + throw(0); +} + +SFReader::~SFReader() +{ + sf_close(sf); +} + +int64 SFReader::Read_(int16 *buffer, int64 frames) +{ + return(sf_read_short(sf, (short*)buffer, frames * 2) / 2); +} + +bool SFReader::Seek_(int64 frame_offset) +{ + // FIXME error condition + if(sf_seek(sf, frame_offset, SEEK_SET) != frame_offset) + return(false); + return(true); +} + +int64 SFReader::FrameCount(void) +{ + return(sfinfo.frames); +} + +#endif + + +AudioReader *AR_Open(Stream *fp) +{ +#ifdef HAVE_AUDIOREADER + try + { + return new MPCReader(fp); + } + catch(int i) + { + } +#endif + +#ifdef HAVE_OPUSFILE + try + { + return new OpusReader(fp); + } + catch(int i) + { + } +#endif + +#ifdef HAVE_AUDIOREADER + try + { + return new OggVorbisReader(fp); + } + catch(int i) + { + } +#endif + +#ifdef HAVE_LIBSNDFILE + try + { + return new SFReader(fp); + } + catch(int i) + { + } +#endif + + return(NULL); +} + diff --git a/psx/mednadisc/cdrom/audioreader.h b/psx/mednadisc/cdrom/audioreader.h new file mode 100644 index 0000000000..014a3e48d3 --- /dev/null +++ b/psx/mednadisc/cdrom/audioreader.h @@ -0,0 +1,43 @@ +#ifndef __MDFN_AUDIOREADER_H +#define __MDFN_AUDIOREADER_H + +#include "../Stream.h" + +class AudioReader +{ + public: + AudioReader(); + virtual ~AudioReader(); + + virtual int64 FrameCount(void); + INLINE int64 Read(int64 frame_offset, int16 *buffer, int64 frames) + { + int64 ret; + + //if(frame_offset >= 0) + { + 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 Read_(int16 *buffer, int64 frames); + virtual bool Seek_(int64 frame_offset); + + int64 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/psx/mednadisc/cdrom/audioreader_opus.cpp b/psx/mednadisc/cdrom/audioreader_opus.cpp new file mode 100644 index 0000000000..264fde4b06 --- /dev/null +++ b/psx/mednadisc/cdrom/audioreader_opus.cpp @@ -0,0 +1,185 @@ +/* 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 "../mednafen.h" +#include "audioreader.h" +#include "audioreader_opus.h" + +// OPUS SUPPORT NOT DONE YET!!! +/* + + (int64)op_pcm_total() * 44100 / 48000 + + resampling vs seek, filter delay, etc. to consider +*/ + +static size_t iop_read_func(void *ptr, size_t size, size_t nmemb, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + if(!size) + return(0); + + try + { + return fw->read(ptr, size * nmemb, false) / size; + } + catch(...) + { + return(0); + } +} + +static int iop_seek_func(void *user_data, opus_int64 offset, int whence) +{ + Stream *fw = (Stream*)user_data; + + try + { + fw->seek(offset, whence); + return(0); + } + catch(...) + { + return(-1); + } +} + +static int iop_close_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + fw->close(); + return(0); + } + catch(...) + { + return EOF; + } +} + +static opus_int64 iop_tell_func(void *user_data) +{ + Stream *fw = (Stream*)user_data; + + try + { + return fw->tell(); + } + catch(...) + { + return(-1); + } +} + +/* Error strings copied from libopusfile header file comments. */ +static const char *op_errstring(int error) +{ + static const struct + { + int code; + const char *str; + } error_table[] = + { + { OP_EREAD, gettext_noop("OP_EREAD: An underlying read, seek, or tell operation failed when it should have succeeded.") }, + { OP_EFAULT, gettext_noop("OP_EFAULT: A NULL pointer was passed where one was unexpected, or an internal memory allocation failed, or an internal library error was encountered.") }, + { OP_EIMPL, gettext_noop("OP_EIMPL: The stream used a feature that is not implemented, such as an unsupported channel family.") }, + { OP_EINVAL, gettext_noop("OP_EINVAL: One or more parameters to a function were invalid.") }, + { OP_ENOTFORMAT, gettext_noop("OP_ENOTFORMAT: A purported Ogg Opus stream did not begin with an Ogg page, or a purported header packet did not start with one of the required strings, \"OpusHead\" or \"OpusTags\".") }, + { OP_EBADHEADER, gettext_noop("OP_EBADHEADER: A required header packet was not properly formatted, contained illegal values, or was missing altogether.") }, + { OP_EVERSION, gettext_noop("OP_EVERSION: The ID header contained an unrecognized version number.") }, + { OP_EBADPACKET, gettext_noop("OP_EBADPACKET: An audio packet failed to decode properly.") }, + { OP_EBADLINK, gettext_noop("OP_EBADLINK: We failed to find data we had seen before, or the bitstream structure was sufficiently malformed that seeking to the target destination was impossible.") }, + { OP_ENOSEEK, gettext_noop("OP_ENOSEEK: An operation that requires seeking was requested on an unseekable stream.") }, + { OP_EBADTIMESTAMP, gettext_noop("OP_EBADTIMESTAMP: The first or last granule position of a link failed basic validity checks.") }, + }; + + for(unsigned i = 0; i < sizeof(error_table) / sizeof(error_table[0]); i++) + { + if(error_table[i].code == error) + { + return _(error_table[i].str); + } + } + + return _("Unknown"); +} + +OggOpusReader::OggOpusReader(Stream *fp) : fw(fp) +{ + OpusFileCallbacks cb; + int error = 0; + + memset(&cb, 0, sizeof(cb)); + cb.read_func = iop_read_func; + cb.seek_func = iop_seek_func; + cb.close_func = iop_close_func; + cb.tell_func = iop_tell_func; + + fp->seek(0, SEEK_SET); + + if(!(opfile = op_open_callbacks((void*)fp, &cb, NULL, 0, &error))) + { + switch(error) + { + default: + throw MDFN_Error(0, _("opusfile: error code: %d(%s)", error, op_errstring(error))); + break; + + case OP_ENOTFORMAT: + throw(0); + break; + } + } +} + +OggOpusReader::~OggOpusReader() +{ + op_free(opfile); +} + +int64 OggOpusReader::Read_(int16 *buffer, int64 frames) +{ + int16 *tr_buffer = buffer; + int64 tr_count = frames * 2; + + while(tr_count > 0) + { + int64 didread = op_read(opfile, tr_buffer, tr_count, NULL); + + if(didread == 0) + break; + + tr_buffer += didread * 2; + tr_count -= didread * 2; + } + + return(frames - (tr_count / 2)); +} + +bool OggOpusReader::Seek_(int64 frame_offset) +{ + op_pcm_seek(opfile, frame_offset); + return(true); +} + +int64 OggOpusReader::FrameCount(void) +{ + return(op_pcm_total(pvfile, -1)); +} diff --git a/psx/mednadisc/cdrom/audioreader_opus.h b/psx/mednadisc/cdrom/audioreader_opus.h new file mode 100644 index 0000000000..130b3546c7 --- /dev/null +++ b/psx/mednadisc/cdrom/audioreader_opus.h @@ -0,0 +1,21 @@ +#ifndef __MDFN_AUDIOREADER_OPUS_H +#define __MDFN_AUDIOREADER_OPUS_H + +#include + +class OggOpusReader : public AudioReader +{ + public: + OggOpusReader(Stream *fp); + ~OggOpusReader(); + + int64 Read_(int16 *buffer, int64 frames); + bool Seek_(int64 frame_offset); + int64 FrameCount(void); + + private: + OggOpus_File *opfile; + Stream *fw; +}; + +#endif diff --git a/psx/mednadisc/cdrom/cdromif.cpp b/psx/mednadisc/cdrom/cdromif.cpp new file mode 100644 index 0000000000..8d35c88ce0 --- /dev/null +++ b/psx/mednadisc/cdrom/cdromif.cpp @@ -0,0 +1,434 @@ +/* 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 "emuware/emuware.h" + +#include "cdromif.h" +#include "CDAccess.h" +#include "general.h" +#include "error.h" + +//undo gettext stuff +#define _(X) X + +using namespace CDUtility; + +enum +{ + // 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, // Emu -> read + + CDIF_MSG_READ_SECTOR, /* Emu -> read + args[0] = lba + */ + + CDIF_MSG_EJECT, // Emu -> read, args[0]; 0=insert, 1=eject +}; + +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_, const std::string &str); + ~CDIF_Message(); + + unsigned int message; + uint32 args[4]; + void *parg; + std::string str_message; +}; + + + +typedef struct +{ + bool valid; + bool error; + uint32 lba; + uint8 data[2352 + 96]; +} CDIF_Sector_Buffer; + + +// TODO: prohibit copy constructor +class CDIF_ST : public CDIF +{ + public: + + CDIF_ST(CDAccess *cda); + virtual ~CDIF_ST(); + + virtual void HintReadSector(uint32 lba); + virtual bool ReadRawSector(uint8 *buf, uint32 lba); + virtual bool Eject(bool eject_status); + + private: + CDAccess *disc_cdaccess; +}; + +CDIF::CDIF() : UnrecoverableError(false), is_phys_cache(false), DiscEjected(false) +{ + +} + +CDIF::~CDIF() +{ + +} + + +CDIF_Message::CDIF_Message() +{ + message = 0; + + memset(args, 0, sizeof(args)); +} + +CDIF_Message::CDIF_Message(unsigned int message_, uint32 arg0, uint32 arg1, uint32 arg2, uint32 arg3) +{ + message = message_; + args[0] = arg0; + args[1] = arg1; + args[2] = arg2; + args[3] = arg3; +} + +CDIF_Message::CDIF_Message(unsigned int message_, const std::string &str) +{ + message = message_; + str_message = str; +} + +CDIF_Message::~CDIF_Message() +{ + +} + + +bool CDIF::ValidateRawSector(uint8 *buf) +{ + int mode = buf[12 + 3]; + + if(mode != 0x1 && mode != 0x2) + return(false); + + if(!edc_lec_check_and_correct(buf, mode == 2)) + return(false); + + return(true); +} +int CDIF::ReadSector(uint8* pBuf, uint32 lba, uint32 nSectors) +{ + int ret = 0; + + if(UnrecoverableError) + return(false); + + while(nSectors--) + { + uint8 tmpbuf[2352 + 96]; + + if(!ReadRawSector(tmpbuf, lba)) + { + puts("CDIF Raw Read error"); + return(FALSE_0); + } + + if(!ValidateRawSector(tmpbuf)) + { + printf(_("Uncorrectable data at sector %d"), lba); + return(false); + } + + const int mode = tmpbuf[12 + 3]; + + if(!ret) + ret = mode; + + if(mode == 1) + { + memcpy(pBuf, &tmpbuf[12 + 4], 2048); + } + else if(mode == 2) + { + memcpy(pBuf, &tmpbuf[12 + 4 + 8], 2048); + } + else + { + printf("CDIF_ReadSector() invalid sector type at LBA=%u\n", (unsigned int)lba); + return(false); + } + + pBuf += 2048; + lba++; + } + + return(ret); +} + +// +// +// Single-threaded implementation follows. +// +// + +CDIF_ST::CDIF_ST(CDAccess *cda) : disc_cdaccess(cda) +{ + //puts("***WARNING USING SINGLE-THREADED CD READER***"); + + is_phys_cache = disc_cdaccess->Is_Physical(); + 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)); + } +} + +CDIF_ST::~CDIF_ST() +{ + if(disc_cdaccess) + { + delete disc_cdaccess; + disc_cdaccess = NULL; + } +} + +void CDIF_ST::HintReadSector(uint32 lba) +{ + // TODO: disc_cdaccess seek hint? (probably not, would require asynchronousitycamel) +} + +bool CDIF_ST::ReadRawSector(uint8 *buf, uint32 lba) +{ + if(UnrecoverableError) + { + memset(buf, 0, 2352 + 96); + return(false); + } + + try + { + disc_cdaccess->Read_Raw_Sector(buf, lba); + } + catch(std::exception &e) + { + printf(_("Sector %u read error: %s"), lba, e.what()); + memset(buf, 0, 2352 + 96); + return(false); + } + + return(true); +} + +bool CDIF_ST::Eject(bool eject_status) +{ + if(UnrecoverableError) + return(false); + + try + { + if(eject_status != DiscEjected) + { + disc_cdaccess->Eject(eject_status); + + // Set after ->Eject(), since it might throw an exception. + DiscEjected = -1; // For if TOC reading fails or there's something horribly wrong with the disc. + + 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) + { + throw(MDFN_Error(0, _("TOC first(%d)/last(%d) track numbers bad."), disc_toc.first_track, disc_toc.last_track)); + } + } + DiscEjected = eject_status; + } + } + catch(std::exception &e) + { + printf("%s", e.what()); + 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) override; + + virtual uint64 read(void *data, uint64 count, bool error_on_eos = true) override; + virtual void write(const void *data, uint64 count) override; + virtual void truncate(uint64 length) override; + + virtual void seek(int64 offset, int whence) override; + virtual uint64 tell(void) override; + virtual uint64 size(void) override; + virtual void flush(void) override; + virtual void close(void) override; + + 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); +} + +uint64 CDIF_Stream_Thing::read(void *data, uint64 count, bool error_on_eos) +{ + if(count > (((uint64)sector_count * 2048) - position)) + { + if(error_on_eos) + { + throw MDFN_Error(0, "EOF"); + } + + count = ((uint64)sector_count * 2048) - position; + } + + if(!count) + return(0); + + for(uint64 rp = position; rp < (position + count); rp = (rp &~ 2047) + 2048) + { + uint8 buf[2048]; + + if(!cdintf->ReadSector(buf, start_lba + (rp / 2048), 1)) + { + throw MDFN_Error(ErrnoHolder(EIO)); + } + + //::printf("Meow: %08llx -- %08llx\n", count, (rp - position) + std::min(2048 - (rp & 2047), count - (rp - position))); + memcpy((uint8*)data + (rp - position), buf + (rp & 2047), std::min(2048 - (rp & 2047), count - (rp - position))); + } + + position += count; + + return count; +} + +void CDIF_Stream_Thing::write(const void *data, uint64 count) +{ + throw MDFN_Error(ErrnoHolder(EBADF)); +} + +void CDIF_Stream_Thing::truncate(uint64 length) +{ + throw MDFN_Error(ErrnoHolder(EBADF)); +} + +void CDIF_Stream_Thing::seek(int64 offset, int whence) +{ + int64 new_position; + + switch(whence) + { + default: + throw MDFN_Error(ErrnoHolder(EINVAL)); + break; + + 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; +} + +uint64 CDIF_Stream_Thing::tell(void) +{ + return position; +} + +uint64 CDIF_Stream_Thing::size(void) +{ + return(sector_count * 2048); +} + +void CDIF_Stream_Thing::flush(void) +{ + +} + +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(const std::string& path, const bool is_device, bool image_memcache) +{ + CDAccess *cda = cdaccess_open_image(path, image_memcache); + + return new CDIF_ST(cda); +} diff --git a/psx/mednadisc/cdrom/cdromif.h b/psx/mednadisc/cdrom/cdromif.h new file mode 100644 index 0000000000..845512c796 --- /dev/null +++ b/psx/mednadisc/cdrom/cdromif.h @@ -0,0 +1,70 @@ +/* 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 + */ + +#ifndef __MDFN_CDROM_CDROMIF_H +#define __MDFN_CDROM_CDROMIF_H + +#include "CDUtility.h" +#include "stream.h" + +#include + +typedef CDUtility::TOC CD_TOC; + +class CDIF +{ + public: + + CDIF(); + virtual ~CDIF(); + + inline void ReadTOC(CDUtility::TOC *read_target) + { + *read_target = disc_toc; + } + + virtual void HintReadSector(uint32 lba) = 0; + virtual bool ReadRawSector(uint8 *buf, uint32 lba) = 0; + + // Call for mode 1 or mode 2 form 1 only. + bool ValidateRawSector(uint8 *buf); + + // 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* pBuf, uint32 lba, uint32 nSectors); + + // 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; + + inline bool IsPhysical(void) { return(is_phys_cache); } + + // 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 lba, uint32 sector_count); + + protected: + bool UnrecoverableError; + bool is_phys_cache; + CDUtility::TOC disc_toc; + int DiscEjected; // 0 = inserted, 1 = ejected, -1 = DRAGONS ATE THE DISC. NOM NOM NOM. +}; + +CDIF *CDIF_Open(const std::string& path, const bool is_device, bool image_memcache); + +#endif diff --git a/psx/mednadisc/cdrom/crc32.cpp b/psx/mednadisc/cdrom/crc32.cpp new file mode 100644 index 0000000000..08268ae8dc --- /dev/null +++ b/psx/mednadisc/cdrom/crc32.cpp @@ -0,0 +1,130 @@ +/* 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 + * + * CRC32 code based upon public domain code by Ross Williams (see notes below) + * + * 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" + +/*** + *** EDC checksum used in CDROM sectors + ***/ + +/*****************************************************************/ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x8001801BL */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +/*****************************************************************/ + +unsigned long edctable[256] = +{ + 0x00000000L, 0x90910101L, 0x91210201L, 0x01B00300L, + 0x92410401L, 0x02D00500L, 0x03600600L, 0x93F10701L, + 0x94810801L, 0x04100900L, 0x05A00A00L, 0x95310B01L, + 0x06C00C00L, 0x96510D01L, 0x97E10E01L, 0x07700F00L, + 0x99011001L, 0x09901100L, 0x08201200L, 0x98B11301L, + 0x0B401400L, 0x9BD11501L, 0x9A611601L, 0x0AF01700L, + 0x0D801800L, 0x9D111901L, 0x9CA11A01L, 0x0C301B00L, + 0x9FC11C01L, 0x0F501D00L, 0x0EE01E00L, 0x9E711F01L, + 0x82012001L, 0x12902100L, 0x13202200L, 0x83B12301L, + 0x10402400L, 0x80D12501L, 0x81612601L, 0x11F02700L, + 0x16802800L, 0x86112901L, 0x87A12A01L, 0x17302B00L, + 0x84C12C01L, 0x14502D00L, 0x15E02E00L, 0x85712F01L, + 0x1B003000L, 0x8B913101L, 0x8A213201L, 0x1AB03300L, + 0x89413401L, 0x19D03500L, 0x18603600L, 0x88F13701L, + 0x8F813801L, 0x1F103900L, 0x1EA03A00L, 0x8E313B01L, + 0x1DC03C00L, 0x8D513D01L, 0x8CE13E01L, 0x1C703F00L, + 0xB4014001L, 0x24904100L, 0x25204200L, 0xB5B14301L, + 0x26404400L, 0xB6D14501L, 0xB7614601L, 0x27F04700L, + 0x20804800L, 0xB0114901L, 0xB1A14A01L, 0x21304B00L, + 0xB2C14C01L, 0x22504D00L, 0x23E04E00L, 0xB3714F01L, + 0x2D005000L, 0xBD915101L, 0xBC215201L, 0x2CB05300L, + 0xBF415401L, 0x2FD05500L, 0x2E605600L, 0xBEF15701L, + 0xB9815801L, 0x29105900L, 0x28A05A00L, 0xB8315B01L, + 0x2BC05C00L, 0xBB515D01L, 0xBAE15E01L, 0x2A705F00L, + 0x36006000L, 0xA6916101L, 0xA7216201L, 0x37B06300L, + 0xA4416401L, 0x34D06500L, 0x35606600L, 0xA5F16701L, + 0xA2816801L, 0x32106900L, 0x33A06A00L, 0xA3316B01L, + 0x30C06C00L, 0xA0516D01L, 0xA1E16E01L, 0x31706F00L, + 0xAF017001L, 0x3F907100L, 0x3E207200L, 0xAEB17301L, + 0x3D407400L, 0xADD17501L, 0xAC617601L, 0x3CF07700L, + 0x3B807800L, 0xAB117901L, 0xAAA17A01L, 0x3A307B00L, + 0xA9C17C01L, 0x39507D00L, 0x38E07E00L, 0xA8717F01L, + 0xD8018001L, 0x48908100L, 0x49208200L, 0xD9B18301L, + 0x4A408400L, 0xDAD18501L, 0xDB618601L, 0x4BF08700L, + 0x4C808800L, 0xDC118901L, 0xDDA18A01L, 0x4D308B00L, + 0xDEC18C01L, 0x4E508D00L, 0x4FE08E00L, 0xDF718F01L, + 0x41009000L, 0xD1919101L, 0xD0219201L, 0x40B09300L, + 0xD3419401L, 0x43D09500L, 0x42609600L, 0xD2F19701L, + 0xD5819801L, 0x45109900L, 0x44A09A00L, 0xD4319B01L, + 0x47C09C00L, 0xD7519D01L, 0xD6E19E01L, 0x46709F00L, + 0x5A00A000L, 0xCA91A101L, 0xCB21A201L, 0x5BB0A300L, + 0xC841A401L, 0x58D0A500L, 0x5960A600L, 0xC9F1A701L, + 0xCE81A801L, 0x5E10A900L, 0x5FA0AA00L, 0xCF31AB01L, + 0x5CC0AC00L, 0xCC51AD01L, 0xCDE1AE01L, 0x5D70AF00L, + 0xC301B001L, 0x5390B100L, 0x5220B200L, 0xC2B1B301L, + 0x5140B400L, 0xC1D1B501L, 0xC061B601L, 0x50F0B700L, + 0x5780B800L, 0xC711B901L, 0xC6A1BA01L, 0x5630BB00L, + 0xC5C1BC01L, 0x5550BD00L, 0x54E0BE00L, 0xC471BF01L, + 0x6C00C000L, 0xFC91C101L, 0xFD21C201L, 0x6DB0C300L, + 0xFE41C401L, 0x6ED0C500L, 0x6F60C600L, 0xFFF1C701L, + 0xF881C801L, 0x6810C900L, 0x69A0CA00L, 0xF931CB01L, + 0x6AC0CC00L, 0xFA51CD01L, 0xFBE1CE01L, 0x6B70CF00L, + 0xF501D001L, 0x6590D100L, 0x6420D200L, 0xF4B1D301L, + 0x6740D400L, 0xF7D1D501L, 0xF661D601L, 0x66F0D700L, + 0x6180D800L, 0xF111D901L, 0xF0A1DA01L, 0x6030DB00L, + 0xF3C1DC01L, 0x6350DD00L, 0x62E0DE00L, 0xF271DF01L, + 0xEE01E001L, 0x7E90E100L, 0x7F20E200L, 0xEFB1E301L, + 0x7C40E400L, 0xECD1E501L, 0xED61E601L, 0x7DF0E700L, + 0x7A80E800L, 0xEA11E901L, 0xEBA1EA01L, 0x7B30EB00L, + 0xE8C1EC01L, 0x7850ED00L, 0x79E0EE00L, 0xE971EF01L, + 0x7700F000L, 0xE791F101L, 0xE621F201L, 0x76B0F300L, + 0xE541F401L, 0x75D0F500L, 0x7460F600L, 0xE4F1F701L, + 0xE381F801L, 0x7310F900L, 0x72A0FA00L, 0xE231FB01L, + 0x71C0FC00L, 0xE151FD01L, 0xE0E1FE01L, 0x7070FF00L +}; + +/* + * CDROM EDC calculation + */ + +uint32 EDCCrc32(const unsigned char *data, int len) +{ + uint32 crc = 0; + + while(len--) + crc = edctable[(crc ^ *data++) & 0xFF] ^ (crc >> 8); + + return crc; +} diff --git a/psx/mednadisc/cdrom/dvdisaster.h b/psx/mednadisc/cdrom/dvdisaster.h new file mode 100644 index 0000000000..7d963c535c --- /dev/null +++ b/psx/mednadisc/cdrom/dvdisaster.h @@ -0,0 +1,173 @@ +/* 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 "../types.h" +#include "emuware/emuware.h" + +#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 EDCCrc32(const unsigned char*, int); + +/*** + *** galois.c + *** + * This is currently the hardcoded GF(2**8). + * int32 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; +} diff --git a/psx/mednadisc/cdrom/galois.cpp b/psx/mednadisc/cdrom/galois.cpp new file mode 100644 index 0000000000..5c48f4f793 --- /dev/null +++ b/psx/mednadisc/cdrom/galois.cpp @@ -0,0 +1,156 @@ +/* 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" + +#include "galois-inlines.h" + +/*** + *** Galois field arithmetic. + *** + * Calculations are done over the extension field GF(2**n). + * Be careful not to overgeneralize these arithmetics; + * they only work for the case of GF(p**n) with p being prime. + */ + +/* Initialize the Galois field tables */ + + +GaloisTables* CreateGaloisTables(int32 gf_generator) +{ + GaloisTables *gt = (GaloisTables *)calloc(1, sizeof(GaloisTables)); + int32 b,log; + + /* Allocate the tables. + The encoder uses a special version of alpha_to which has the mod_fieldmax() + folded into the table. */ + + gt->gfGenerator = gf_generator; + + gt->indexOf = (int32 *)calloc(GF_FIELDSIZE, sizeof(int32)); + gt->alphaTo = (int32 *)calloc(GF_FIELDSIZE, sizeof(int32)); + gt->encAlphaTo = (int32 *)calloc(2*GF_FIELDSIZE, sizeof(int32)); + + /* create the log/ilog values */ + + for(b=1, log=0; logindexOf[b] = log; + gt->alphaTo[log] = b; + b = b << 1; + if(b & GF_FIELDSIZE) + b = b ^ gf_generator; + } + + if(b!=1) + { + printf("Failed to create the Galois field log tables!\n"); + exit(1); + } + + /* we're even closed using infinity (makes things easier) */ + + gt->indexOf[0] = GF_ALPHA0; /* log(0) = inf */ + 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)]; + + return gt; +} + +void FreeGaloisTables(GaloisTables *gt) +{ + if(gt->indexOf) free(gt->indexOf); + if(gt->alphaTo) free(gt->alphaTo); + if(gt->encAlphaTo) free(gt->encAlphaTo); + + free(gt); +} + +/*** + *** Create the the Reed-Solomon generator polynomial + *** and some auxiliary data structures. + */ + +ReedSolomonTables *CreateReedSolomonTables(GaloisTables *gt, + int32 first_consecutive_root, + int32 prim_elem, + int nroots_in) +{ ReedSolomonTables *rt = (ReedSolomonTables *)calloc(1, sizeof(ReedSolomonTables)); + int32 i,j,root; + + rt->gfTables = gt; + rt->fcr = first_consecutive_root; + rt->primElem = prim_elem; + rt->nroots = nroots_in; + rt->ndata = GF_FIELDMAX - rt->nroots; + + rt->gpoly = (int32 *)calloc((rt->nroots+1), sizeof(int32)); + + /* Create the RS code generator polynomial */ + + rt->gpoly[0] = 1; + + 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) */ + + 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)]; + } + + /* Store the polynomials index for faster encoding */ + + for(i=0; i<=rt->nroots; 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); + + free(rt); +} diff --git a/psx/mednadisc/cdrom/l-ec.cpp b/psx/mednadisc/cdrom/l-ec.cpp new file mode 100644 index 0000000000..39b4cbdd9f --- /dev/null +++ b/psx/mednadisc/cdrom/l-ec.cpp @@ -0,0 +1,478 @@ +/* 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" + +#include "galois-inlines.h" + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/*** + *** Mapping between cd frame and parity vectors + ***/ + +/* + * Mapping of frame bytes to P/Q Vectors + */ + +int PToByteIndex(int p, int i) +{ return 12 + p + i*86; +} + +void ByteIndexToP(int b, int *p, int *i) +{ *p = (b-12)%86; + *i = (b-12)/86; +} + +int QToByteIndex(int q, int i) +{ int offset = 12 + (q & 1); + + if(i == 43) return 2248+q; + if(i == 44) return 2300+q; + + q&=~1; + return offset + (q*43 + i*88) % 2236; +} + +void ByteIndexToQ(int b, int *q, int *i) +{ int x,y,offset; + + if(b >= 2300) + { *i = 44; + *q = (b-2300); + return; + } + + if(b >= 2248) + { *i = 43; + *q = (b-2248); + return; + } + + offset = b&1; + b = (b-12)/2; + x = b/43; + y = (b-(x*43))%26; + *i = b-(x*43); + *q = 2*((x+26-y)%26)+offset; +} + +/* + * There are 86 vectors of P-parity, yielding a RS(26,24) code. + */ + +void GetPVector(unsigned char *frame, unsigned char *data, int n) +{ int i; + int w_idx = n+12; + + for(i=0; i<26; i++, w_idx+=86) + data[i] = frame[w_idx]; +} + +void SetPVector(unsigned char *frame, unsigned char *data, int n) +{ int i; + int w_idx = n+12; + + for(i=0; i<26; i++, w_idx+=86) + frame[w_idx] = data[i]; +} + +void FillPVector(unsigned char *frame, unsigned char data, int n) +{ int i; + int w_idx = n+12; + + for(i=0; i<26; i++, w_idx+=86) + frame[w_idx] = data; +} + +void OrPVector(unsigned char *frame, unsigned char value, int n) +{ int i; + int w_idx = n+12; + + for(i=0; i<26; i++, w_idx+=86) + frame[w_idx] |= value; +} + +void AndPVector(unsigned char *frame, unsigned char value, int n) +{ int i; + int w_idx = n+12; + + for(i=0; i<26; i++, w_idx+=86) + frame[w_idx] &= value; +} + +/* + * There are 52 vectors of Q-parity, yielding a RS(45,43) code. + */ + +void GetQVector(unsigned char *frame, unsigned char *data, int n) +{ int offset = 12 + (n & 1); + int w_idx = (n&~1) * 43; + int i; + + for(i=0; i<43; i++, w_idx+=88) + data[i] = frame[(w_idx % 2236) + offset]; + + data[43] = frame[2248 + n]; + data[44] = frame[2300 + n]; +} + +void SetQVector(unsigned char *frame, unsigned char *data, int n) +{ int offset = 12 + (n & 1); + int w_idx = (n&~1) * 43; + int i; + + for(i=0; i<43; i++, w_idx+=88) + frame[(w_idx % 2236) + offset] = data[i]; + + frame[2248 + n] = data[43]; + frame[2300 + n] = data[44]; +} + +void FillQVector(unsigned char *frame, unsigned char data, int n) +{ int offset = 12 + (n & 1); + int w_idx = (n&~1) * 43; + int i; + + for(i=0; i<43; i++, w_idx+=88) + frame[(w_idx % 2236) + offset] = data; + + frame[2248 + n] = data; + frame[2300 + n] = data; +} + +void OrQVector(unsigned char *frame, unsigned char data, int n) +{ int offset = 12 + (n & 1); + int w_idx = (n&~1) * 43; + int i; + + for(i=0; i<43; i++, w_idx+=88) + frame[(w_idx % 2236) + offset] |= data; + + frame[2248 + n] |= data; + frame[2300 + n] |= data; +} + +void AndQVector(unsigned char *frame, unsigned char data, int n) +{ int offset = 12 + (n & 1); + int w_idx = (n&~1) * 43; + int i; + + for(i=0; i<43; i++, w_idx+=88) + frame[(w_idx % 2236) + offset] &= data; + + frame[2248 + n] &= data; + frame[2300 + n] &= data; +} + +/*** + *** C2 error counting + ***/ + +int CountC2Errors(unsigned char *frame) +{ int i,count = 0; + frame += 2352; + + for(i=0; i<294; i++, frame++) + { if(*frame & 0x01) count++; + if(*frame & 0x02) count++; + if(*frame & 0x04) count++; + if(*frame & 0x08) count++; + if(*frame & 0x10) count++; + if(*frame & 0x20) count++; + if(*frame & 0x40) count++; + if(*frame & 0x80) count++; + } + + return count; +} + +/*** + *** L-EC error correction for CD raw data sectors + ***/ + +/* + * These could be used from ReedSolomonTables, + * but hardcoding them is faster. + */ + +#define NROOTS 2 +#define LEC_FIRST_ROOT 0 //GF_ALPHA0 +#define LEC_PRIM_ELEM 1 +#define LEC_PRIMTH_ROOT 1 + +/* + * Calculate the error syndrome + */ + +int DecodePQ(ReedSolomonTables *rt, unsigned char *data, int padding, + int *erasure_list, int erasure_count) +{ GaloisTables *gt = rt->gfTables; + int syndrome[NROOTS]; + int lambda[NROOTS+1]; + int omega[NROOTS+1]; + int b[NROOTS+1]; + int reg[NROOTS+1]; + int root[NROOTS]; + int loc[NROOTS]; + int syn_error; + int deg_lambda,lambda_roots; + int deg_omega; + int shortened_size = GF_FIELDMAX - 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)]; + + /*** Convert syndrome to index form, check for nonzero condition. */ + + syn_error = 0; + for(i=0; iindexOf[syndrome[i]]; + } + + /*** If the syndrome is zero, everything is fine. */ + + if(!syn_error) + return 0; + + /*** Initialize lambda to be the erasure locator polynomial */ + + lambda[0] = 1; + lambda[1] = lambda[2] = 0; + + erasure_list[0] += padding; + erasure_list[1] += padding; + + if(erasure_count > 2) /* sanity check */ + erasure_count = 0; + + if(erasure_count > 0) + { lambda[1] = gt->alphaTo[mod_fieldmax(LEC_PRIM_ELEM*(GF_FIELDMAX-1-erasure_list[0]))]; + + for(i=1; i0; j--) + { int tmp = gt->indexOf[lambda[j-1]]; + + if(tmp != GF_ALPHA0) + lambda[j] ^= gt->alphaTo[mod_fieldmax(u + tmp)]; + } + } + } + + for(i=0; iindexOf[lambda[i]]; + + /*** Berlekamp-Massey algorithm to determine error+erasure locator polynomial */ + + r = erasure_count; /* r is the step number */ + el = erasure_count; + + /* Compute discrepancy at the r-th step in poly-form */ + + while(++r <= NROOTS) + { int discr_r = 0; + + for(i=0; ialphaTo[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; + } + 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]; + } + + 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; + } + + memcpy(lambda, t, (NROOTS+1)*sizeof(t[0])); + } + } + + /*** Convert lambda to index form and compute deg(lambda(x)) */ + + deg_lambda = 0; + for(i=0; iindexOf[lambda[i]]; + if(lambda[i] != GF_ALPHA0) + deg_lambda = i; + } + + /*** Find roots of the error+erasure locator polynomial by Chien search */ + + memcpy(reg+1, lambda+1, NROOTS*sizeof(reg[0])); + lambda_roots = 0; /* Number of roots of lambda(x) */ + + for(i=1, k=LEC_PRIMTH_ROOT-1; i<=GF_FIELDMAX; i++, k=mod_fieldmax(k+LEC_PRIMTH_ROOT)) + { int q=1; /* lambda[0] is always 0 */ + + for(j=deg_lambda; j>0; j--) + { if(reg[j] != GF_ALPHA0) + { reg[j] = mod_fieldmax(reg[j] + j); + q ^= gt->alphaTo[reg[j]]; + } + } + + if(q != 0) continue; /* Not a root */ + + /* store root in index-form and the error location number */ + + root[lambda_roots] = i; + loc[lambda_roots] = k; + + /* If we've already found max possible roots, abort the search to save time */ + + if(++lambda_roots == deg_lambda) break; + } + + /* deg(lambda) unequal to number of roots => uncorrectable error detected + This is not reliable for very small numbers of roots, e.g. nroots = 2 */ + + if(deg_lambda != lambda_roots) + { return -1; + } + + /* Compute err+eras evaluator poly omega(x) = syn(x)*lambda(x) + (modulo x**nroots). in index form. Also find deg(omega). */ + + deg_omega = deg_lambda-1; + + for(i=0; i<=deg_omega; i++) + { int tmp = 0; + + 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])]; + } + + omega[i] = gt->indexOf[tmp]; + } + + /* Compute error values in poly-form. + num1 = omega(inv(X(l))), + num2 = inv(X(l))**(FIRST_ROOT-1) and + den = lambda_pr(inv(X(l))) all in poly-form. */ + + for(j=lambda_roots-1; j>=0; j--) + { int num1 = 0; + int num2; + int den; + int location = loc[j]; + + for(i=deg_omega; i>=0; i--) + { if(omega[i] != GF_ALPHA0) + 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])]; + } + + /* 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])]; + + /* 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 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)]; + } + + /*** Convert syndrome to index form, check for nonzero condition. */ +#if 1 + for(i=0; i + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#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 u_int8_t gf8_t; + +static u_int8_t GF8_LOG[256]; +static gf8_t GF8_ILOG[256]; + +static const class Gf8_Q_Coeffs_Results_01 { +private: + u_int16_t table[43][256]; +public: + Gf8_Q_Coeffs_Results_01(); + ~Gf8_Q_Coeffs_Results_01() {} + const u_int16_t *operator[] (int i) const { return &table[i][0]; } + operator const u_int16_t *() const { return &table[0][0]; } +} CF8_Q_COEFFS_RESULTS_01; + +static const class CrcTable { +private: + u_int32_t table[256]; +public: + CrcTable(); + ~CrcTable() {} + u_int32_t operator[](int i) const { return table[i]; } + operator const u_int32_t *() const { return table; } +} CRCTABLE; + +static const class ScrambleTable { +private: + u_int8_t table[2340]; +public: + ScrambleTable(); + ~ScrambleTable() {} + u_int8_t operator[](int i) const { return table[i]; } + operator const u_int8_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() +{ + u_int8_t log; + u_int16_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[(u_int8_t)b] = log; + GF8_ILOG[log] = (u_int8_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; + u_int16_t c; + gf8_t GF8_COEFFS_HELP[2][45]; + u_int8_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 u_int32_t mirror_bits(u_int32_t d, int bits) +{ + int i; + u_int32_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 () +{ + u_int32_t i, j; + u_int32_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 u_int32_t calc_edc(u_int8_t *data, int len) +{ + u_int32_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() +{ + u_int16_t i, j; + u_int16_t reg = 1; + u_int8_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(u_int8_t *sector) +{ + u_int32_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(u_int8_t *sector) +{ + u_int32_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(u_int8_t *sector) +{ + u_int32_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(u_int8_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 u_int8_t bin2bcd(u_int8_t b) +{ + return (((b/10) << 4) & 0xf0) | ((b%10) & 0x0f); +} + +/* Builds the sector header. + */ +static void set_sector_header(u_int8_t mode, u_int32_t adr, u_int8_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(u_int8_t *sector) +{ + int i, j; + u_int16_t p01_msb, p01_lsb; + u_int8_t *p_lsb_start; + u_int8_t *p_lsb; + u_int8_t *p0, *p1; + u_int8_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(u_int8_t *sector) +{ + int i, j; + u_int16_t q01_lsb, q01_msb; + u_int8_t *q_lsb_start; + u_int8_t *q_lsb; + u_int8_t *q0, *q1, *q_start; + u_int8_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(u_int32_t adr, u_int8_t *sector) +{ + u_int16_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(u_int32_t adr, u_int8_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(u_int32_t adr, u_int8_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(u_int32_t adr, u_int8_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(u_int32_t adr, u_int8_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(u_int8_t *sector) +{ + u_int16_t i; + const u_int8_t *stable = SCRAMBLE_TABLE; + u_int8_t *p = sector; + u_int8_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; + u_int8_t buffer1[2352]; + u_int8_t buffer2[2352]; + u_int32_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/psx/mednadisc/cdrom/lec.h b/psx/mednadisc/cdrom/lec.h new file mode 100644 index 0000000000..b41c06b8fa --- /dev/null +++ b/psx/mednadisc/cdrom/lec.h @@ -0,0 +1,77 @@ +/* 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. + */ + +#ifndef __LEC_H__ +#define __LEC_H__ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +typedef uint32_t u_int32_t; +typedef uint16_t u_int16_t; +typedef uint8_t u_int8_t; + +#ifndef TRUE +#define TRUE 1 +#endif + +/* Encodes a MODE 0 sector. + * 'adr' is the current physical sector address + * 'sector' must be 2352 byte wide + */ +void lec_encode_mode0_sector(u_int32_t adr, u_int8_t *sector); + +/* 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(u_int32_t adr, u_int8_t *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(u_int32_t adr, u_int8_t *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(u_int32_t adr, u_int8_t *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(u_int32_t adr, u_int8_t *sector); + +/* Scrambles and byte swaps an encoded sector. + * 'sector' must be 2352 byte wide. + */ +void lec_scramble(u_int8_t *sector); + +#endif diff --git a/psx/mednadisc/cdrom/recover-raw.cpp b/psx/mednadisc/cdrom/recover-raw.cpp new file mode 100644 index 0000000000..b6d091cbd4 --- /dev/null +++ b/psx/mednadisc/cdrom/recover-raw.cpp @@ -0,0 +1,203 @@ +/* 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. + */ + +#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); + + return(1); +} + +void Kill_LEC_Correct(void) +{ + FreeGaloisTables(gt); + FreeReedSolomonTables(rt); +} + +/*** + *** CD level CRC calculation + ***/ + +/* + * Test raw sector against its 32bit CRC. + * 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; + + 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(expected_crc == real_crc) + return(1); + else + { + //printf("Bad EDC CRC: Calculated: %08x, Recorded: %08x\n", real_crc, expected_crc); + return(0); + } +} + +/*** + *** A very simple L-EC error correction. + *** + * Perform just one pass over the Q and P vectors to see if everything + * is okay respectively correct minor errors. This is pretty much the + * same stuff the drive is supposed to do in the final L-EC stage. + */ + +static int simple_lec(unsigned char *frame) +{ + unsigned char byte_state[2352]; + unsigned char p_vector[P_VECTOR_SIZE]; + unsigned char q_vector[Q_VECTOR_SIZE]; + unsigned char p_state[P_VECTOR_SIZE]; + int erasures[Q_VECTOR_SIZE], erasure_count; + int ignore[2]; + int p_failures, q_failures; + int p_corrected, q_corrected; + int p,q; + + /* Setup */ + + memset(byte_state, 0, 2352); + + p_failures = q_failures = 0; + p_corrected = q_corrected = 0; + + /* Perform Q-Parity error correction */ + + for(q=0; q 2) + { 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); + } + } + + /* See what we've got */ + + if(err < 0) /* Uncorrectable. */ + { p_failures++; + } + else /* Correctable. */ + { 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 0; +} + +/*** + *** Validate CD raw sector + ***/ + +int ValidateRawSector(unsigned char *frame, bool xaMode) +{ + int lec_did_sth = FALSE_0; + + /* 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)) + { + lec_did_sth = simple_lec(frame); + } + /* Test internal sector checksum again */ + + if(!CheckEDC(frame, xaMode)) + { + /* EDC failure in RAW sector */ + return FALSE_0; + } + + return TRUE_1; +} + diff --git a/psx/mednadisc/cdrom/scsicd-pce-commands.inc b/psx/mednadisc/cdrom/scsicd-pce-commands.inc new file mode 100644 index 0000000000..47091adb5a --- /dev/null +++ b/psx/mednadisc/cdrom/scsicd-pce-commands.inc @@ -0,0 +1,259 @@ +/******************************************************** +* * +* PC Engine CD Command 0xD8 - SAPSP * +* * +********************************************************/ +static void DoNEC_PCE_SAPSP(const uint8 *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 *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 *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 *cdb) +{ + uint8 *SubQBuf = cd.SubQBuf[QMode_Time]; + uint8 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 *cdb) +{ + // Problems: + // Returned data lengths on real PCE are not confirmed. + // Mode 0x03 behavior not tested on real PCE + + uint8 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 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 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/psx/mednadisc/cdrom/scsicd.cpp b/psx/mednadisc/cdrom/scsicd.cpp new file mode 100644 index 0000000000..64563595f4 --- /dev/null +++ b/psx/mednadisc/cdrom/scsicd.cpp @@ -0,0 +1,3246 @@ +/* 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 + +#define SCSIDBG(format, ...) { printf("[SCSICD] " format "\n", ## __VA_ARGS__); } +//#define SCSIDBG(format, ...) { } + +using namespace CDUtility; + +static uint32 CD_DATA_TRANSFER_RATE; +static uint32 System_Clock; +static void (*CDIRQCallback)(int); +static void (*CDStuffSubchannels)(uint8, int); +static int32* 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 message_pending; + + bool status_sent, message_sent; + + // Pending error codes + uint8 key_pending, asc_pending, ascq_pending, fru_pending; + + uint8 command_buffer[256]; + uint8 command_buffer_pos; + uint8 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 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 data_out_pos; // Current index for writing into data_out. + uint8 data_out_want; // Total number of bytes to buffer into data_out. + + bool DiscChanged; + + uint8 SubQBuf[4][0xC]; // One for each of the 4 most recent q-Modes. + uint8 SubQBuf_Last[0xC]; // The most recent q subchannel data, regardless of q-mode. + + uint8 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 CDDADivAcc; + uint8 CDDADivAccVolFudge; // For PC-FX CD-DA rate control RE impulses and resampling; 100 = 1.0. + uint32 scan_sec_end; + + uint8 PlayMode; + int32 CDDAVolume[2]; // 65536 = 1.0, the maximum. + int16 CDDASectorBuffer[1176]; + uint32 CDDAReadPos; + + int8 CDDAStatus; + uint8 ScanMode; + int64 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 OutPortChSelect[2]; + uint32 OutPortChSelectCache[2]; + int32 OutPortVolumeCache[2]; + + float DeemphState[2][2]; +} cdda_t; + +void MakeSense(uint8 * target, uint8 key, uint8 asc, uint8 ascq, uint8 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 monotonic_timestamp; +static int64 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 read_sec_start; +static uint32 read_sec; +static uint32 read_sec_end; + +static int32 CDReadTimer; +static uint32 SectorAddr; +static uint32 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 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 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 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 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 status, uint8 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 *data_in, uint32 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 *data, const uint32 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 *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 default_value; + uint8 alterable_mask; // Alterable mask reported when PC == 1 + uint8 real_mask; // Real alterable mask. +}; + +struct ModePage +{ + const uint8 code; + const uint8 param_length; + const ModePageParam params[64]; // 64 should be more than enough + uint8 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 *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)mp->current_value[0])); + rate = 44100 + 441 * speed; + + //printf("[SCSICD] Speed: %d(pre-clamped=%d) %d\n", speed, (int8)mp->current_value[0], rate); + cdda.CDDADivAcc = ((int64)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 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 *data, const uint8 data_len) +{ + uint8 mode_data_length, medium_type, device_specific, block_descriptor_length; + uint32 offset = 0; + + printf("[SCSICD] Mode Select (6) Data: Length=0x%02x, ", data_len); + for(uint32 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 code = data[offset++]; + uint8 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 *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 data_in[8192]; + uint8 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); // 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 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 *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 *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 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 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 *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 *cdb) +{ + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + + + +/******************************************************** +* * +* PC-FX CD Command 0xDC - EJECT * +* * +********************************************************/ +static void DoNEC_EJECT(const uint8 *cdb) +{ + CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_REQUEST_IN_CDB); +} + +static void DoREQUESTSENSE(const uint8 *cdb) +{ + uint8 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 *buf, uint8 POINTER_RAW, int32 LBA, uint32 PLBA, uint8 control) +{ + uint8 MIN, SEC, FRAC; + uint8 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 *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 data_in[2048]; + uint32 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 lilba = -150; + uint8 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 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 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 *cdb) +{ + uint8 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 *subptr = &data_in[RealSize]; + uint32 lba; + uint8 m, s, f; + uint32 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 *cdb) +{ + bool pmi = cdb[8] & 0x1; + uint32 lba = MDFN_de32msb(cdb + 0x2); + uint32 ret_lba; + uint32 ret_bl; + uint8 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 *cdb) +{ + uint8 data_in[8192]; + bool WantInMSF = cdb[1] & 0x2; + uint32 HeaderLBA = MDFN_de32msb(cdb + 0x2); + int AllocSize = MDFN_de16msb(cdb + 0x7); + uint8 raw_buf[2352 + 96]; + uint8 mode; + int m, s, f; + uint32 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 *cdb) // Command 0xDB, Set Stop Time +{ + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoPABase(const uint32 lba, const uint32 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 *cdb) +{ + uint32 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 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 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 *cdb) +{ + uint32 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 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 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 *cdb) +{ + // Real PC-FX Bug: Error out on LBA >(not >=) leadout sector number + const uint32 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 *cdb) +{ + // Real PC-FX Bug: Error out on LBA >(not >=) leadout sector number + const uint32 lba = MDFN_de32msb(cdb + 0x2); + const uint32 length = MDFN_de32msb(cdb + 0x6); + + DoPABase(lba, length); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x47 - PLAY AUDIO MSF * +* * +********************************************************/ +static void DoPAMSF(const uint8 *cdb) +{ + int32 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)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 *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 lba, const uint32 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 *cdb) +{ + const int32 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 *cdb) +{ + const int32 rel_lba = MDFN_de32msb(cdb + 0x2); + const int StartTrack = cdb[10]; + const uint32 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 *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 sa, uint32 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 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)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 *cdb) +{ + uint32 sa = ((cdb[1] & 0x1F) << 16) | (cdb[2] << 8) | (cdb[3] << 0); + uint32 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 *cdb) +{ + uint32 sa = MDFN_de32msb(cdb + 0x2); + uint32 sc = MDFN_de16msb(cdb + 0x7); + + DoREADBase(sa, sc); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0xA8 - READ(12) * +* * +********************************************************/ +static void DoREAD12(const uint8 *cdb) +{ + uint32 sa = MDFN_de32msb(cdb + 0x2); + uint32 sc = MDFN_de32msb(cdb + 0x6); + + DoREADBase(sa, sc); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x34 - PREFETCH(10) * +* * +********************************************************/ +static void DoPREFETCH(const uint8 *cdb) +{ + uint32 lba = MDFN_de32msb(cdb + 0x2); + //uint32 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 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 *cdb) +{ + uint32 lba = ((cdb[1] & 0x1F) << 16) | (cdb[2] << 8) | cdb[3]; + + DoSEEKBase(lba); +} + + + +/******************************************************** +* * +* SCSI-2 CD Command 0x2B - SEEK(10) * +* * +********************************************************/ +static void DoSEEK10(const uint8 *cdb) +{ + uint32 lba = MDFN_de32msb(cdb + 0x2); + + DoSEEKBase(lba); +} + +// 353 +/******************************************************** +* * +* SCSI-2 CD Command 0x42 - READ SUB-CHANNEL(10) * +* * +********************************************************/ +static void DoREADSUBCHANNEL(const uint8 *cdb) +{ + uint8 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 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 *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 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 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 *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 *cdb) +{ + uint8 *SubQBuf = cd.SubQBuf[QMode_Time]; + uint8 data_in[10]; + const uint8 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 *cdb) +{ + SendStatusAndMessage(STATUS_GOOD, 0x00); +} + +static void DoNEC_PAUSE(const uint8 *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 *cdb) +{ + uint32 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 *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 cmd; + uint32 flags; + void (*func)(const uint8 *cdb); + const char *pretty_name; + const char *format_string; +} SCSICH; + +static const int32 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 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 system_timestamp, int32 run_time) +{ + if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) + { + cdda.CDDADiv -= (int64)run_time << 20; + + while(cdda.CDDADiv <= 0) + { + const uint32 synthtime_ex = (((uint64)system_timestamp << 20) + (int64)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 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 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 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 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 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)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 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* tb0 = &HRBufs[0][synthtime]; + int32* tb1 = &HRBufs[1][synthtime]; + + for(unsigned c = 0; c < CDDA_FILTER_NUMCONVOLUTIONS; c++) + { + tb0[c] += ((int64)coeff[c] * sample_va[0]) >> FINAL_OUT_SHIFT; + tb1[c] += ((int64)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 system_timestamp, int32 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) 1 * 2048 * System_Clock / CD_DATA_TRANSFER_RATE; + + //CDReadTimer += (uint64) 1 * 128 * System_Clock / CD_DATA_TRANSFER_RATE; + } + else + { + uint8 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) 1 * 2048 * System_Clock / CD_DATA_TRANSFER_RATE; + } + else + { + cd.data_transfer_done = TRUE; + } + } + } // end else to if(!Cur_CDIF->ReadSector + + } + } +} + + +uint32 SCSICD_Run(scsicd_timestamp_t system_timestamp) +{ + int32 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 next_time = 0x7fffffff; + + if(CDReadTimer > 0 && CDReadTimer < next_time) + next_time = CDReadTimer; + + if(cdda.CDDAStatus == CDDASTATUS_PLAYING || cdda.CDDAStatus == CDDASTATUS_SCANNING) + { + int32 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 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* left_hrbuf, int32* right_hrbuf, uint32 TransferRate, uint32 SystemClock, void (*IRQFunc)(int), void (*SSCFunc)(uint8, 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)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/psx/mednadisc/cdrom/scsicd.h b/psx/mednadisc/cdrom/scsicd.h new file mode 100644 index 0000000000..3aa82f4f69 --- /dev/null +++ b/psx/mednadisc/cdrom/scsicd.h @@ -0,0 +1,99 @@ +#ifndef __PCFX_SCSICD_H +#define __PCFX_SCSICD_H + +typedef int32 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 DB; + + uint32 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)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 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 SCSICD_Run(scsicd_timestamp_t); +void SCSICD_ResetTS(uint32 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* left_hrbuf, int32* right_hrbuf, uint32 TransferRate, uint32 SystemClock, void (*IRQFunc)(int), void (*SSCFunc)(uint8, int)); +void SCSICD_Close(void); + +void SCSICD_SetTransferRate(uint32 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/psx/mednadisc/cdrom/scsicd_cdda_filter.inc b/psx/mednadisc/cdrom/scsicd_cdda_filter.inc new file mode 100644 index 0000000000..f80b20a524 --- /dev/null +++ b/psx/mednadisc/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/psx/mednadisc/emuware/EW_state.cpp b/psx/mednadisc/emuware/EW_state.cpp new file mode 100644 index 0000000000..9b408d844e --- /dev/null +++ b/psx/mednadisc/emuware/EW_state.cpp @@ -0,0 +1,96 @@ +#include "EW_state.h" +#include +#include +#include +#include +#include + +namespace EW { + +NewStateDummy::NewStateDummy() + :length(0) +{ +} +void NewStateDummy::Save(const void *ptr, size_t size, const char *name) +{ + length += size; +} +void NewStateDummy::Load(void *ptr, size_t size, const char *name) +{ +} + +NewStateExternalBuffer::NewStateExternalBuffer(char *buffer, long maxlength) + :buffer(buffer), length(0), maxlength(maxlength) +{ +} + +void NewStateExternalBuffer::Save(const void *ptr, size_t size, const char *name) +{ + if (maxlength - length >= (long)size) + { + std::memcpy(buffer + length, ptr, size); + } + length += size; +} + +void NewStateExternalBuffer::Load(void *ptr, size_t size, const char *name) +{ + char *dst = static_cast(ptr); + if (maxlength - length >= (long)size) + { + std::memcpy(dst, buffer + length, size); + } + length += size; +} + +NewStateExternalFunctions::NewStateExternalFunctions(const FPtrs *ff) + :Save_(ff->Save_), + Load_(ff->Load_), + EnterSection_(ff->EnterSection_), + ExitSection_(ff->ExitSection_) +{ +} + +void NewStateExternalFunctions::Save(const void *ptr, size_t size, const char *name) +{ + Save_(ptr, size, name); +} +void NewStateExternalFunctions::Load(void *ptr, size_t size, const char *name) +{ + Load_(ptr, size, name); +} + +void NewStateExternalFunctions::EnterSection(const char *name, ...) +{ + //analysis: multiple passes to generate string not ideal, but there arent many sections.. so it should be OK. improvement would be special vararg overload + va_list ap; + va_start(ap,name); + char easybuf[32]; + int size = vsnprintf(easybuf,0,name,ap); + char *ptr = easybuf; + if(size>31) + ptr = (char*)malloc(size+1); + vsprintf(ptr,name,ap); + EnterSection_(ptr); + if(ptr != easybuf) + free(ptr); + va_end(ap); +} +void NewStateExternalFunctions::ExitSection(const char *name, ...) +{ + va_list ap; + va_start(ap,name); + char easybuf[32]; + int size = vsnprintf(easybuf,0,name,ap); + char *ptr = easybuf; + if(size>31) + ptr = (char*)malloc(size+1); + vsprintf(ptr,name,ap); + ExitSection_(ptr); + if(ptr != easybuf) + free(ptr); + va_end(ap); +} + + +} diff --git a/psx/mednadisc/emuware/EW_state.h b/psx/mednadisc/emuware/EW_state.h new file mode 100644 index 0000000000..acd3c1396a --- /dev/null +++ b/psx/mednadisc/emuware/EW_state.h @@ -0,0 +1,105 @@ +#ifndef NEWSTATE_H +#define NEWSTATE_H + +#include +#include + +namespace EW +{ + + class NewState + { + public: + virtual void Save(const void *ptr, size_t size, const char *name) = 0; + virtual void Load(void *ptr, size_t size, const char *name) = 0; + virtual void EnterSection(const char *name, ...) { } + virtual void ExitSection(const char *name, ...) { } + }; + + class NewStateDummy : public NewState + { + private: + long length; + public: + NewStateDummy(); + long GetLength() { return length; } + void Rewind() { length = 0; } + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); + }; + + class NewStateExternalBuffer : public NewState + { + private: + char *const buffer; + long length; + const long maxlength; + public: + NewStateExternalBuffer(char *buffer, long maxlength); + long GetLength() { return length; } + void Rewind() { length = 0; } + bool Overflow() { return length > maxlength; } + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); + }; + + struct FPtrs + { + void (*Save_)(const void *ptr, size_t size, const char *name); + void (*Load_)(void *ptr, size_t size, const char *name); + void (*EnterSection_)(const char *name); + void (*ExitSection_)(const char *name); + }; + + class NewStateExternalFunctions : public NewState + { + private: + void (*Save_)(const void *ptr, size_t size, const char *name); + void (*Load_)(void *ptr, size_t size, const char *name); + void (*EnterSection_)(const char *name); + void (*ExitSection_)(const char *name); + public: + NewStateExternalFunctions(const FPtrs *ff); + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); + virtual void EnterSection(const char *name, ...); + virtual void ExitSection(const char *name, ...); + }; + + // defines and explicitly instantiates + #define SYNCFUNC(x)\ + template void x::SyncState(EW::NewState *ns);\ + template void x::SyncState(EW::NewState *ns);\ + templatevoid x::SyncState(EW::NewState *ns) + + // N = normal variable + // P = pointer to fixed size data + // S = "sub object" + // T = "ptr to sub object" + // R = pointer, store its offset from some other pointer + // E = general purpose cased value "enum" + + + // first line is default value in converted enum; last line is default value in argument x + #define EBS(x,d) do { int _ttmp = (d); if (isReader) ns->Load(&_ttmp, sizeof(_ttmp), #x); if (0) + #define EVS(x,v,n) else if (!isReader && (x) == (v)) _ttmp = (n); else if (isReader && _ttmp == (n)) (x) = (v) + #define EES(x,d) else if (isReader) (x) = (d); if (!isReader) ns->Save(&_ttmp, sizeof(_ttmp), #x); } while (0) + + #define RSS(x,b) do { if (isReader)\ + { ptrdiff_t _ttmp; ns->Load(&_ttmp, sizeof(_ttmp), #x); (x) = (_ttmp == (ptrdiff_t)0xdeadbeef ? 0 : (b) + _ttmp); }\ + else\ + { ptrdiff_t _ttmp = (x) == 0 ? 0xdeadbeef : (x) - (b); ns->Save(&_ttmp, sizeof(_ttmp), #x); } } while (0) + + #define PSS(x,s) do { if (isReader) ns->Load((x), (s), #x); else ns->Save((x), (s), #x); } while (0) + + #define NSS(x) do { if (isReader) ns->Load(&(x), sizeof(x), #x); else ns->Save(&(x), sizeof(x), #x); } while (0) + + #define SSS(x) do { ns->EnterSection(#x); (x).SyncState(ns); ns->ExitSection(#x); } while (0) + + #define TSS(x) do { ns->EnterSection(#x); (x)->SyncState(ns); ns->ExitSection(#x); } while (0) + + } + + + +#endif //NEWSTATE_H \ No newline at end of file diff --git a/psx/mednadisc/emuware/PACKED.h b/psx/mednadisc/emuware/PACKED.h new file mode 100644 index 0000000000..eaef0c7a40 --- /dev/null +++ b/psx/mednadisc/emuware/PACKED.h @@ -0,0 +1,12 @@ +#ifndef __GNUC__ +#pragma pack(push, 1) +#pragma warning(disable : 4103) +#endif + +#ifndef __PACKED + #ifdef __GNUC__ + #define __PACKED __attribute__((__packed__)) + #else + #define __PACKED + #endif +#endif diff --git a/psx/mednadisc/emuware/PACKED_END.h b/psx/mednadisc/emuware/PACKED_END.h new file mode 100644 index 0000000000..6eb7bd7c15 --- /dev/null +++ b/psx/mednadisc/emuware/PACKED_END.h @@ -0,0 +1,3 @@ +#ifndef __GNUC__ +#pragma pack(pop) +#endif diff --git a/psx/mednadisc/emuware/emuware.cpp b/psx/mednadisc/emuware/emuware.cpp new file mode 100644 index 0000000000..02119e1d59 --- /dev/null +++ b/psx/mednadisc/emuware/emuware.cpp @@ -0,0 +1,3 @@ +#include "emuware.h" + +//this file intentionally empty \ No newline at end of file diff --git a/psx/mednadisc/emuware/emuware.h b/psx/mednadisc/emuware/emuware.h new file mode 100644 index 0000000000..ae00914128 --- /dev/null +++ b/psx/mednadisc/emuware/emuware.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include + +#ifdef _MSC_VER +typedef __int64 s64; +typedef __int32 s32; +typedef __int16 s16; +typedef __int8 s8; +typedef unsigned __int64 u64; +typedef unsigned __int32 u32; +typedef unsigned __int16 u16; +typedef unsigned __int8 u8; + +typedef __int64 int64; +typedef __int32 int32; +typedef __int16 int16; +typedef __int8 int8; +typedef unsigned __int64 uint64; +typedef unsigned __int32 uint32; +typedef unsigned __int16 uint16; +typedef unsigned __int8 uint8; +#else +typedef __int64_t s64; +typedef __int32_t s32; +typedef __int16_t s16; +typedef __int8_t s8; +typedef __uint64_t u64; +typedef __uint32_t u32; +typedef __uint16_t u16; +typedef __uint8_t u8; + +typedef __int64_t int64; +typedef __int32_t int32; +typedef __int16_t int16; +typedef __int8_t int8; +typedef __uint64_t uint64; +typedef __uint32_t uint32; +typedef __uint16_t uint16; +typedef __uint8_t uint8; +#endif + +#define final +#define noexcept + +#ifdef _MSC_VER +#include +//http://stackoverflow.com/questions/355967/how-to-use-msvc-intrinsics-to-get-the-equivalent-of-this-gcc-code +//if needed +//uint32_t __inline ctz( uint32_t value ) +//{ +// DWORD trailing_zero = 0; +// +// if ( _BitScanForward( &trailing_zero, value ) ) +// { +// return trailing_zero; +// } +// else +// { +// // This is undefined, I better choose 32 than 0 +// return 32; +// } +//} + +uint32 __inline __builtin_clz( uint32_t value ) +{ + unsigned long leading_zero = 0; + + if ( _BitScanReverse( &leading_zero, value ) ) + { + return 31 - leading_zero; + } + else + { + // Same remarks as above + return 32; + } +} +#endif + +//#if MDFN_GCC_VERSION >= MDFN_MAKE_GCCV(4,7,0) +// #define MDFN_ASSUME_ALIGNED(p, align) __builtin_assume_aligned((p), (align)) +//#else +// #define MDFN_ASSUME_ALIGNED(p, align) (p) +//#endif +#define MDFN_ASSUME_ALIGNED(p, align) (p) + +//#define MDFN_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) +#define MDFN_WARN_UNUSED_RESULT + +//#define MDFN_COLD __attribute__((cold)) +#define MDFN_COLD + +//#define NO_INLINE __attribute__((noinline)) +#define NO_INLINE + +//#define MDFN_UNLIKELY(n) __builtin_expect((n) != 0, 0) +//#define MDFN_LIKELY(n) __builtin_expect((n) != 0, 1) +#define MDFN_UNLIKELY(n) (n) +#define MDFN_LIKELY(n) (n) + +//#define MDFN_NOWARN_UNUSED __attribute__((unused)) +#define MDFN_NOWARN_UNUSED + +//#define MDFN_FORMATSTR(a,b,c) __attribute__ ((format (a, b, c))) +#define MDFN_FORMATSTR(a,b,c) + +#define INLINE inline + +#ifndef UNALIGNED +#define UNALIGNED +#endif + +#ifdef _MSC_VER + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strcasecmp _stricmp + #define strncasecmp _strnicmp +#endif + +#define TRUE_1 1 +#define FALSE_0 0 + +#ifndef ARRAY_SIZE +//taken from winnt.h +extern "C++" // templates cannot be declared to have 'C' linkage +template +char (*BLAHBLAHBLAH( UNALIGNED T (&)[N] ))[N]; + +#define ARRAY_SIZE(A) (sizeof(*BLAHBLAHBLAH(A))) +#endif + +//------------alignment macros------------- +//dont apply these to types without further testing. it only works portably here on declarations of variables +//cant we find a pattern other people use more successfully? +#if defined(_MSC_VER) || defined(__INTEL_COMPILER) +#define EW_VAR_ALIGN(X) __declspec(align(X)) +#elif defined(__GNUC__) +#define EW_VAR_ALIGN(X) __attribute__ ((aligned (X))) +#else +#error +#endif +//--------------------------------------------- + +#ifdef EW_EXPORT +#undef EW_EXPORT +#define EW_EXPORT extern "C" __declspec(dllexport) +#else +#define EW_EXPORT extern "C" __declspec(dllimport) +#endif + + +#define SIZEOF_DOUBLE 8 + +#define LSB_FIRST + +//no MSVC support, no use anyway?? +#define override \ No newline at end of file diff --git a/psx/mednadisc/emuware/msvc/changelog.txt b/psx/mednadisc/emuware/msvc/changelog.txt new file mode 100644 index 0000000000..cf0539c253 --- /dev/null +++ b/psx/mednadisc/emuware/msvc/changelog.txt @@ -0,0 +1,138 @@ +------------------------------------------------------------------------ +r26 | 2009-10-02 13:36:47 +0400 | 2 lines + +[Issue 5] Change to "stdint.h" to let compiler search for it in local directory. + +------------------------------------------------------------------------ +r25 | 2009-09-17 23:46:49 +0400 | 2 lines + +[Issue 4] Fix incorrect int8_t behaviour if compiled with /J flag. + +------------------------------------------------------------------------ +r24 | 2009-05-13 14:53:48 +0400 | 2 lines + +Forgot about #ifdef __cplusplus guard around 'extern "C"', so inclusion to C files has been broken. + +------------------------------------------------------------------------ +r23 | 2009-05-12 01:27:45 +0400 | 3 lines + +[Issue 2] Always wrap is included. + +------------------------------------------------------------------------ +r19 | 2007-07-04 02:14:40 +0400 | 3 lines + +Explicitly cast to appropriate type INT8_MIN, INT16_MIN, INT32_MIN and INT64_MIN constants. +Due to their unusual definition in Visual Studio headers (-_Ix_MAX-1) they are propagated to int and thus do not have expected type, causing VS6 strict compiler to claim about type inconsistency. + +------------------------------------------------------------------------ +r18 | 2007-06-26 16:53:23 +0400 | 2 lines + +Better handling of (U)INTx_C macros - now they generate constants of exact width. + +------------------------------------------------------------------------ +r17 | 2007-03-29 20:16:14 +0400 | 2 lines + +Fix typo: Miscrosoft -> Microsoft. + +------------------------------------------------------------------------ +r16 | 2007-02-24 17:32:58 +0300 | 4 lines + +Remove include, as it is not present in Visual Studio 2005 Epxress Edition and required only for INT_PTR and UINT_PTR types. + +'intptr_t' and 'uintptr_t' types now defined explicitly with #ifdef _WIN64. + +------------------------------------------------------------------------ +r15 | 2007-02-11 20:53:05 +0300 | 2 lines + +More correct fix for compilation under VS6. + +------------------------------------------------------------------------ +r14 | 2007-02-11 20:04:32 +0300 | 2 lines + +Bugfix: fix compiling under VS6, when stdint.h enclosed in 'extern "C" {}'. + +------------------------------------------------------------------------ +r13 | 2006-12-13 16:53:11 +0300 | 2 lines + +Make _inline modifier for imaxdiv default option. Use STATIC_IMAXDIV to make it static. + +------------------------------------------------------------------------ +r12 | 2006-12-13 16:42:24 +0300 | 2 lines + +Error message changed: VC6 supported from now. + +------------------------------------------------------------------------ +r11 | 2006-12-13 16:39:33 +0300 | 2 lines + +All (U)INT* types changed to (unsigned) __int*. This should make stdint.h compatible with VC6. + +------------------------------------------------------------------------ +r10 | 2006-12-13 16:20:57 +0300 | 3 lines + +Added INLINE_IMAXDIV define switch. +If INLINE_IMAXDIV is defined imaxdiv() have static modifier. If not - it is _inline. + +------------------------------------------------------------------------ +r9 | 2006-12-13 15:53:52 +0300 | 2 lines + +Error message for non-MSC compiler changed. + +------------------------------------------------------------------------ +r8 | 2006-12-13 12:47:48 +0300 | 2 lines + +Added #ifndef for SIZE_MAX (it is defined in limits.h on MSVSC 8). + +------------------------------------------------------------------------ +r7 | 2006-12-13 01:08:02 +0300 | 2 lines + +License chaged to BSD-derivative. + +------------------------------------------------------------------------ +r6 | 2006-12-13 00:53:20 +0300 | 2 lines + +Added include to avoid warnings when it is included after stdint.h. + +------------------------------------------------------------------------ +r5 | 2006-12-12 00:58:05 +0300 | 2 lines + +BUGFIX: Definitions of INTPTR_MIN, INTPTR_MAX and UINTPTR_MAX for WIN32 and WIN64 was mixed up. + +------------------------------------------------------------------------ +r4 | 2006-12-12 00:51:55 +0300 | 2 lines + +Rise #error if _MSC_VER is not defined. I.e. compiler other then Microsoft Visual C++ is used. + +------------------------------------------------------------------------ +r3 | 2006-12-11 22:54:14 +0300 | 2 lines + +Added include to stdint.h. + +------------------------------------------------------------------------ +r2 | 2006-12-11 21:39:27 +0300 | 2 lines + +Initial check in. + +------------------------------------------------------------------------ +r1 | 2006-12-11 21:30:23 +0300 | 1 line + +Initial directory structure. +------------------------------------------------------------------------ diff --git a/psx/mednadisc/emuware/msvc/inttypes.h b/psx/mednadisc/emuware/msvc/inttypes.h new file mode 100644 index 0000000000..25542771f5 --- /dev/null +++ b/psx/mednadisc/emuware/msvc/inttypes.h @@ -0,0 +1,305 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + + +#endif // _MSC_INTTYPES_H_ ] diff --git a/psx/mednadisc/emuware/msvc/stdint.h b/psx/mednadisc/emuware/msvc/stdint.h new file mode 100644 index 0000000000..59d067302f --- /dev/null +++ b/psx/mednadisc/emuware/msvc/stdint.h @@ -0,0 +1,247 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2008 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C" { +#endif +# include +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#define INTMAX_C INT64_C +#define UINTMAX_C UINT64_C + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] diff --git a/psx/mednadisc/endian.cpp b/psx/mednadisc/endian.cpp new file mode 100644 index 0000000000..05e8745fc3 --- /dev/null +++ b/psx/mednadisc/endian.cpp @@ -0,0 +1,151 @@ +/* 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 "emuware/emuware.h" +#include "endian.h" + +void Endian_A16_Swap(void *src, uint32 nelements) +{ + uint32 i; + uint8 *nsrc = (uint8 *)src; + + for(i = 0; i < nelements; i++) + { + uint8 tmp = nsrc[i * 2]; + + nsrc[i * 2] = nsrc[i * 2 + 1]; + nsrc[i * 2 + 1] = tmp; + } +} + +void Endian_A32_Swap(void *src, uint32 nelements) +{ + uint32 i; + uint8 *nsrc = (uint8 *)src; + + for(i = 0; i < nelements; i++) + { + uint8 tmp1 = nsrc[i * 4]; + uint8 tmp2 = nsrc[i * 4 + 1]; + + nsrc[i * 4] = nsrc[i * 4 + 3]; + nsrc[i * 4 + 1] = nsrc[i * 4 + 2]; + + nsrc[i * 4 + 2] = tmp2; + nsrc[i * 4 + 3] = tmp1; + } +} + +void Endian_A64_Swap(void *src, uint32 nelements) +{ + uint32 i; + uint8 *nsrc = (uint8 *)src; + + for(i = 0; i < nelements; i++) + { + uint8 *base = &nsrc[i * 8]; + + for(int z = 0; z < 4; z++) + { + uint8 tmp = base[z]; + + base[z] = base[7 - z]; + base[7 - z] = tmp; + } + } +} + +void Endian_A16_NE_LE(void *src, uint32 nelements) +{ + #ifdef MSB_FIRST + Endian_A16_Swap(src, nelements); + #endif +} + +void Endian_A32_NE_LE(void *src, uint32 nelements) +{ + #ifdef MSB_FIRST + Endian_A32_Swap(src, nelements); + #endif +} + +void Endian_A64_NE_LE(void *src, uint32 nelements) +{ + #ifdef MSB_FIRST + Endian_A64_Swap(src, nelements); + #endif +} + +// +// +// +void Endian_A16_NE_BE(void *src, uint32 nelements) +{ + #ifdef LSB_FIRST + Endian_A16_Swap(src, nelements); + #endif +} + +void Endian_A32_NE_BE(void *src, uint32 nelements) +{ + #ifdef LSB_FIRST + Endian_A32_Swap(src, nelements); + #endif +} + +void Endian_A64_NE_BE(void *src, uint32 nelements) +{ + #ifdef LSB_FIRST + Endian_A64_Swap(src, nelements); + #endif +} + +static void FlipByteOrder(uint8 *src, uint32 count) +{ + uint8 *start=src; + uint8 *end=src+count-1; + + if((count&1) || !count) return; /* This shouldn't happen. */ + + count >>= 1; + + while(count--) + { + uint8 tmp; + + tmp=*end; + *end=*start; + *start=tmp; + end--; + start++; + } +} + +void Endian_V_NE_LE(void *src, uint32 bytesize) +{ + #ifdef MSB_FIRST + FlipByteOrder((uint8 *)src, bytesize); + #endif +} + +void Endian_V_NE_BE(void *src, uint32 bytesize) +{ + #ifdef LSB_FIRST + FlipByteOrder((uint8 *)src, bytesize); + #endif +} diff --git a/psx/mednadisc/endian.h b/psx/mednadisc/endian.h new file mode 100644 index 0000000000..f0128cdb57 --- /dev/null +++ b/psx/mednadisc/endian.h @@ -0,0 +1,296 @@ +#ifndef __MDFN_ENDIAN_H +#define __MDFN_ENDIAN_H + +#pragma warning(once : 4519) +static INLINE uint32 BitsExtract(const uint8* ptr, const size_t bit_offset, const size_t bit_count) +{ + uint32 ret = 0; + + for(size_t x = 0; x < bit_count; x++) + { + size_t co = bit_offset + x; + bool b = (ptr[co >> 3] >> (co & 7)) & 1; + + ret |= (uint64)b << x; + } + + return ret; +} + +static INLINE void BitsIntract(uint8* ptr, const size_t bit_offset, const size_t bit_count, uint32 value) +{ + for(size_t x = 0; x < bit_count; x++) + { + size_t co = bit_offset + x; + bool b = (value >> x) & 1; + uint8 tmp = ptr[co >> 3]; + + tmp &= ~(1 << (co & 7)); + tmp |= b << (co & 7); + + ptr[co >> 3] = tmp; + } +} + +/* + Regarding safety of calling MDFN_*sb on dynamically-allocated memory with new uint8[], see C++ standard 3.7.3.1(i.e. it should be + safe provided the offsets into the memory are aligned/multiples of the MDFN_*sb access type). malloc()'d and calloc()'d + memory should be safe as well. + + Statically-allocated arrays/memory should be unioned with a big POD type or C++11 "alignas"'d. (May need to audit code to ensure + this is being done). +*/ + +void Endian_A16_Swap(void *src, uint32 nelements); +void Endian_A32_Swap(void *src, uint32 nelements); +void Endian_A64_Swap(void *src, uint32 nelements); + +void Endian_A16_NE_LE(void *src, uint32 nelements); +void Endian_A32_NE_LE(void *src, uint32 nelements); +void Endian_A64_NE_LE(void *src, uint32 nelements); + +void Endian_A16_NE_BE(void *src, uint32 nelements); +void Endian_A32_NE_BE(void *src, uint32 nelements); +void Endian_A64_NE_BE(void *src, uint32 nelements); + +void Endian_V_NE_LE(void *src, uint32 bytesize); +void Endian_V_NE_BE(void *src, uint32 bytesize); + +static INLINE uint16 MDFN_bswap16(uint16 v) +{ + return (v << 8) | (v >> 8); +} + +static INLINE uint32 MDFN_bswap32(uint32 v) +{ + return (v << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | (v >> 24); +} + +static INLINE uint64 MDFN_bswap64(uint64 v) +{ + //octoshock edit + //return (v << 56) | (v >> 56) | ((v & 0xFF00) << 40) | ((v >> 40) & 0xFF00) | ((uint64)MDFN_bswap32(v >> 16) << 16); + return (v << 56) | (v >> 56) | ((v & 0xFF00) << 40) | ((v >> 40) & 0xFF00) | ((uint64)MDFN_bswap32(((uint32)v) >> 16) << 16); +} + +#ifdef LSB_FIRST + #define MDFN_ENDIANH_IS_BIGENDIAN 0 +#else + #define MDFN_ENDIANH_IS_BIGENDIAN 1 +#endif + +// +// X endian. +// +template +static INLINE T MDFN_deXsb(const void* ptr) +{ + T tmp; + + memcpy(&tmp, MDFN_ASSUME_ALIGNED(ptr, (aligned ? sizeof(T) : 1)), sizeof(T)); + + if(isbigendian != -1 && isbigendian != MDFN_ENDIANH_IS_BIGENDIAN) + { + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Gummy penguins."); + + if(sizeof(T) == 8) + return (T)MDFN_bswap64(tmp); + else if(sizeof(T) == 4) + return (T)MDFN_bswap32(tmp); + else if(sizeof(T) == 2) + return (T)MDFN_bswap16(tmp); + } + + return tmp; +} + +// +// Native endian. +// +template +static INLINE T MDFN_densb(const void* ptr) +{ + return MDFN_deXsb<-1, T, aligned>(ptr); +} + +// +// Little endian. +// +template +static INLINE T MDFN_delsb(const void* ptr) +{ + return MDFN_deXsb<0, T, aligned>(ptr); +} + +template +static INLINE uint16 MDFN_de16lsb(const void* ptr) +{ + return MDFN_delsb(ptr); +} + +static INLINE uint32 MDFN_de24lsb(const void* ptr) +{ + const uint8* morp = (const uint8*)ptr; + return(morp[0]|(morp[1]<<8)|(morp[2]<<16)); +} + +template +static INLINE uint32 MDFN_de32lsb(const void* ptr) +{ + return MDFN_delsb(ptr); +} + +template +static INLINE uint64 MDFN_de64lsb(const void* ptr) +{ + return MDFN_delsb(ptr); +} + +// +// Big endian. +// +template +static INLINE T MDFN_demsb(const void* ptr) +{ + return MDFN_deXsb<1, T, aligned>(ptr); +} + +template +static INLINE uint16 MDFN_de16msb(const void* ptr) +{ + return MDFN_demsb(ptr); +} + +static INLINE uint32 MDFN_de24msb(const void* ptr) +{ + const uint8* morp = (const uint8*)ptr; + return((morp[2]<<0)|(morp[1]<<8)|(morp[0]<<16)); +} + +template +static INLINE uint32 MDFN_de32msb(const void* ptr) +{ + return MDFN_demsb(ptr); +} + +template +static INLINE uint64 MDFN_de64msb(const void* ptr) +{ + return MDFN_demsb(ptr); +} + +// +// +// +// +// +// +// +// + +// +// X endian. +// +template +static INLINE void MDFN_enXsb(void* ptr, T value) +{ + T tmp = value; + + if(isbigendian != -1 && isbigendian != MDFN_ENDIANH_IS_BIGENDIAN) + { + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Gummy penguins."); + + if(sizeof(T) == 8) + tmp = (T)MDFN_bswap64(value); + else if(sizeof(T) == 4) + tmp = (T)MDFN_bswap32(value); + else if(sizeof(T) == 2) + tmp = (T)MDFN_bswap16(value); + } + + memcpy(MDFN_ASSUME_ALIGNED(ptr, (aligned ? sizeof(T) : 1)), &tmp, sizeof(T)); +} + +// +// Native endian. +// +template +static INLINE void MDFN_ennsb(void* ptr, T value) +{ + MDFN_enXsb<-1, T, aligned>(ptr, value); +} + +// +// Little endian. +// +template +static INLINE void MDFN_enlsb(void* ptr, T value) +{ + MDFN_enXsb<0, T, aligned>(ptr, value); +} + +template +static INLINE void MDFN_en16lsb(void* ptr, uint16 value) +{ + MDFN_enlsb(ptr, value); +} + +static INLINE void MDFN_en24lsb(void* ptr, uint32 value) +{ + uint8* morp = (uint8*)ptr; + + morp[0] = value; + morp[1] = value >> 8; + morp[2] = value >> 16; +} + +template +static INLINE void MDFN_en32lsb(void* ptr, uint32 value) +{ + MDFN_enlsb(ptr, value); +} + +template +static INLINE void MDFN_en64lsb(void* ptr, uint64 value) +{ + MDFN_enlsb(ptr, value); +} + + +// +// Big endian. +// +template +static INLINE void MDFN_enmsb(void* ptr, T value) +{ + MDFN_enXsb<1, T, aligned>(ptr, value); +} + +template +static INLINE void MDFN_en16msb(void* ptr, uint16 value) +{ + MDFN_enmsb(ptr, value); +} + +static INLINE void MDFN_en24msb(void* ptr, uint32 value) +{ + uint8* morp = (uint8*)ptr; + + morp[0] = value; + morp[1] = value >> 8; + morp[2] = value >> 16; +} + +template +static INLINE void MDFN_en32msb(void* ptr, uint32 value) +{ + MDFN_enmsb(ptr, value); +} + +template +static INLINE void MDFN_en64msb(void* ptr, uint64 value) +{ + MDFN_enmsb(ptr, value); +} + +#endif diff --git a/psx/mednadisc/error.cpp b/psx/mednadisc/error.cpp new file mode 100644 index 0000000000..4229a5c8bc --- /dev/null +++ b/psx/mednadisc/error.cpp @@ -0,0 +1,155 @@ +/* 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 "emuware/emuware.h" +#include "error.h" + +MDFN_Error::MDFN_Error() noexcept +{ + abort(); +} + +MDFN_Error::MDFN_Error(int errno_code_new, const char *format, ...) noexcept +{ + errno_code = errno_code_new; + + error_message = NULL; + + int size = 128; + for(;;) { + va_list ap; + va_start(ap, format); + error_message = (char*)malloc(size); + size *= 2; + int ret = vsprintf(error_message, format, ap); + va_end(ap); + if(ret>=0) + break; + free(error_message); + } +} + + +MDFN_Error::MDFN_Error(const ErrnoHolder &enh) +{ + errno_code = enh.Errno(); + + int size = 128; + for(;;) { + error_message = (char*)malloc(size); + size *= 2; + int ret = sprintf("%s", enh.StrError()); + if(ret>=0) + break; + free(error_message); + } +} + + +MDFN_Error::~MDFN_Error() noexcept +{ + if(error_message) + { + free(error_message); + error_message = NULL; + } +} + +MDFN_Error::MDFN_Error(const MDFN_Error &ze_error) noexcept +{ + if(ze_error.error_message) + error_message = strdup(ze_error.error_message); + else + error_message = NULL; + + errno_code = ze_error.errno_code; +} + +MDFN_Error& MDFN_Error::operator=(const MDFN_Error &ze_error) noexcept +{ + char *new_error_message = ze_error.error_message ? strdup(ze_error.error_message) : NULL; + int new_errno_code = ze_error.errno_code; + + if(error_message) + free(error_message); + + error_message = new_error_message; + errno_code = new_errno_code; + + return(*this); +} + + +const char * MDFN_Error::what(void) const noexcept +{ + if(!error_message) + return("Error allocating memory for the error message!"); + + return(error_message); +} + +int MDFN_Error::GetErrno(void) const noexcept +{ + return(errno_code); +} + +static const char *srr_wrap(int ret, const char *local_strerror) +{ + if(ret == -1) + return("ERROR IN strerror_r()!!!"); + + return(local_strerror); +} + +static const char *srr_wrap(const char *ret, const char *local_strerror) +{ + if(ret == NULL) + return("ERROR IN strerror_r()!!!"); + + return(ret); +} + +void ErrnoHolder::SetErrno(int the_errno) +{ + local_errno = the_errno; + + if(the_errno == 0) + local_strerror[0] = 0; + else + { + #ifdef HAVE_STRERROR_R + const char *retv; + + retv = srr_wrap(strerror_r(the_errno, local_strerror, 256), local_strerror); + + if(retv != local_strerror) + strncpy(local_strerror, retv, 255); + + #else // No strerror_r :( + + strncpy(local_strerror, strerror(the_errno), 255); + + #endif + + local_strerror[255] = 0; + } +} + diff --git a/psx/mednadisc/error.h b/psx/mednadisc/error.h new file mode 100644 index 0000000000..ac55bb7823 --- /dev/null +++ b/psx/mednadisc/error.h @@ -0,0 +1,75 @@ +#ifndef __MDFN_ERROR_H +#define __MDFN_ERROR_H + +#include +#include +#include + +#ifdef __cplusplus + +class ErrnoHolder; +class MDFN_Error : public std::exception +{ + public: + + MDFN_Error() noexcept; + + MDFN_Error(int errno_code_new, const char *format, ...) noexcept MDFN_FORMATSTR(gnu_printf, 3, 4); + MDFN_Error(const ErrnoHolder &enh); + + ~MDFN_Error() noexcept; + + MDFN_Error(const MDFN_Error &ze_error) noexcept; + MDFN_Error & operator=(const MDFN_Error &ze_error) noexcept; + + virtual const char *what(void) const noexcept; + int GetErrno(void) const noexcept; + + private: + + int errno_code; + char *error_message; +}; + +class ErrnoHolder +{ + public: + + ErrnoHolder() + { + //SetErrno(0); + local_errno = 0; + local_strerror[0] = 0; + } + + ErrnoHolder(int the_errno) + { + SetErrno(the_errno); + } + + inline int Errno(void) const + { + return(local_errno); + } + + const char *StrError(void) const + { + return(local_strerror); + } + + void operator=(int the_errno) + { + SetErrno(the_errno); + } + + private: + + void SetErrno(int the_errno); + + int local_errno; + char local_strerror[256]; +}; + +#endif + +#endif diff --git a/psx/mednadisc/general.cpp b/psx/mednadisc/general.cpp new file mode 100644 index 0000000000..3a7a2fc04b --- /dev/null +++ b/psx/mednadisc/general.cpp @@ -0,0 +1,366 @@ +/* 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 "emuware/emuware.h" + +#include +#include + +#include +#include + +#include +#include + +#include "general.h" + +#include "error.h" + +#define _(X) X + +#ifdef WIN32 +#define PSS "\\" +#else +#define PSS "/" +#endif + +static struct { + bool untrusted_fip_check; +} s_settings; + + +using namespace std; + +static string BaseDirectory; +static string FileBase; +static string FileExt; /* Includes the . character, as in ".nes" */ +static string FileBaseDirectory; + +void MDFN_SetBaseDirectory(const std::string& dir) +{ + BaseDirectory = string(dir); +} + +std::string MDFN_GetBaseDirectory(void) +{ + return BaseDirectory; +} + +// Really dumb, maybe we should use boost? +static bool IsAbsolutePath(const char *path) +{ + #if PSS_STYLE==4 + if(path[0] == ':') + #elif PSS_STYLE==1 + if(path[0] == '/') + #else + if(path[0] == '\\' + #if PSS_STYLE!=3 + || path[0] == '/' + #endif + ) + #endif + { + return(true); + } + + #if defined(WIN32) || defined(DOS) + if((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z')) + { + if(path[1] == ':') + { + return(true); + } + } + #endif + + return(false); +} + +static bool IsAbsolutePath(const std::string &path) +{ + return(IsAbsolutePath(path.c_str())); +} + +bool MDFN_IsFIROPSafe(const std::string &path) +{ + // + // First, check for any 8-bit characters, and print a warning about portability. + // + for(size_t x = 0; x < path.size(); x++) + { + if(path[x] & 0x80) + { + printf(_("WARNING: Referenced path \"%s\" contains at least one 8-bit non-ASCII character; this may cause portability issues.\n"), path.c_str()); + break; + } + } + + // We could make this more OS-specific, but it shouldn't hurt to try to weed out usage of characters that are path + // separators in one OS but not in another, and we'd also run more of a risk of missing a special path separator case + // in some OS. + if(!s_settings.untrusted_fip_check) + return(true); + + if(path.find('\0') != string::npos) + return(false); + + if(path.find(':') != string::npos) + return(false); + + if(path.find('\\') != string::npos) + return(false); + + if(path.find('/') != string::npos) + return(false); + +#if defined(DOS) || defined(WIN32) + // + // http://support.microsoft.com/kb/74496 + // + { + static const char* dev_names[] = + { + "CON", "PRN", "AUX", "CLOCK$", "NUL", "COM1", "COM2", "COM3", "COM4", "LPT1", "LPT2", "LPT3", NULL + }; + + for(const char** ls = dev_names; *ls != NULL; ls++) + { + if(!strcasecmp(*ls, path.c_str())) + return(false); + } + } +#endif + + return(true); +} + +void MDFN_GetFilePathComponents(const std::string &file_path, std::string *dir_path_out, std::string *file_base_out, std::string *file_ext_out) +{ + size_t final_ds; // in file_path + string file_name; + size_t fn_final_dot; // in local var file_name + // Temporary output: + string dir_path, file_base, file_ext; + +#if PSS_STYLE==4 + final_ds = file_path.find_last_of(':'); +#elif PSS_STYLE==1 + final_ds = file_path.find_last_of('/'); +#else + final_ds = file_path.find_last_of('\\'); + + #if PSS_STYLE!=3 + { + size_t alt_final_ds = file_path.find_last_of('/'); + + if(final_ds == string::npos || (alt_final_ds != string::npos && alt_final_ds > final_ds)) + final_ds = alt_final_ds; + } + #endif +#endif + + if(final_ds == string::npos) + { + dir_path = string("."); + file_name = file_path; + } + else + { + dir_path = file_path.substr(0, final_ds); + file_name = file_path.substr(final_ds + 1); + } + + fn_final_dot = file_name.find_last_of('.'); + + if(fn_final_dot != string::npos) + { + file_base = file_name.substr(0, fn_final_dot); + file_ext = file_name.substr(fn_final_dot); + } + else + { + file_base = file_name; + file_ext = string(""); + } + + if(dir_path_out) + *dir_path_out = dir_path; + + if(file_base_out) + *file_base_out = file_base; + + if(file_ext_out) + *file_ext_out = file_ext; +} + +std::string MDFN_EvalFIP(const std::string &dir_path, const std::string &rel_path, bool skip_safety_check) +{ + if(!skip_safety_check && !MDFN_IsFIROPSafe(rel_path)) + throw MDFN_Error(0, _("Referenced path \"%s\" is potentially unsafe. See \"filesys.untrusted_fip_check\" setting.\n"), rel_path.c_str()); + + if(IsAbsolutePath(rel_path.c_str())) + return(rel_path); + else + { + return(dir_path + std::string(PSS) + rel_path); + } +} + + +typedef std::map FSMap; + +static std::string EvalPathFS(const std::string &fstring, /*const (won't work because entry created if char doesn't exist) */ FSMap &fmap) +{ + std::string ret = ""; + const char *str = fstring.c_str(); + bool in_spec = false; + + while(*str) + { + int c = *str; + + if(!in_spec && c == '%') + in_spec = true; + else if(in_spec == true) + { + if(c == '%') + ret = ret + std::string("%"); + else + ret = ret + fmap[(char)c]; + in_spec = false; + } + else + { + char ct[2]; + ct[0] = c; + ct[1] = 0; + ret += std::string(ct); + } + + str++; + } + + return(ret); +} + +#if 0 +static void CreateMissingDirs(const char *path) +{ + const char *s = path; + bool first_psep = true; + char last_char = 0; + const char char_test1 = '/', char_test2 = '/'; + + + while(*s) + { + if(*s == char_test1 || *s == char_test2) + { + if(last_char != *s) //char_test1 && last_char != char_test2) + { + if(!first_psep) + { + char tmpbuf[(s - path) + 1]; + tmpbuf[s - path] = 0; + strncpy(tmpbuf, path, s - path); + + puts(tmpbuf); + //MDFN_mkdir(tmpbuf, S_IRWXU); + } + } + + first_psep = false; + } + last_char = *s; + s++; + } +} +#endif + +const char * GetFNComponent(const char *str) +{ + const char *tp1; + + #if PSS_STYLE==4 + tp1=((char *)strrchr(str,':')); + #elif PSS_STYLE==1 + tp1=((char *)strrchr(str,'/')); + #else + tp1=((char *)strrchr(str,'\\')); + #if PSS_STYLE!=3 + { + const char *tp3; + tp3=((char *)strrchr(str,'/')); + if(tp1tp1)) + { + char* tmpbase = (char*)alloca(tp3 - tp1 + 1); + + memcpy(tmpbase,tp1,tp3-tp1); + tmpbase[tp3-tp1]=0; + FileBase = string(tmpbase); + FileExt = string(tp3); + } + else + { + FileBase = string(tp1); + FileExt = ""; + } +} + diff --git a/psx/mednadisc/general.h b/psx/mednadisc/general.h new file mode 100644 index 0000000000..be305838ec --- /dev/null +++ b/psx/mednadisc/general.h @@ -0,0 +1,52 @@ +#ifndef _GENERAL_H +#define _GENERAL_H + +#include + +#if 0 +class FilePathMaker +{ + + + void SetBaseDirectory(const char* path); + std::string GetBaseDirectory(void); + + void BuildPath(unsigned type, int id1, const char* cd1); + + static void GetFileBase( + +}; +#endif + +void MDFN_SetBaseDirectory(const std::string& dir); +std::string MDFN_GetBaseDirectory(void); + +void GetFileBase(const char *f); + +// File-inclusion for-read-only path, for PSF and CUE/TOC sheet usage. +bool MDFN_IsFIROPSafe(const std::string &path); + +std::string MDFN_MakeFName(int type, int id1, const char *cd1); + +typedef enum +{ + MDFNMKF_STATE = 0, + MDFNMKF_SNAP, + MDFNMKF_SAV, + MDFNMKF_CHEAT, + MDFNMKF_PALETTE, + MDFNMKF_IPS, + MDFNMKF_MOVIE, + MDFNMKF_AUX, + MDFNMKF_SNAP_DAT, + MDFNMKF_CHEAT_TMP, + MDFNMKF_FIRMWARE +} MakeFName_Type; + +std::string MDFN_MakeFName(MakeFName_Type type, int id1, const char *cd1); +INLINE std::string MDFN_MakeFName(MakeFName_Type type, int id1, const std::string& cd1) { return MDFN_MakeFName(type, id1, cd1.c_str()); } +const char * GetFNComponent(const char *str); + +void MDFN_GetFilePathComponents(const std::string &file_path, std::string *dir_path_out, std::string *file_base_out = NULL, std::string *file_ext_out = NULL); +std::string MDFN_EvalFIP(const std::string &dir_path, const std::string &rel_path, bool skip_safety_check = false); +#endif diff --git a/psx/mednadisc/math_ops.h b/psx/mednadisc/math_ops.h new file mode 100644 index 0000000000..656abf6738 --- /dev/null +++ b/psx/mednadisc/math_ops.h @@ -0,0 +1,87 @@ +#ifndef __MDFN_MATH_OPS_H +#define __MDFN_MATH_OPS_H + +// Source: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +// Rounds up to the nearest power of 2. +static INLINE uint64 round_up_pow2(uint64 v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + v++; + + v += (v == 0); + + return(v); +} + +static INLINE uint32 uilog2(uint32 v) +{ + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + + static const uint32 MultiplyDeBruijnBitPosition[32] = + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27]; +} + +// Some compilers' optimizers and some platforms might fubar the generated code from these macros, +// so some tests are run in...tests.cpp +#define sign_8_to_s16(_value) ((int16)(int8)(_value)) +#define sign_9_to_s16(_value) (((int16)((unsigned int)(_value) << 7)) >> 7) +#define sign_10_to_s16(_value) (((int16)((uint32)(_value) << 6)) >> 6) +#define sign_11_to_s16(_value) (((int16)((uint32)(_value) << 5)) >> 5) +#define sign_12_to_s16(_value) (((int16)((uint32)(_value) << 4)) >> 4) +#define sign_13_to_s16(_value) (((int16)((uint32)(_value) << 3)) >> 3) +#define sign_14_to_s16(_value) (((int16)((uint32)(_value) << 2)) >> 2) +#define sign_15_to_s16(_value) (((int16)((uint32)(_value) << 1)) >> 1) + +// This obviously won't convert higher-than-32 bit numbers to signed 32-bit ;) +// Also, this shouldn't be used for 8-bit and 16-bit signed numbers, since you can +// convert those faster with typecasts... +#define sign_x_to_s32(_bits, _value) (((int32)((uint32)(_value) << (32 - _bits))) >> (32 - _bits)) + +static INLINE int32 clamp_to_u8(int32 i) +{ + if(i & 0xFFFFFF00) + i = (((~i) >> 30) & 0xFF); + + return(i); +} + +static INLINE int32 clamp_to_u16(int32 i) +{ + if(i & 0xFFFF0000) + i = (((~i) >> 31) & 0xFFFF); + + return(i); +} + +template static INLINE void clamp(T *val, U minimum, V maximum) +{ + if(*val < minimum) + { + //printf("Warning: clamping to minimum(%d)\n", (int)minimum); + *val = minimum; + } + if(*val > maximum) + { + //printf("Warning: clamping to maximum(%d)\n", (int)maximum); + *val = maximum; + } +} + +#endif diff --git a/psx/mednadisc/string/ConvertUTF.cpp b/psx/mednadisc/string/ConvertUTF.cpp new file mode 100644 index 0000000000..ee02e25c3a --- /dev/null +++ b/psx/mednadisc/string/ConvertUTF.cpp @@ -0,0 +1,560 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + + +#include "../types.h" +#include "ConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +#include +#include + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF +#define false 0 +#define true 1 + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG +if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); +} +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +static bool isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* + * Exported function to return whether a UTF-8 sequence is legal or not. + * This is not used here; it's just exported. + */ +bool isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +UTF32 *MakeUTF32FromUTF8(UTF8 *string) +{ + UTF32 *ret, *tstart; + const UTF8 *tstring = string; + + size_t string_length = strlen((char *)string); + + tstart = ret = (UTF32 *)malloc(string_length * sizeof(UTF32) + 1); + + ConvertUTF8toUTF32(&tstring, &string[string_length], &tstart, &tstart[string_length], lenientConversion); + + *tstart = 0; + + return(ret); +} + + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ diff --git a/psx/mednadisc/string/ConvertUTF.h b/psx/mednadisc/string/ConvertUTF.h new file mode 100644 index 0000000000..a4d64d6b32 --- /dev/null +++ b/psx/mednadisc/string/ConvertUTF.h @@ -0,0 +1,149 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +/* This is for C++ and does no harm in C */ +#ifdef __cplusplus +extern "C" { +#endif + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +bool isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + + +/* Extra Mednafen convenience functions. */ +UTF32 *MakeUTF32FromUTF8(UTF8 *string); + + +#ifdef __cplusplus +} +#endif + +/* --------------------------------------------------------------------- */ diff --git a/psx/mednadisc/string/Makefile.am.inc b/psx/mednadisc/string/Makefile.am.inc new file mode 100644 index 0000000000..ae18bc946d --- /dev/null +++ b/psx/mednadisc/string/Makefile.am.inc @@ -0,0 +1,2 @@ +mednafen_SOURCES += string/escape.cpp string/trim.cpp string/ConvertUTF.cpp + diff --git a/psx/mednadisc/string/escape.cpp b/psx/mednadisc/string/escape.cpp new file mode 100644 index 0000000000..8aed2b324d --- /dev/null +++ b/psx/mednadisc/string/escape.cpp @@ -0,0 +1,157 @@ +/* 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 "../mednafen.h" +#include "escape.h" + +static unsigned int hex_nibble_to_val(char nibble) +{ + unsigned int ret = 0; + nibble = tolower(nibble); + + if(nibble >= '0' && nibble <= '9') + ret = nibble - '0'; + else + ret = nibble - 'a'; + + return(ret); +} + +void unescape_string(char *string) +{ + char *src = string; + bool inescape = 0; + uint8 hoval = 0; + int inhex = 0; + int inoctal = 0; + + while(*src) + { + if(*src == '\\') + { + inescape = TRUE; + inhex = 0; + inoctal = 0; + } + else if(inhex) + { + if(inhex == 1) + { + hoval = hex_nibble_to_val(*src) << 4; + inhex++; + } + else if(inhex == 2) + { + hoval |= hex_nibble_to_val(*src); + *string = hoval; + string++; + hoval = 0; + inhex = 0; + } + } + else if(inoctal) + { + if(inoctal == 1) + { + hoval = (*src - '0') * 8 * 8; + } + else if(inoctal == 2) + { + hoval += (*src - '0') * 8; + } + else + { + hoval += *src - '0'; + *string = hoval; + string++; + hoval = 0; + inoctal = 0; + } + } + else if(inescape) + { + switch(*src) + { + case 'a': *string = 7; string++; break; + case 'b': *string = 8; string++; break; + case 'f': *string = 12; string++; break; + case 'n': *string = 10; string++; break; + case 'r': *string = 13; string++; break; + case 't': *string = 9; string++; break; + case 'v': *string = 11; string++; break; + + case '\\': *string = '\\'; string++; break; + case '?': *string = '?'; string++; break; + case '\'': *string = '\''; string++; break; + case '"': *string = '"'; string++; break; + + case 'o': inoctal = 1; break; + case 'x': inhex = 1; break; + + + default: *string = *src; string++; break; + } + inescape = 0; + } + else + { + *string = *src; + string++; + } + src++; + } + *string = 0; +} + +char *escape_string(const char *text) +{ + uint32 slen = strlen(text); + char *ret = (char*)malloc(slen * 4 + 1); // \xFF + char *outoo = ret; + + for(uint32 x = 0; x < slen; x++) + { + int c = (uint8)text[x]; + + if(c < 0x20 || c == 0x7F || c == '\\' || c == '\'' || c == '"') + { + *outoo++ = '\\'; + + switch(c) + { + case '\\': *outoo++ = '\\'; break; + case '\'': *outoo++ = '\''; break; + case '"': *outoo++ = '"'; break; + case 7: *outoo++ = 'a'; break; + case 8: *outoo++ = 'b'; break; + case 12: *outoo++ = 'f'; break; + case 10: *outoo++ = 'n'; break; + case 13: *outoo++ = 'r'; break; + case 9: *outoo++ = 't'; break; + case 11: *outoo++ = 'v'; break; + + default: outoo += sprintf(outoo, "x%02x", c); break; + } + } + else + *outoo++ = c; + } + + *outoo = 0; + + return(ret); +} diff --git a/psx/mednadisc/string/escape.h b/psx/mednadisc/string/escape.h new file mode 100644 index 0000000000..a1ed431a2f --- /dev/null +++ b/psx/mednadisc/string/escape.h @@ -0,0 +1,9 @@ +#ifndef __MDFN_ESCAPE_H +#define __MDFN_ESCAPE_H + +// These functions are safe to call before calling MDFNI_Initialize(). + +void unescape_string(char *string); +char* escape_string(const char *text); + +#endif diff --git a/psx/mednadisc/string/trim.cpp b/psx/mednadisc/string/trim.cpp new file mode 100644 index 0000000000..b810ccd339 --- /dev/null +++ b/psx/mednadisc/string/trim.cpp @@ -0,0 +1,147 @@ +/* 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 "emuware/emuware.h" +#include "trim.h" + +// Remove whitespace from beginning of string +void MDFN_ltrim(char *string) +{ + int32 di, si; + bool InWhitespace = true; + + di = si = 0; + + while(string[si]) + { + if(InWhitespace && (string[si] == ' ' || string[si] == '\r' || string[si] == '\n' || string[si] == '\t' || string[si] == 0x0b)) + { + + } + else + { + InWhitespace = false; + string[di] = string[si]; + di++; + } + si++; + } + string[di] = 0; +} + +// Remove whitespace from end of string +void MDFN_rtrim(char *string) +{ + int32 len = strlen(string); + + if(len) + { + for(int32 x = len - 1; x >= 0; x--) + { + if(string[x] == ' ' || string[x] == '\r' || string[x] == '\n' || string[x] == '\t' || string[x] == 0x0b) + string[x] = 0; + else + break; + } + } + +} + +void MDFN_trim(char *string) +{ + MDFN_rtrim(string); + MDFN_ltrim(string); +} + + +// Remove whitespace from beginning of string +void MDFN_ltrim(std::string &string) +{ + size_t len = string.length(); + size_t di, si; + bool InWhitespace = true; + + di = si = 0; + + while(si < len) + { + if(InWhitespace && (string[si] == ' ' || string[si] == '\r' || string[si] == '\n' || string[si] == '\t' || string[si] == 0x0b)) + { + + } + else + { + InWhitespace = false; + string[di] = string[si]; + di++; + } + si++; + } + + string.resize(di); +} + +// Remove whitespace from end of string +void MDFN_rtrim(std::string &string) +{ + size_t len = string.length(); + + if(len) + { + size_t x = len; + size_t new_len = len; + + do + { + x--; + + if(!(string[x] == ' ' || string[x] == '\r' || string[x] == '\n' || string[x] == '\t' || string[x] == 0x0b)) + break; + + new_len--; + } while(x); + + string.resize(new_len); + } +} + + +void MDFN_trim(std::string &string) +{ + MDFN_rtrim(string); + MDFN_ltrim(string); +} + + +char *MDFN_RemoveControlChars(char *str) +{ + char *orig = str; + + if(str) + { + while(*str) + { + if((unsigned char)*str < 0x20) + *str = 0x20; + str++; + } + } + + return(orig); +} + diff --git a/psx/mednadisc/string/trim.h b/psx/mednadisc/string/trim.h new file mode 100644 index 0000000000..691de8e9cd --- /dev/null +++ b/psx/mednadisc/string/trim.h @@ -0,0 +1,14 @@ +#ifndef __MDFN_STRING_TRIM_H +#define __MDFN_STRING_TRIM_H + +void MDFN_ltrim(char *string); +void MDFN_rtrim(char *string); +void MDFN_trim(char *string); + +void MDFN_ltrim(std::string &string); +void MDFN_rtrim(std::string &string); +void MDFN_trim(std::string &string); + +char *MDFN_RemoveControlChars(char *str); + +#endif