diff --git a/psx/mednadisc/Mednadisc.cpp b/psx/mednadisc/Mednadisc.cpp index 41d1e95487..25a6dc13a8 100644 --- a/psx/mednadisc/Mednadisc.cpp +++ b/psx/mednadisc/Mednadisc.cpp @@ -25,7 +25,7 @@ EW_EXPORT void* mednadisc_LoadCD(const char* fname) { CDAccess* disc = NULL; try { - disc = cdaccess_open_image(fname,false); + disc = CDAccess_Open(fname,false); } catch(MDFN_Error &) { return NULL; @@ -53,6 +53,9 @@ EW_EXPORT void mednadisc_ReadTOC(MednaDisc* md, JustTOC* justToc, CDUtility::TOC memcpy(tracks101,toc.tracks,sizeof(toc.tracks)); } +//NOTE: the subcode will come out interleaved. +//this is almost useless, but it won't always be needed, so we're not deinterleaving it here yet +//we should probably have more granular control than just reading this one sector eventually EW_EXPORT int32 mednadisc_ReadSector(MednaDisc* md, int lba, void* buf2448) { CDAccess* disc = md->disc; diff --git a/psx/mednadisc/bizhawk/mednadisc.filters b/psx/mednadisc/bizhawk/mednadisc.filters deleted file mode 100644 index 72d05a57b3..0000000000 --- a/psx/mednadisc/bizhawk/mednadisc.filters +++ /dev/null @@ -1,248 +0,0 @@ - - - - - {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.vcxproj b/psx/mednadisc/bizhawk/mednadisc.vcxproj index 1dfd3512b7..f2cbc6f816 100644 --- a/psx/mednadisc/bizhawk/mednadisc.vcxproj +++ b/psx/mednadisc/bizhawk/mednadisc.vcxproj @@ -11,10 +11,10 @@ - + @@ -30,12 +30,15 @@ + + + - + @@ -51,6 +54,7 @@ + {5F35CAFC-6208-4FBE-AD17-0E69BA3F70EC} @@ -81,7 +85,7 @@ true - $(ProjectDir)\..\..\..\output\dll\ + $(ProjectDir)..\..\..\output\dll\ false @@ -92,7 +96,7 @@ NotUsing Level3 Disabled - EW_EXPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;OCTOSHOCK_EXPORTS;%(PreprocessorDefinitions) + TRIO_PUBLIC=;TRIO_PRIVATE=static;EW_EXPORT;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;OCTOSHOCK_EXPORTS;%(PreprocessorDefinitions) ../emuware/msvc;.. diff --git a/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters b/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters index 9aaf63d447..2b0d826726 100644 --- a/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters +++ b/psx/mednadisc/bizhawk/mednadisc.vcxproj.filters @@ -10,6 +10,9 @@ {798fa5bd-6381-487a-99d2-35a15a6da439} + + {a43930f5-41a5-4b2b-92ef-bd90f9716127} + @@ -51,10 +54,19 @@ string - + + + trio + + cdrom - + + trio + + + trio + @@ -96,9 +108,12 @@ string - + + + trio + + cdrom - \ No newline at end of file diff --git a/psx/mednadisc/cdrom/CDAFReader.cpp b/psx/mednadisc/cdrom/CDAFReader.cpp new file mode 100644 index 0000000000..37bafe358c --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader.cpp @@ -0,0 +1,83 @@ +/* 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 + */ + +// CDAFR_Open(), and CDAFReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access +// to it for as long as the CDAFReader object exists. + +// Don't allow exceptions to propagate into the vorbis/musepack/etc. libraries, as it could easily leave the state of the library's decoder "object" in an +// inconsistent state, which would cause all sorts of unfun when we try to destroy it while handling the exception farther up. + +#include "emuware/emuware.h" +#include "CDAFReader.h" +#include "CDAFReader_Vorbis.h" +#include "CDAFReader_MPC.h" + +#ifdef HAVE_LIBSNDFILE +#include "CDAFReader_SF.h" +#endif + +CDAFReader::CDAFReader() : LastReadPos(0) +{ + +} + +CDAFReader::~CDAFReader() +{ + +} + +CDAFReader* CDAFR_Null_Open(Stream* fp) +{ + return NULL; +} + +CDAFReader *CDAFR_Open(Stream *fp) +{ + static CDAFReader* (* const OpenFuncs[])(Stream* fp) = + { +#ifdef HAVE_MPC + CDAFR_MPC_Open, +#endif + +#ifdef HAVE_VORBIS + CDAFR_Vorbis_Open, // Must come before CDAFR_SF_Open +#endif + +#ifdef HAVE_LIBSNDFILE + CDAFR_SF_Open, +#endif + + CDAFR_Null_Open + }; + + for(int idx=0;idxrewind(); + return f(fp); + } + catch(int i) + { + + } + } + + return(NULL); +} + diff --git a/psx/mednadisc/cdrom/CDAFReader.h b/psx/mednadisc/cdrom/CDAFReader.h new file mode 100644 index 0000000000..9ac0880294 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader.h @@ -0,0 +1,41 @@ +#ifndef __MDFN_CDAFREADER_H +#define __MDFN_CDAFREADER_H + +#include "Stream.h" + +class CDAFReader +{ + public: + CDAFReader(); + virtual ~CDAFReader(); + + virtual uint64 FrameCount(void) = 0; + INLINE uint64 Read(uint64 frame_offset, int16 *buffer, uint64 frames) + { + uint64 ret; + + if(LastReadPos != frame_offset) + { + //puts("SEEK"); + if(!Seek_(frame_offset)) + return(0); + LastReadPos = frame_offset; + } + + ret = Read_(buffer, frames); + LastReadPos += ret; + return(ret); + } + + private: + virtual uint64 Read_(int16 *buffer, uint64 frames) = 0; + virtual bool Seek_(uint64 frame_offset) = 0; + + uint64 LastReadPos; +}; + +// AR_Open(), and CDAFReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access +// to it for as long as the CDAFReader object exists. +CDAFReader *CDAFR_Open(Stream *fp); + +#endif diff --git a/psx/mednadisc/cdrom/CDAFReader_MPC.cpp b/psx/mednadisc/cdrom/CDAFReader_MPC.cpp new file mode 100644 index 0000000000..3f26026c17 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader_MPC.cpp @@ -0,0 +1,238 @@ +/* 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 "CDAFReader.h" +#include "CDAFReader_MPC.h" + +#if 0 + #include +#else + #include +#endif + +class CDAFReader_MPC final : public CDAFReader +{ + public: + CDAFReader_MPC(Stream *fp); + ~CDAFReader_MPC(); + + uint64 Read_(int16 *buffer, uint64 frames) override; + bool Seek_(uint64 frame_offset) override; + uint64 FrameCount(void) override; + + 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); +} + +CDAFReader_MPC::CDAFReader_MPC(Stream *fp) : fw(fp) +{ + demux = NULL; + memset(&si, 0, sizeof(si)); + memset(MPCBuffer, 0, sizeof(MPCBuffer)); + MPCBufferOffs = 0; + MPCBufferIn = 0; + + memset(&reader, 0, sizeof(reader)); + reader.read = impc_read; + reader.seek = impc_seek; + reader.tell = impc_tell; + reader.get_size = impc_get_size; + reader.canseek = impc_canseek; + reader.data = (void*)fp; + + if(!(demux = mpc_demux_init(&reader))) + { + throw(0); + } + mpc_demux_get_info(demux, &si); + + if(si.channels != 2) + { + mpc_demux_exit(demux); + demux = NULL; + throw MDFN_Error(0, _("MusePack stream has wrong number of channels(%u); the correct number is 2."), si.channels); + } + + if(si.sample_freq != 44100) + { + mpc_demux_exit(demux); + demux = NULL; + throw MDFN_Error(0, _("MusePack stream has wrong samplerate(%u Hz); the correct samplerate is 44100 Hz."), si.sample_freq); + } +} + +CDAFReader_MPC::~CDAFReader_MPC() +{ + if(demux) + { + mpc_demux_exit(demux); + demux = NULL; + } +} + +uint64 CDAFReader_MPC::Read_(int16 *buffer, uint64 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 CDAFReader_MPC::Seek_(uint64 frame_offset) +{ + MPCBufferOffs = 0; + MPCBufferIn = 0; + + if(mpc_demux_seek_sample(demux, frame_offset) < 0) + return(false); + + return(true); +} + +uint64 CDAFReader_MPC::FrameCount(void) +{ + return(mpc_streaminfo_get_length_samples(&si)); +} + + +CDAFReader* CDAFR_MPC_Open(Stream* fp) +{ + return new CDAFReader_MPC(fp); +} diff --git a/psx/mednadisc/cdrom/CDAFReader_MPC.h b/psx/mednadisc/cdrom/CDAFReader_MPC.h new file mode 100644 index 0000000000..6580fba79c --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader_MPC.h @@ -0,0 +1,6 @@ +#ifndef __MDFN_CDAFREADER_MPC_H +#define __MDFN_CDAFREADER_MPC_H + +CDAFReader* CDAFR_MPC_Open(Stream* fp); + +#endif diff --git a/psx/mednadisc/cdrom/CDAFReader_SF.cpp b/psx/mednadisc/cdrom/CDAFReader_SF.cpp new file mode 100644 index 0000000000..9abc6a6f0d --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader_SF.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 "CDAFReader.h" +#include "CDAFReader_SF.h" + +#include + +class CDAFReader_SF final : public CDAFReader +{ + public: + + CDAFReader_SF(Stream *fp); + ~CDAFReader_SF(); + + uint64 Read_(int16 *buffer, uint64 frames) override; + bool Seek_(uint64 frame_offset) override; + uint64 FrameCount(void) override; + + 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); + } +} + +CDAFReader_SF::CDAFReader_SF(Stream *fp) : fw(fp) +{ + 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); +} + +CDAFReader_SF::~CDAFReader_SF() +{ + sf_close(sf); +} + +uint64 CDAFReader_SF::Read_(int16 *buffer, uint64 frames) +{ + return(sf_read_short(sf, (short*)buffer, frames * 2) / 2); +} + +bool CDAFReader_SF::Seek_(uint64 frame_offset) +{ + // FIXME error condition + if((uint64)sf_seek(sf, frame_offset, SEEK_SET) != frame_offset) + return(false); + + return(true); +} + +uint64 CDAFReader_SF::FrameCount(void) +{ + return(sfinfo.frames); +} + + +CDAFReader* CDAFR_SF_Open(Stream* fp) +{ + return new CDAFReader_SF(fp); +} diff --git a/psx/mednadisc/cdrom/CDAFReader_SF.h b/psx/mednadisc/cdrom/CDAFReader_SF.h new file mode 100644 index 0000000000..2ab145bb37 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader_SF.h @@ -0,0 +1,6 @@ +#ifndef __MDFN_CDAFREADER_SF_H +#define __MDFN_CDAFREADER_SF_H + +CDAFReader* CDAFR_SF_Open(Stream* fp); + +#endif diff --git a/psx/mednadisc/cdrom/CDAFReader_Vorbis.cpp b/psx/mednadisc/cdrom/CDAFReader_Vorbis.cpp new file mode 100644 index 0000000000..5975b215b2 --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader_Vorbis.cpp @@ -0,0 +1,158 @@ +/* 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 "CDAFReader.h" +#include "CDAFReader_Vorbis.h" + +#if 0 + #include +#else + #include +#endif + +class CDAFReader_Vorbis final : public CDAFReader +{ + public: + CDAFReader_Vorbis(Stream *fp); + ~CDAFReader_Vorbis(); + + uint64 Read_(int16 *buffer, uint64 frames) override; + bool Seek_(uint64 frame_offset) override; + uint64 FrameCount(void) override; + + private: + OggVorbis_File ovfile; + Stream *fw; +}; + + +static size_t iov_read_func(void *ptr, size_t size, size_t nmemb, void *user_data) +{ + Stream *fw = (Stream*)user_data; + + if(!size) + return(0); + + 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); + } +} + +CDAFReader_Vorbis::CDAFReader_Vorbis(Stream *fp) : fw(fp) +{ + ov_callbacks cb; + + memset(&cb, 0, sizeof(cb)); + cb.read_func = iov_read_func; + cb.seek_func = iov_seek_func; + cb.close_func = iov_close_func; + cb.tell_func = iov_tell_func; + + if(ov_open_callbacks(fp, &ovfile, NULL, 0, cb)) + throw(0); +} + +CDAFReader_Vorbis::~CDAFReader_Vorbis() +{ + ov_clear(&ovfile); +} + +uint64 CDAFReader_Vorbis::Read_(int16 *buffer, uint64 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 CDAFReader_Vorbis::Seek_(uint64 frame_offset) +{ + ov_pcm_seek(&ovfile, frame_offset); + return(true); +} + +uint64 CDAFReader_Vorbis::FrameCount(void) +{ + return(ov_pcm_total(&ovfile, -1)); +} + +CDAFReader* CDAFR_Vorbis_Open(Stream* fp) +{ + return new CDAFReader_Vorbis(fp); +} diff --git a/psx/mednadisc/cdrom/CDAFReader_Vorbis.h b/psx/mednadisc/cdrom/CDAFReader_Vorbis.h new file mode 100644 index 0000000000..f4a8c55b4f --- /dev/null +++ b/psx/mednadisc/cdrom/CDAFReader_Vorbis.h @@ -0,0 +1,7 @@ +#ifndef __MDFN_CDAFREADER_VORBIS_H +#define __MDFN_CDAFREADER_VORBIS_H + +CDAFReader* CDAFR_Vorbis_Open(Stream* fp); + +#endif + diff --git a/psx/mednadisc/cdrom/CDAccess.cpp b/psx/mednadisc/cdrom/CDAccess.cpp index 641fc4c4ae..14cf659adf 100644 --- a/psx/mednadisc/cdrom/CDAccess.cpp +++ b/psx/mednadisc/cdrom/CDAccess.cpp @@ -1,58 +1,46 @@ -/* 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 -} +/* 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" + +using namespace CDUtility; + +CDAccess::CDAccess() +{ + +} + +CDAccess::~CDAccess() +{ + +} + +CDAccess* CDAccess_Open(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; +} + diff --git a/psx/mednadisc/cdrom/CDAccess.h b/psx/mednadisc/cdrom/CDAccess.h index 667c6b8b66..fd4338d15e 100644 --- a/psx/mednadisc/cdrom/CDAccess.h +++ b/psx/mednadisc/cdrom/CDAccess.h @@ -1,32 +1,33 @@ -#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 +#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; + + // Returns false if the read wouldn't be "fast"(i.e. reading from a disk), + // or if the read can't be done in a thread-safe re-entrant manner. + // + // Writes 96 bytes into pwbuf, and returns 'true' otherwise. + virtual bool Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept = 0; + + virtual void Read_TOC(CDUtility::TOC *toc) = 0; + + private: + CDAccess(const CDAccess&); // No copy constructor. + CDAccess& operator=(const CDAccess&); // No assignment operator. +}; + +CDAccess* CDAccess_Open(const std::string& path, bool image_memcache); + +#endif diff --git a/psx/mednadisc/cdrom/CDAccess_CCD.cpp b/psx/mednadisc/cdrom/CDAccess_CCD.cpp index 6817a20d03..0ec073e17d 100644 --- a/psx/mednadisc/cdrom/CDAccess_CCD.cpp +++ b/psx/mednadisc/cdrom/CDAccess_CCD.cpp @@ -1,435 +1,432 @@ -/* 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) -{ - -} - +/* 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 CHECK_CCD_GARBAGE_CUBQ_A +//#define CHECK_CCD_GARBAGE_CUBQ_B +//#define CHECK_CCD_GARBAGE_CUBQ_C +//#define CHECK_CCD_GARBAGE_CUBQ_D + +#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_numsectors(0) +{ + Load(path, image_memcache); +} + +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]; + trio_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; + tocd.tracks[point].valid = true; + } + 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; + tocd.tracks[100].valid = true; + break; + } + } + } + + // + // 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.reset(new MemoryStream(new FileStream(image_path, FileStream::MODE_READ))); + } + else + { + img_stream.reset(new FileStream(image_path, FileStream::MODE_READ)); + } + + uint64 ss = img_stream->size(); + + if(ss % 2352) + throw MDFN_Error(0, _("CCD image size is not evenly divisible by 2352.")); + + if(ss > 0x7FFFFFFF) + throw MDFN_Error(0, _("CCD image is too large.")); + + img_numsectors = ss / 2352; + } + + // + // Open subchannel stream + { + std::string sub_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(sub_extsd), true); + FileStream sub_stream(sub_path, FileStream::MODE_READ); + + if(sub_stream.size() != (uint64)img_numsectors * 96) + throw MDFN_Error(0, _("CCD SUB file size mismatch.")); + + sub_data.reset(new uint8[(uint64)img_numsectors * 96]); + sub_stream.read(sub_data.get(), (uint64)img_numsectors * 96); + } + + 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; + + memcpy(buf.full, &sub_data[s * 96], 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) + { + #ifdef CHECK_CCD_GARBAGE_SUBQ_A + 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); + #endif + } + 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); + + #ifdef CHECK_CCD_GARBAGE_SUBQ_B + if(prev_lba != INT_MAX && abs(lba - prev_lba) > 100) + throw MDFN_Error(0, _("Garbage subchannel Q data detected(excessively large jump in AMSF)")); + #endif + + #ifdef CHECK_CCD_GARBAGE_SUBQ_C + if(abs(lba - (int)s) > 100) //zero 19-jun-2015 a bit of a sneaky signed/unsigned fixup here + throw MDFN_Error(0, _("Garbage subchannel Q data detected(AMSF value is out of tolerance)")); + #endif + + prev_lba = lba; + + #ifdef CHECK_CCD_GARBAGE_SUBQ_D + if(track < prev_track) + throw MDFN_Error(0, _("Garbage subchannel Q data detected(bad track number)")); + #endif + //else if(prev_track && track - pre + + prev_track = track; + } + checksum_pass_counter++; + } + } + } + + //printf("%u/%u\n", checksum_pass_counter, img_numsectors); +} + +CDAccess_CCD::~CDAccess_CCD() +{ + +} + +void CDAccess_CCD::Read_Raw_Sector(uint8 *buf, int32 lba) +{ + if(lba < 0) + { + synth_udapp_sector_lba(0xFF, tocd, lba, 0, buf); + return; + } + + if((size_t)lba >= img_numsectors) + { + synth_leadout_sector_lba(0xFF, tocd, lba, buf); + return; + } + + img_stream->seek(lba * 2352, SEEK_SET); + img_stream->read(buf, 2352); + + subpw_interleave(&sub_data[lba * 96], buf + 2352); +} + +bool CDAccess_CCD::Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept +{ + if(lba < 0) + { + subpw_synth_udapp_lba(tocd, lba, 0, pwbuf); + return true; + } + + if((size_t)lba >= img_numsectors) + { + subpw_synth_leadout_lba(tocd, lba, pwbuf); + return true; + } + + subpw_interleave(&sub_data[lba * 96], pwbuf); + + return true; +} + +void CDAccess_CCD::Read_TOC(CDUtility::TOC *toc) +{ + *toc = tocd; +} + diff --git a/psx/mednadisc/cdrom/CDAccess_CCD.h b/psx/mednadisc/cdrom/CDAccess_CCD.h index 23a07f6aaf..ffc2efa995 100644 --- a/psx/mednadisc/cdrom/CDAccess_CCD.h +++ b/psx/mednadisc/cdrom/CDAccess_CCD.h @@ -1,50 +1,48 @@ -/* 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; -}; +/* 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 bool Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept; + + virtual void Read_TOC(CDUtility::TOC *toc); + + private: + + void Load(const std::string& path, bool image_memcache); + void Cleanup(void); + + void CheckSubQSanity(void); + + std::unique_ptr img_stream; + std::unique_ptr sub_data; + + size_t img_numsectors; + CDUtility::TOC tocd; +}; diff --git a/psx/mednadisc/cdrom/CDAccess_Image.cpp b/psx/mednadisc/cdrom/CDAccess_Image.cpp index 3d669278bf..8e7ae71a37 100644 --- a/psx/mednadisc/cdrom/CDAccess_Image.cpp +++ b/psx/mednadisc/cdrom/CDAccess_Image.cpp @@ -1,1247 +1,1319 @@ -/* 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) -{ - //test whether it exists - FILE* inf = fopen(sbi_path.c_str(),"rb"); - if(inf == NULL) - return; - - 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) -{ - -} - +/* 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. +*/ + +#include "emuware/emuware.h" + +//undo gettext stuff +#define _(X) (X) + +#include + +#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 "CDAFReader.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_CDI_RAW = 0x07, + _DI_FORMAT_COUNT +}; + +static const int32 DI_Size_Table[8] = +{ + 2352, // Audio + 2048, // MODE1 + 2352, // MODE1 RAW + 2336, // MODE2 + 2048, // MODE2 Form 1 + 2324, // Mode 2 Form 2 + 2352, // MODE2 RAW + 2352, // CD-I RAW +}; + +static const char *DI_CDRDAO_Strings[8] = +{ + "AUDIO", + "MODE1", + "MODE1_RAW", + "MODE2", + "MODE2_FORM1", + "MODE2_FORM2", + "MODE2_RAW", + "CDI_RAW" +}; + +static const char *DI_CUE_Strings[8] = +{ + "AUDIO", + "MODE1/2048", + "MODE1/2352", + "MODE2/2336", // FIXME: A guess + "MODE2/2048", // FIXME: A guess + "MODE2/2324", // FIXME: A guess + "MODE2/2352", // FIXME: A guess + "CDI/2352", +}; + +// Should return an offset to the start of the next argument(past any whitespace), or if there isn't a next argument, +// it'll return the length of the src string. +static size_t UnQuotify(const std::string &src, size_t source_offset, std::string &dest, bool parse_quotes = true) +{ + 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 = CDAFR_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 && trio_sscanf(binoffset, "%ld", &tmp_long) == 1) + { + offset += tmp_long; + } + + if(msfoffset && trio_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(trio_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) +{ + //test whether it exists + FILE* inf = fopen(sbi_path.c_str(),"rb"); + if(inf == NULL) + return; + + 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; + } + } +} + +static void StringToMSF(const char* str, unsigned* m, unsigned* s, unsigned* f) +{ + if(trio_sscanf(str, "%u:%u:%u", m, s, f) != 3) + throw MDFN_Error(0, _("M:S:F time \"%s\" is malformed."), str); + + if(*m > 99 || *s > 59 || *f > 74) + throw MDFN_Error(0, _("M:S:F time \"%s\" contains component(s) out of range."), str); +} + +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_0; + 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_1; // 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())); + } + + unsigned int m,s,f; + + StringToMSF(args[0].c_str(), &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())); + } + + unsigned int m,s,f; + + StringToMSF(args[0].c_str(), &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 = CDAFR_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 < 1 || active_track > 99) + { + throw(MDFN_Error(0, _("Invalid track number: %d\n"), active_track)); + } + + 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())); + } + } + else if(cmdbuf == "INDEX") + { + if(active_track >= 0) + { + unsigned int m,s,f; + + StringToMSF(args[1].c_str(), &m, &s, &f); + + 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; + + StringToMSF(args[0].c_str(), &m, &s, &f); + + TmpTrack.pregap = (m * 60 + s) * 75 + f; + } + } + else if(cmdbuf == "POSTGAP") + { + if(active_track >= 0) + { + unsigned int m,s,f; + + StringToMSF(args[0].c_str(), &m, &s, &f); + + 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; + + RunningLBA -= 150; + Tracks[FirstTrack].pregap += 150; + + for(int x = FirstTrack; x < (FirstTrack + NumTracks); x++) + { + if(!Tracks[x].fp && !Tracks[x].AReader) + throw MDFN_Error(0, _("Missing track %u."), 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. + { + if(disc_type != DISC_TYPE_CD_I) + { + switch(Tracks[x].DIFormat) + { + default: break; + + case DI_FORMAT_MODE2: + case DI_FORMAT_MODE2_FORM1: + case DI_FORMAT_MODE2_FORM2: + case DI_FORMAT_MODE2_RAW: + disc_type = DISC_TYPE_CD_XA; + break; + + case DI_FORMAT_CDI_RAW: + disc_type = DISC_TYPE_CD_I; + break; + } + } + } + + 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()); + } + + GenerateTOC(); +} + +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) +{ + uint8 SimuQ[0xC]; + int32 track; + CDRFILE_TRACK_INFO *ct; + + // + // Leadout synthesis + // + if(lba >= total_sectors) + { + uint8 data_synth_mode = (disc_type == DISC_TYPE_CD_XA ? 0x02 : 0x01); + + switch(Tracks[LastTrack].DIFormat) + { + case DI_FORMAT_AUDIO: + break; + + case DI_FORMAT_MODE1_RAW: + case DI_FORMAT_MODE1: + data_synth_mode = 0x01; + break; + + case DI_FORMAT_MODE2_RAW: + case DI_FORMAT_MODE2_FORM1: + case DI_FORMAT_MODE2_FORM2: + case DI_FORMAT_MODE2: + case DI_FORMAT_CDI_RAW: + data_synth_mode = 0x02; + break; + } + + synth_leadout_sector_lba(data_synth_mode, toc, lba, buf); + return; + } + // + // + // + + memset(buf + 2352, 0, 96); + track = MakeSubPQ(lba, buf + 2352); + subq_deinterleave(buf + 2352, SimuQ); + + ct = &Tracks[track]; + + // + // Handle pregap and postgap reading + // + if(lba < (ct->LBA - ct->pregap_dv) || lba >= (ct->LBA + ct->sectors)) + { + int32 pg_offset = lba - ct->LBA; + CDRFILE_TRACK_INFO* et = ct; + + if(pg_offset < -150) + { + if((Tracks[track].subq_control & SUBQ_CTRLF_DATA) && (FirstTrack < track) && !(Tracks[track - 1].subq_control & SUBQ_CTRLF_DATA)) + et = &Tracks[track - 1]; + } + + memset(buf, 0, 2352); + switch(et->DIFormat) + { + case DI_FORMAT_AUDIO: + break; + + case DI_FORMAT_MODE1_RAW: + case DI_FORMAT_MODE1: + encode_mode1_sector(lba + 150, buf); + break; + + case DI_FORMAT_MODE2_RAW: + case DI_FORMAT_MODE2_FORM1: + case DI_FORMAT_MODE2_FORM2: + case DI_FORMAT_MODE2: + case DI_FORMAT_CDI_RAW: + buf[12 + 6] = 0x20; + buf[12 + 10] = 0x20; + encode_mode2_form2_sector(lba + 150, buf); + // TODO: Zero out optional(?) checksum bytes? + break; + } + //printf("Pre/post-gap read, LBA=%d(LBA-track_start_LBA=%d)\n", lba, lba - ct->LBA); + } + else + { + if(ct->AReader) + { + int16 AudioBuf[588 * 2]; + uint64 frames_read = ct->AReader->Read((ct->FileOffset / 4) + (lba - ct->LBA) * 588, AudioBuf, 588); + + ct->LastSamplePos += frames_read; + + if(frames_read > 588) // This shouldn't happen. + { + printf("Error: frames_read out of range: %llu\n", (unsigned long long)frames_read); + frames_read = 0; + } + + if(frames_read < 588) + memset((uint8 *)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: + case DI_FORMAT_CDI_RAW: + ct->fp->read(buf, 2352); + break; + + case DI_FORMAT_MODE2: + ct->fp->read(buf + 16, 2336); + encode_mode2_sector(lba + 150, buf); + break; + + + // FIXME: M2F1, M2F2, does sub-header come before or after user data(standards say before, but I wonder + // about cdrdao...). + case DI_FORMAT_MODE2_FORM1: + ct->fp->read(buf + 24, 2048); + //encode_mode2_form1_sector(lba + 150, buf); + break; + + case DI_FORMAT_MODE2_FORM2: + ct->fp->read(buf + 24, 2324); + //encode_mode2_form2_sector(lba + 150, buf); + break; + + } + + if(ct->SubchannelMode) + ct->fp->read(buf + 2352, 96); + } + } // end if audible part of audio track read. +} + +bool CDAccess_Image::Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept +{ + int32 track; + + if(lba >= total_sectors) + { + subpw_synth_leadout_lba(toc, lba, pwbuf); + return(true); + } + + memset(pwbuf, 0, 96); + try + { + track = MakeSubPQ(lba, pwbuf); + } + catch(...) + { + return(false); + } + + // + // If TOC+BIN has embedded subchannel data, we can't fast-read(synthesize) it... + // + if(Tracks[track].SubchannelMode && lba >= (Tracks[track].LBA - Tracks[track].pregap_dv) && (lba < Tracks[track].LBA + Tracks[track].sectors)) + return(false); + + return(true); +} + +// +// Note: this function makes use of the current contents(as in |=) in SubPWBuf. +// +int32 CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) const +{ + 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; + } + } + + if(!track_found) + throw(MDFN_Error(0, _("Could not find track for sector %u!"), lba)); + + if(lba < Tracks[track].LBA) + lba_relative = Tracks[track].LBA - 1 - lba; + else + lba_relative = lba - Tracks[track].LBA; + + f = (lba_relative % 75); + s = ((lba_relative / 75) % 60); + m = (lba_relative / 75 / 60); + + 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; + + return track; +} + +void CDAccess_Image::Read_TOC(TOC *rtoc) +{ + *rtoc = toc; +} + +void CDAccess_Image::GenerateTOC(void) +{ + toc.Clear(); + + toc.first_track = FirstTrack; + toc.last_track = FirstTrack + NumTracks - 1; + toc.disc_type = disc_type; + + for(int i = FirstTrack; i < FirstTrack + NumTracks; i++) + { + if(Tracks[i].DIFormat == DI_FORMAT_CDI_RAW) + { + toc.first_track = std::min(99, i + 1); + toc.last_track = std::max(toc.first_track, toc.last_track); + } + + toc.tracks[i].lba = Tracks[i].LBA; + toc.tracks[i].adr = ADR_CURPOS; + toc.tracks[i].control = Tracks[i].subq_control; + toc.tracks[i].valid = true; + } + + toc.tracks[100].lba = total_sectors; + toc.tracks[100].adr = ADR_CURPOS; + toc.tracks[100].control = Tracks[FirstTrack + NumTracks - 1].subq_control & 0x4; + toc.tracks[100].valid = true; +} + + diff --git a/psx/mednadisc/cdrom/CDAccess_Image.h b/psx/mednadisc/cdrom/CDAccess_Image.h index 9f3108c4f8..659032164a 100644 --- a/psx/mednadisc/cdrom/CDAccess_Image.h +++ b/psx/mednadisc/cdrom/CDAccess_Image.h @@ -1,102 +1,103 @@ -#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 +#ifndef __MDFN_CDACCESS_IMAGE_H +#define __MDFN_CDACCESS_IMAGE_H + +#include +#include + +class Stream; +class CDAFReader; + +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; + + CDAFReader *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 bool Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept; + + virtual void Read_TOC(CDUtility::TOC *toc); + + private: + + int32 NumTracks; + int32 FirstTrack; + int32 LastTrack; + int32 total_sectors; + uint8 disc_type; + CDRFILE_TRACK_INFO Tracks[100]; // Track #0(HMM?) through 99 + CDUtility::TOC toc; + + std::map> SubQReplaceMap; + + std::string base_dir; + + void ImageOpen(const std::string& path, bool image_memcache); + void LoadSBI(const std::string& sbi_path); + void GenerateTOC(void); + void Cleanup(void); + + // MakeSubPQ will OR the simulated P and Q subchannel data into SubPWBuf. + int32 MakeSubPQ(int32 lba, uint8 *SubPWBuf) const; + + 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/CDUtility.cpp b/psx/mednadisc/cdrom/CDUtility.cpp index c027535237..f1f6fd9ef4 100644 --- a/psx/mednadisc/cdrom/CDUtility.cpp +++ b/psx/mednadisc/cdrom/CDUtility.cpp @@ -1,324 +1,435 @@ -/* 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 - */ - +/* 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]; -} - -} +#include "emuware/emuware.h" +#include "CDUtility.h" +#include "dvdisaster.h" +#include "lec.h" + +#include +// 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[100].control; + + if(toc.tracks[toc.last_track].valid) + control |= toc.tracks[toc.last_track].control & 0x4; + else if(toc.disc_type == DISC_TYPE_CD_I) + control |= 0x4; + + memset(buf, 0, 0xC); + 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(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(out_buf[2352 + 1] & 0x40) + { + if(mode == 0xFF) + { + if(toc.disc_type == DISC_TYPE_CD_XA || toc.disc_type == DISC_TYPE_CD_I) + mode = 0x02; + else + mode = 0x01; + } + + switch(mode) + { + default: + encode_mode0_sector(LBA_to_ABA(lba), out_buf); + break; + + case 0x01: + encode_mode1_sector(LBA_to_ABA(lba), out_buf); + break; + + case 0x02: + out_buf[12 + 6] = 0x20; + out_buf[12 + 10] = 0x20; + encode_mode2_form2_sector(LBA_to_ABA(lba), out_buf); + break; + } + } +} + +// ISO/IEC 10149:1995 (E): 20.2 +// +void subpw_synth_udapp_lba(const TOC& toc, const int32 lba, const int32 lba_subq_relative_offs, uint8* SubPWBuf) +{ + uint8 buf[0xC]; + uint32 lba_relative; + uint32 ma, sa, fa; + uint32 m, s, f; + + if(lba < -150 || lba >= 0) + printf("[BUG] subpw_synth_udapp_lba() lba out of range --- %d\n", lba); + + { + int32 lba_tmp = lba + lba_subq_relative_offs; + + if(lba_tmp < 0) + lba_relative = 0 - 1 - lba_tmp; + else + lba_relative = lba_tmp - 0; + } + + f = (lba_relative % 75); + s = ((lba_relative / 75) % 60); + m = (lba_relative / 75 / 60); + + fa = (lba + 150) % 75; + sa = ((lba + 150) / 75) % 60; + ma = ((lba + 150) / 75 / 60); + + uint8 adr = 0x1; // Q channel data encodes position + uint8 control; + + if(toc.disc_type == DISC_TYPE_CD_I && toc.first_track > 1) + control = 0x4; + else if(toc.tracks[toc.first_track].valid) + control = toc.tracks[toc.first_track].control; + else + control = 0x0; + + memset(buf, 0, 0xC); + buf[0] = (adr << 0) | (control << 4); + buf[1] = U8_to_BCD(toc.first_track); + buf[2] = U8_to_BCD(0x00); + + // Track relative MSF address + buf[3] = U8_to_BCD(m); + buf[4] = U8_to_BCD(s); + buf[5] = U8_to_BCD(f); + + buf[6] = 0; // Zerroooo + + // Absolute MSF address + buf[7] = U8_to_BCD(ma); + buf[8] = U8_to_BCD(sa); + buf[9] = U8_to_BCD(fa); + + subq_generate_checksum(buf); + + for(int i = 0; i < 96; i++) + SubPWBuf[i] = (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | 0x80; +} + +void synth_udapp_sector_lba(uint8 mode, const TOC& toc, const int32 lba, int32 lba_subq_relative_offs, uint8* out_buf) +{ + memset(out_buf, 0, 2352 + 96); + subpw_synth_udapp_lba(toc, lba, lba_subq_relative_offs, out_buf + 2352); + + if(out_buf[2352 + 1] & 0x40) + { + if(mode == 0xFF) + { + if(toc.disc_type == DISC_TYPE_CD_XA || toc.disc_type == DISC_TYPE_CD_I) + mode = 0x02; + else + mode = 0x01; + } + + switch(mode) + { + default: + encode_mode0_sector(LBA_to_ABA(lba), out_buf); + break; + + case 0x01: + encode_mode1_sector(LBA_to_ABA(lba), out_buf); + break; + + case 0x02: + out_buf[12 + 6] = 0x20; + out_buf[12 + 10] = 0x20; + encode_mode2_form2_sector(LBA_to_ABA(lba), out_buf); + break; + } + } +} + +#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 index 8570fd24d3..b796acfaaf 100644 --- a/psx/mednadisc/cdrom/CDUtility.h +++ b/psx/mednadisc/cdrom/CDUtility.h @@ -1,225 +1,234 @@ -#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 +#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; + bool valid; // valid/present; oh CD-i... + }; + + // SubQ control field flags. + enum + { + SUBQ_CTRLF_PRE = 0x01, // With 50/15us pre-emphasis. + SUBQ_CTRLF_DCP = 0x02, // Digital copy permitted. + SUBQ_CTRLF_DATA = 0x04, // Data track. + SUBQ_CTRLF_4CH = 0x08, // 4-channel CD-DA. + }; + + 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) const + { + int32 lvt = 0; + + for(int32 track = 1; track <= 100; track++) + { + if(!tracks[track].valid) + continue; + + if(LBA < tracks[track].lba) + break; + + lvt = track; + } + + return(lvt); + } + + uint8 first_track; + uint8 last_track; + uint8 disc_type; + TOC_Track tracks[100 + 1]; // [0] is unused, [100] is for the leadout track. + }; + + // + // 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 + + + // User data area pre-pause(MSF 00:00:00 through 00:01:74), lba -150 through -1 + // out_buf must be able to contain 2352+96 bytes. + // "mode" is not used if the area is to be encoded as audio. + // pass 0xFF for "mode" for "don't know", and to make guess based on the TOC. + void synth_udapp_sector_lba(uint8 mode, const TOC& toc, const int32 lba, int32 lba_subq_relative_offs, uint8* out_buf); + void subpw_synth_udapp_lba(const TOC& toc, const int32 lba, const int32 lba_subq_relative_offs, uint8* SubPWBuf); + + // out_buf must be able to contain 2352+96 bytes. + // "mode" is not used if the area is to be encoded as audio. + // pass 0xFF for "mode" for "don't know", and to make guess based on the TOC. + void synth_leadout_sector_lba(uint8 mode, const TOC& toc, const int32 lba, uint8* out_buf); + void subpw_synth_leadout_lba(const TOC& toc, const int32 lba, uint8* SubPWBuf); + + + // + // 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 index daf38931ee..b06d158c12 100644 --- a/psx/mednadisc/cdrom/Makefile.am.inc +++ b/psx/mednadisc/cdrom/Makefile.am.inc @@ -1,7 +1,11 @@ -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 +mednafen_SOURCES += 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 + +mednafen_SOURCES += cdrom/CDAFReader.cpp +mednafen_SOURCES += cdrom/CDAFReader_Vorbis.cpp +mednafen_SOURCES += cdrom/CDAFReader_MPC.cpp + +if HAVE_LIBSNDFILE +mednafen_SOURCES += cdrom/CDAFReader_SF.cpp +endif diff --git a/psx/mednadisc/cdrom/audioreader.cpp b/psx/mednadisc/cdrom/audioreader.cpp deleted file mode 100644 index 86b544417b..0000000000 --- a/psx/mednadisc/cdrom/audioreader.cpp +++ /dev/null @@ -1,617 +0,0 @@ -/* Mednafen - Multi-system Emulator - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -// AR_Open(), and AudioReader, will NOT take "ownership" of the Stream object(IE it won't ever delete it). Though it does assume it has exclusive access -// to it for as long as the AudioReader object exists. - -// Don't allow exceptions to propagate into the vorbis/musepack/etc. libraries, as it could easily leave the state of the library's decoder "object" in an -// inconsistent state, which would cause all sorts of unfun when we try to destroy it while handling the exception farther up. - -#include "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 deleted file mode 100644 index 014a3e48d3..0000000000 --- a/psx/mednadisc/cdrom/audioreader.h +++ /dev/null @@ -1,43 +0,0 @@ -#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 deleted file mode 100644 index 264fde4b06..0000000000 --- a/psx/mednadisc/cdrom/audioreader_opus.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* Mednafen - Multi-system Emulator - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#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 deleted file mode 100644 index 130b3546c7..0000000000 --- a/psx/mednadisc/cdrom/audioreader_opus.h +++ /dev/null @@ -1,21 +0,0 @@ -#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 index 8d35c88ce0..ca09dc74aa 100644 --- a/psx/mednadisc/cdrom/cdromif.cpp +++ b/psx/mednadisc/cdrom/cdromif.cpp @@ -1,434 +1,898 @@ -/* 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); -} +/* 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 +#include + +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 + */ +}; + +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; +}; + +#ifdef WANT_QUEUE +class CDIF_Queue +{ + public: + + CDIF_Queue(); + ~CDIF_Queue(); + + bool Read(CDIF_Message *message, bool blocking = TRUE); + + void Write(const CDIF_Message &message); + + private: + std::queue ze_queue; + MDFN_Mutex *ze_mutex; + MDFN_Cond *ze_cond; +}; +#endif + + +typedef struct +{ + bool valid; + bool error; + int32 lba; + uint8 data[2352 + 96]; +} CDIF_Sector_Buffer; + +#ifdef WANT_QUEUE +// TODO: prohibit copy constructor +class CDIF_MT : public CDIF +{ + public: + + CDIF_MT(CDAccess *cda); + virtual ~CDIF_MT(); + + virtual void HintReadSector(int32 lba); + virtual bool ReadRawSector(uint8 *buf, int32 lba); + virtual bool ReadRawSectorPWOnly(uint8* pwbuf, int32 lba, bool hint_fullread); + + // FIXME: Semi-private: + int ReadThreadStart(void); + + private: + + CDAccess *disc_cdaccess; + + MDFN_Thread *CDReadThread; + + // Queue for messages to the read thread. + CDIF_Queue ReadThreadQueue; + + // Queue for messages to the emu thread. + CDIF_Queue EmuThreadQueue; + + + enum { SBSize = 256 }; + CDIF_Sector_Buffer SectorBuffers[SBSize]; + + uint32 SBWritePos; + + MDFN_Mutex *SBMutex; + MDFN_Cond *SBCond; + + + // + // Read-thread-only: + // + int32 ra_lba; + int32 ra_count; + int32 last_read_lba; +}; +#endif //WANT_QUEUE + + +// TODO: prohibit copy constructor +class CDIF_ST : public CDIF +{ + public: + + CDIF_ST(CDAccess *cda); + virtual ~CDIF_ST(); + + virtual void HintReadSector(int32 lba); + virtual bool ReadRawSector(uint8 *buf, int32 lba); + virtual bool ReadRawSectorPWOnly(uint8* pwbuf, int32 lba, bool hint_fullread); + + private: + CDAccess *disc_cdaccess; +}; + +CDIF::CDIF() : UnrecoverableError(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() +{ + +} + +#ifdef WANT_QUEUE +CDIF_Queue::CDIF_Queue() +{ + ze_mutex = MDFND_CreateMutex(); + ze_cond = MDFND_CreateCond(); +} + +CDIF_Queue::~CDIF_Queue() +{ + MDFND_DestroyMutex(ze_mutex); + MDFND_DestroyCond(ze_cond); +} + +// Returns FALSE if message not read, TRUE if it was read. Will always return TRUE if "blocking" is set. +// Will throw MDFN_Error if the read message code is CDIF_MSG_FATAL_ERROR +bool CDIF_Queue::Read(CDIF_Message *message, bool blocking) +{ + bool ret = true; + + // + // + // + MDFND_LockMutex(ze_mutex); + + if(blocking) + { + while(ze_queue.size() == 0) // while, not just if. + { + MDFND_WaitCond(ze_cond, ze_mutex); + } + } + + if(ze_queue.size() == 0) + ret = false; + else + { + *message = ze_queue.front(); + ze_queue.pop(); + } + + MDFND_UnlockMutex(ze_mutex); + // + // + // + + if(ret && message->message == CDIF_MSG_FATAL_ERROR) + throw MDFN_Error(0, "%s", message->str_message.c_str()); + + return(ret); +} + +void CDIF_Queue::Write(const CDIF_Message &message) +{ + MDFND_LockMutex(ze_mutex); + + try + { + ze_queue.push(message); + } + catch(...) + { + fprintf(stderr, "\n\nCDIF_Message queue push failed!!! (We now return you to your regularly unscheduled lockup)\n\n"); + } + + MDFND_SignalCond(ze_cond); // Signal while the mutex is held to prevent icky race conditions. + + MDFND_UnlockMutex(ze_mutex); +} + +struct RTS_Args +{ + CDIF_MT *cdif_ptr; +}; + +static int ReadThreadStart_C(void *v_arg) +{ + RTS_Args *args = (RTS_Args *)v_arg; + + return args->cdif_ptr->ReadThreadStart(); +} + +int CDIF_MT::ReadThreadStart() +{ + bool Running = TRUE; + + SBWritePos = 0; + ra_lba = 0; + ra_count = 0; + last_read_lba = LBA_Read_Maximum + 1; + + try + { + 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)); + } + + SBWritePos = 0; + ra_lba = 0; + ra_count = 0; + last_read_lba = LBA_Read_Maximum + 1; + memset(SectorBuffers, 0, SBSize * sizeof(CDIF_Sector_Buffer)); + } + catch(std::exception &e) + { + EmuThreadQueue.Write(CDIF_Message(CDIF_MSG_FATAL_ERROR, std::string(e.what()))); + return(0); + } + + EmuThreadQueue.Write(CDIF_Message(CDIF_MSG_DONE)); + + while(Running) + { + CDIF_Message msg; + + // Only do a blocking-wait for a message if we don't have any sectors to read-ahead. + // MDFN_DispMessage("%d %d %d\n", last_read_lba, ra_lba, ra_count); + if(ReadThreadQueue.Read(&msg, ra_count ? FALSE : TRUE)) + { + switch(msg.message) + { + case CDIF_MSG_DIEDIEDIE: + Running = FALSE; + break; + + case CDIF_MSG_READ_SECTOR: + { + static const int max_ra = 16; + static const int initial_ra = 1; + static const int speedmult_ra = 2; + int32 new_lba = msg.args[0]; + + assert((unsigned int)max_ra < (SBSize / 4)); + + if(new_lba == (last_read_lba + 1)) + { + int how_far_ahead = ra_lba - new_lba; + + if(how_far_ahead <= max_ra) + ra_count = std::min(speedmult_ra, 1 + max_ra - how_far_ahead); + else + ra_count++; + } + else if(new_lba != last_read_lba) + { + ra_lba = new_lba; + ra_count = initial_ra; + } + + last_read_lba = new_lba; + } + break; + } + } + + // + // Don't read beyond what the disc (image) readers can handle sanely. + // + if(ra_count && ra_lba == LBA_Read_Maximum) + { + ra_count = 0; + //printf("Ephemeral scarabs: %d!\n", ra_lba); + } + + if(ra_count) + { + uint8 tmpbuf[2352 + 96]; + bool error_condition = false; + + try + { + disc_cdaccess->Read_Raw_Sector(tmpbuf, ra_lba); + } + catch(std::exception &e) + { + MDFN_PrintError(_("Sector %u read error: %s"), ra_lba, e.what()); + memset(tmpbuf, 0, sizeof(tmpbuf)); + error_condition = true; + } + + // + // + MDFND_LockMutex(SBMutex); + + SectorBuffers[SBWritePos].lba = ra_lba; + memcpy(SectorBuffers[SBWritePos].data, tmpbuf, 2352 + 96); + SectorBuffers[SBWritePos].valid = TRUE; + SectorBuffers[SBWritePos].error = error_condition; + SBWritePos = (SBWritePos + 1) % SBSize; + + MDFND_SignalCond(SBCond); + + MDFND_UnlockMutex(SBMutex); + // + // + + ra_lba++; + ra_count--; + } + } + + return(1); +} + +CDIF_MT::CDIF_MT(CDAccess *cda) : disc_cdaccess(cda), CDReadThread(NULL), SBMutex(NULL), SBCond(NULL) +{ + try + { + CDIF_Message msg; + RTS_Args s; + + if(!(SBMutex = MDFND_CreateMutex())) + throw MDFN_Error(0, _("Error creating CD read thread mutex.")); + + if(!(SBCond = MDFND_CreateCond())) + throw MDFN_Error(0, _("Error creating CD read thread condition variable.")); + + UnrecoverableError = false; + + s.cdif_ptr = this; + + if(!(CDReadThread = MDFND_CreateThread(ReadThreadStart_C, &s))) + throw MDFN_Error(0, _("Error creating CD read thread.")); + + EmuThreadQueue.Read(&msg); + } + catch(...) + { + if(CDReadThread) + { + MDFND_WaitThread(CDReadThread, NULL); + CDReadThread = NULL; + } + + if(SBMutex) + { + MDFND_DestroyMutex(SBMutex); + SBMutex = NULL; + } + + if(SBCond) + { + MDFND_DestroyCond(SBCond); + SBCond = NULL; + } + + if(disc_cdaccess) + { + delete disc_cdaccess; + disc_cdaccess = NULL; + } + + throw; + } +} + + +CDIF_MT::~CDIF_MT() +{ + bool thread_deaded_failed = false; + + try + { + ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_DIEDIEDIE)); + } + catch(std::exception &e) + { + MDFND_PrintError(e.what()); + thread_deaded_failed = true; + } + + if(!thread_deaded_failed) + MDFND_WaitThread(CDReadThread, NULL); + + if(SBMutex) + { + MDFND_DestroyMutex(SBMutex); + SBMutex = NULL; + } + + if(SBCond) + { + MDFND_DestroyCond(SBCond); + SBCond = NULL; + } + + if(disc_cdaccess) + { + delete disc_cdaccess; + disc_cdaccess = NULL; + } +} +#endif //WANT_QUEUE + +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); +} + +#ifdef WANT_QUEUE +bool CDIF_MT::ReadRawSector(uint8 *buf, int32 lba) +{ + bool found = FALSE; + bool error_condition = false; + + if(UnrecoverableError) + { + memset(buf, 0, 2352 + 96); + return(false); + } + + if(lba < LBA_Read_Minimum || lba > LBA_Read_Maximum) + { + printf("Attempt to read sector out of bounds; LBA=%d\n", lba); + memset(buf, 0, 2352 + 96); + return(false); + } + + ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_READ_SECTOR, lba)); + + // + // + // + MDFND_LockMutex(SBMutex); + + do + { + for(int i = 0; i < SBSize; i++) + { + if(SectorBuffers[i].valid && SectorBuffers[i].lba == lba) + { + error_condition = SectorBuffers[i].error; + memcpy(buf, SectorBuffers[i].data, 2352 + 96); + found = TRUE; + } + } + + if(!found) + { + //int32 swt = MDFND_GetTime(); + MDFND_WaitCond(SBCond, SBMutex); + //printf("SB Waited: %d\n", MDFND_GetTime() - swt); + } + } while(!found); + + MDFND_UnlockMutex(SBMutex); + // + // + // + + + return(!error_condition); +} + +bool CDIF_MT::ReadRawSectorPWOnly(uint8* pwbuf, int32 lba, bool hint_fullread) +{ + if(UnrecoverableError) + { + memset(pwbuf, 0, 96); + return(false); + } + + if(lba < LBA_Read_Minimum || lba > LBA_Read_Maximum) + { + printf("Attempt to read sector out of bounds; LBA=%d\n", lba); + memset(pwbuf, 0, 96); + return(false); + } + + if(disc_cdaccess->Fast_Read_Raw_PW_TSRE(pwbuf, lba)) + { + if(hint_fullread) + ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_READ_SECTOR, lba)); + + return(true); + } + else + { + uint8 tmpbuf[2352 + 96]; + bool ret; + + ret = ReadRawSector(tmpbuf, lba); + memcpy(pwbuf, tmpbuf + 2352, 96); + + return ret; + } +} + +void CDIF_MT::HintReadSector(int32 lba) +{ + if(UnrecoverableError) + return; + + ReadThreadQueue.Write(CDIF_Message(CDIF_MSG_READ_SECTOR, lba)); +} +#endif //WANT_QUEUE + + +int CDIF::ReadSector(uint8* buf, int32 lba, uint32 sector_count, bool suppress_uncorrectable_message) +{ + int ret = 0; + + if(UnrecoverableError) + return(false); + + while(sector_count--) + { + uint8 tmpbuf[2352 + 96]; + + if(!ReadRawSector(tmpbuf, lba)) + { + puts("CDIF Raw Read error"); + return(FALSE_0); + } + + if(!ValidateRawSector(tmpbuf)) + { + if(!suppress_uncorrectable_message) + { + printf(_("Uncorrectable data at sector %d"), lba); + } + + return(false); + } + + const int mode = tmpbuf[12 + 3]; + + if(!ret) + ret = mode; + + if(mode == 1) + { + memcpy(buf, &tmpbuf[12 + 4], 2048); + } + else if(mode == 2) + { + memcpy(buf, &tmpbuf[12 + 4 + 8], 2048); + } + else + { + printf("CDIF_ReadSector() invalid sector type at LBA=%u\n", (unsigned int)lba); + return(false); + } + + buf += 2048; + lba++; + } + + return(ret); +} + +// +// +// Single-threaded implementation follows. +// +// + +CDIF_ST::CDIF_ST(CDAccess *cda) : disc_cdaccess(cda) +{ + //puts("***WARNING USING SINGLE-THREADED CD READER***"); + + UnrecoverableError = 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(int32 lba) +{ + // TODO: disc_cdaccess seek hint? (probably not, would require asynchronousitycamel) +} + +bool CDIF_ST::ReadRawSector(uint8 *buf, int32 lba) +{ + if(UnrecoverableError) + { + memset(buf, 0, 2352 + 96); + return(false); + } + + if(lba < LBA_Read_Minimum || lba > LBA_Read_Maximum) + { + printf("Attempt to read sector out of bounds; LBA=%d\n", lba); + memset(buf, 0, 2352 + 96); + return(false); + } + + 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::ReadRawSectorPWOnly(uint8* pwbuf, int32 lba, bool hint_fullread) +{ + if(UnrecoverableError) + { + memset(pwbuf, 0, 96); + return(false); + } + + if(lba < LBA_Read_Minimum || lba > LBA_Read_Maximum) + { + printf("Attempt to read sector out of bounds; LBA=%d\n", lba); + memset(pwbuf, 0, 96); + return(false); + } + + if(disc_cdaccess->Fast_Read_Raw_PW_TSRE(pwbuf, lba)) + return(true); + else + { + uint8 tmpbuf[2352 + 96]; + bool ret; + + ret = ReadRawSector(tmpbuf, lba); + memcpy(pwbuf, tmpbuf + 2352, 96); + + return ret; + } +} + +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(int32 lba, uint32 sector_count) +{ + return new CDIF_Stream_Thing(this, lba, sector_count); +} + + +CDIF *CDIF_Open(const std::string& path, bool image_memcache) +{ + CDAccess *cda = CDAccess_Open(path, image_memcache); + +#ifdef WANT_QUEUE + if(!image_memcache) + return new CDIF_MT(cda); + else +#endif + return new CDIF_ST(cda); +} diff --git a/psx/mednadisc/cdrom/cdromif.h b/psx/mednadisc/cdrom/cdromif.h index 845512c796..728f2e6c66 100644 --- a/psx/mednadisc/cdrom/cdromif.h +++ b/psx/mednadisc/cdrom/cdromif.h @@ -1,70 +1,66 @@ -/* 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 +/* 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(); + + static const int32 LBA_Read_Minimum = -150; + static const int32 LBA_Read_Maximum = 449849; // 100 * 75 * 60 - 150 - 1 + + inline void ReadTOC(CDUtility::TOC *read_target) + { + *read_target = disc_toc; + } + + virtual void HintReadSector(int32 lba) = 0; + virtual bool ReadRawSector(uint8 *buf, int32 lba) = 0; // Reads 2352+96 bytes of data into buf. + virtual bool ReadRawSectorPWOnly(uint8* pwbuf, int32 lba, bool hint_fullread) = 0; // Reads 96 bytes(of raw subchannel PW data) into pwbuf. + + // 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* buf, int32 lba, uint32 sector_count, bool suppress_uncorrectable_message = false); + + // For Mode 1, or Mode 2 Form 1. + // No reference counting or whatever is done, so if you destroy the CDIF object before you destroy the returned Stream, things will go BOOM. + Stream *MakeStream(int32 lba, uint32 sector_count); + + protected: + bool UnrecoverableError; + CDUtility::TOC disc_toc; +}; + +CDIF *CDIF_Open(const std::string& path, bool image_memcache); + +#endif diff --git a/psx/mednadisc/cdrom/recover-raw.cpp b/psx/mednadisc/cdrom/recover-raw.cpp index b6d091cbd4..78be2e2a54 100644 --- a/psx/mednadisc/cdrom/recover-raw.cpp +++ b/psx/mednadisc/cdrom/recover-raw.cpp @@ -1,203 +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; -} - +/* 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 index 47091adb5a..4441e9304b 100644 --- a/psx/mednadisc/cdrom/scsicd-pce-commands.inc +++ b/psx/mednadisc/cdrom/scsicd-pce-commands.inc @@ -1,259 +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); -} - +/******************************************************** +* * +* 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 index 64563595f4..21d81e2d1e 100644 --- a/psx/mednadisc/cdrom/scsicd.cpp +++ b/psx/mednadisc/cdrom/scsicd.cpp @@ -1,3246 +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]); - } -} +/* 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 index 3aa82f4f69..8fc7496bd3 100644 --- a/psx/mednadisc/cdrom/scsicd.h +++ b/psx/mednadisc/cdrom/scsicd.h @@ -1,99 +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 +#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/trio/trio.c b/psx/mednadisc/trio/trio.c new file mode 100644 index 0000000000..28b5e4e80d --- /dev/null +++ b/psx/mednadisc/trio/trio.c @@ -0,0 +1,7907 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 1998, 2009 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************* + * + * A note to trio contributors: + * + * Avoid heap allocation at all costs to ensure that the trio functions + * are async-safe. The exceptions are the printf/fprintf functions, which + * uses fputc, and the asprintf functions and the modifier, which + * by design are required to allocate form the heap. + * + ************************************************************************/ + +/* + * TODO: + * - Scan is probably too permissive about its modifiers. + * - C escapes in %#[] ? + * - Multibyte characters (done for format parsing, except scan groups) + * - Complex numbers? (C99 _Complex) + * - Boolean values? (C99 _Bool) + * - C99 NaN(n-char-sequence) missing. The n-char-sequence can be used + * to print the mantissa, e.g. NaN(0xc000000000000000) + * - Should we support the GNU %a alloc modifier? GNU has an ugly hack + * for %a, because C99 used %a for other purposes. If specified as + * %as or %a[ it is interpreted as the alloc modifier, otherwise as + * the C99 hex-float. This means that you cannot scan %as as a hex-float + * immediately followed by an 's'. + * - Scanning of collating symbols. + */ + +/************************************************************************* + * Trio include files + */ +#include "triodef.h" +#include "trio.h" +#include "triop.h" + +#if defined(TRIO_EMBED_NAN) +# define TRIO_PUBLIC_NAN static +# if TRIO_FEATURE_FLOAT +# define TRIO_FUNC_NAN +# define TRIO_FUNC_NINF +# define TRIO_FUNC_PINF +# define TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT +# define TRIO_FUNC_ISINF +# endif +#endif +#include "trionan.h" + +#if defined(TRIO_EMBED_STRING) +# define TRIO_PUBLIC_STRING static +# define TRIO_FUNC_LENGTH +# define TRIO_FUNC_LENGTH_MAX +# define TRIO_FUNC_TO_LONG +# if TRIO_FEATURE_LOCALE +# define TRIO_FUNC_COPY_MAX +# endif +# if TRIO_FEATURE_DYNAMICSTRING +# define TRIO_FUNC_XSTRING_DUPLICATE +# endif +# if TRIO_EXTENSION && TRIO_FEATURE_SCANF +# define TRIO_FUNC_EQUAL_LOCALE +# endif +# if TRIO_FEATURE_ERRNO +# define TRIO_FUNC_ERROR +# endif +# if TRIO_FEATURE_FLOAT && TRIO_FEATURE_SCANF +# define TRIO_FUNC_TO_DOUBLE +# endif +# if TRIO_FEATURE_DYNAMICSTRING +# define TRIO_FUNC_STRING_EXTRACT +# endif +# if TRIO_FEATURE_DYNAMICSTRING +# define TRIO_FUNC_STRING_TERMINATE +# endif +# if TRIO_FEATURE_USER_DEFINED +# define TRIO_FUNC_DUPLICATE +# endif +# if TRIO_FEATURE_DYNAMICSTRING +# define TRIO_FUNC_STRING_DESTROY +# endif +# if TRIO_FEATURE_USER_DEFINED +# define TRIO_FUNC_DESTROY +# endif +# if TRIO_FEATURE_USER_DEFINED || (TRIO_FEATURE_FLOAT && TRIO_FEATURE_SCANF) +# define TRIO_FUNC_EQUAL +# endif +# if TRIO_FEATURE_USER_DEFINED || TRIO_FEATURE_SCANF +# define TRIO_FUNC_EQUAL_CASE +# endif +# if (TRIO_EXTENSION && TRIO_FEATURE_SCANF) +# define TRIO_FUNC_EQUAL_MAX +# endif +# if TRIO_FEATURE_SCANF +# define TRIO_FUNC_TO_UPPER +# endif +# if TRIO_FEATURE_DYNAMICSTRING +# define TRIO_FUNC_XSTRING_APPEND_CHAR +# endif +#endif +#include "triostr.h" + +/************************************************************************** + * + * Definitions + * + *************************************************************************/ + +#include +#if TRIO_FEATURE_FLOAT +# include +# include +#endif + +#if defined(__STDC_ISO_10646__) || defined(MB_LEN_MAX) || defined(USE_MULTIBYTE) || TRIO_FEATURE_WIDECHAR +# if !defined(TRIO_PLATFORM_WINCE) +# define TRIO_COMPILER_SUPPORTS_MULTIBYTE +# if !defined(MB_LEN_MAX) +# define MB_LEN_MAX 6 +# endif +# endif +#endif + +#if (defined(TRIO_COMPILER_VISUALC) && (TRIO_COMPILER_VISUALC >= 1100)) || defined(TRIO_COMPILER_BORLAND) +# define TRIO_COMPILER_SUPPORTS_VISUALC_INT +#endif + +#if TRIO_FEATURE_FLOAT +# if defined(PREDEF_STANDARD_C99) \ + || defined(PREDEF_STANDARD_UNIX03) +# if !defined(HAVE_FLOORL) && !defined(TRIO_NO_FLOORL) +# define HAVE_FLOORL +# endif +# if !defined(HAVE_CEILL) && !defined(TRIO_NO_CEILL) +# define HAVE_CEILL +# endif +# if !defined(HAVE_POWL) && !defined(TRIO_NO_POWL) +# define HAVE_POWL +# endif +# if !defined(HAVE_FMODL) && !defined(TRIO_NO_FMODL) +# define HAVE_FMODL +# endif +# if !defined(HAVE_LOG10L) && !defined(TRIO_NO_LOG10L) +# define HAVE_LOG10L +# endif +# endif +# if defined(TRIO_COMPILER_VISUALC) +# if defined(floorl) +# define HAVE_FLOORL +# endif +# if defined(ceill) +# define HAVE_CEILL +# endif +# if defined(powl) +# define HAVE_POWL +# endif +# if defined(fmodl) +# define HAVE_FMODL +# endif +# if defined(log10l) +# define HAVE_LOG10L +# endif +# endif +#endif + +/************************************************************************* + * Generic definitions + */ + +#if !(defined(DEBUG) || defined(NDEBUG)) +# define NDEBUG +#endif + +#include +#include +#if defined(PREDEF_STANDARD_C99) && !defined(isascii) +# define isascii(x) ((x) & 0x7F) +#endif +#if defined(TRIO_COMPILER_ANCIENT) +# include +#else +# include +#endif +#include +#if defined(TRIO_PLATFORM_WINCE) +extern int errno; +#else +# include +#endif + +#ifndef NULL +# define NULL 0 +#endif +#define NIL ((char)0) +#ifndef FALSE +# define FALSE (1 == 0) +# define TRUE (! FALSE) +#endif +#define BOOLEAN_T int + +/* mincore() can be used for debugging purposes */ +#define VALID(x) (NULL != (x)) + +#if TRIO_FEATURE_ERRORCODE + /* + * Encode the error code and the position. This is decoded + * with TRIO_ERROR_CODE and TRIO_ERROR_POSITION. + */ +# define TRIO_ERROR_RETURN(x,y) (- ((x) + ((y) << 8))) +#else +# define TRIO_ERROR_RETURN(x,y) (-1) +#endif + +typedef unsigned long trio_flags_t; + + +/************************************************************************* + * Platform specific definitions + */ +#if defined(TRIO_PLATFORM_UNIX) +# include +# include +# include +# if !defined(TRIO_FEATURE_LOCALE) +# define USE_LOCALE +# endif +#endif /* TRIO_PLATFORM_UNIX */ +#if defined(TRIO_PLATFORM_VMS) +# include +#endif +#if defined(TRIO_PLATFORM_WIN32) +# if defined(TRIO_PLATFORM_WINCE) +int read(int handle, char *buffer, unsigned int length); +int write(int handle, const char *buffer, unsigned int length); +# else +# include +# define read _read +# define write _write +# endif +#endif /* TRIO_PLATFORM_WIN32 */ + +#if TRIO_FEATURE_WIDECHAR +# if defined(PREDEF_STANDARD_C94) +# include +# include +typedef wchar_t trio_wchar_t; +typedef wint_t trio_wint_t; +# else +typedef char trio_wchar_t; +typedef int trio_wint_t; +# define WCONST(x) L ## x +# define WEOF EOF +# define iswalnum(x) isalnum(x) +# define iswalpha(x) isalpha(x) +# define iswcntrl(x) iscntrl(x) +# define iswdigit(x) isdigit(x) +# define iswgraph(x) isgraph(x) +# define iswlower(x) islower(x) +# define iswprint(x) isprint(x) +# define iswpunct(x) ispunct(x) +# define iswspace(x) isspace(x) +# define iswupper(x) isupper(x) +# define iswxdigit(x) isxdigit(x) +# endif +#endif + + +/************************************************************************* + * Compiler dependent definitions + */ + +/* Support for long long */ +#ifndef __cplusplus +# if !defined(USE_LONGLONG) +# if defined(TRIO_COMPILER_GCC) && !defined(__STRICT_ANSI__) +# define USE_LONGLONG +# else +# if defined(TRIO_COMPILER_SUNPRO) +# define USE_LONGLONG +# else +# if defined(TRIO_COMPILER_MSVC) && (_MSC_VER >= 1400) +# define USE_LONGLONG +# else +# if defined(_LONG_LONG) || defined(_LONGLONG) +# define USE_LONGLONG +# endif +# endif +# endif +# endif +# endif +#endif + +/* The extra long numbers */ +#if defined(USE_LONGLONG) +typedef signed long long int trio_longlong_t; +typedef unsigned long long int trio_ulonglong_t; +#else +# if defined(TRIO_COMPILER_SUPPORTS_VISUALC_INT) +typedef signed __int64 trio_longlong_t; +typedef unsigned __int64 trio_ulonglong_t; +# else +typedef TRIO_SIGNED long int trio_longlong_t; +typedef unsigned long int trio_ulonglong_t; +# endif +#endif + +/* Maximal and fixed integer types */ +#if defined(PREDEF_STANDARD_C99) +# include +typedef intmax_t trio_intmax_t; +typedef uintmax_t trio_uintmax_t; +typedef int8_t trio_int8_t; +typedef int16_t trio_int16_t; +typedef int32_t trio_int32_t; +typedef int64_t trio_int64_t; +#else +# if defined(PREDEF_STANDARD_UNIX98) +# include +typedef intmax_t trio_intmax_t; +typedef uintmax_t trio_uintmax_t; +typedef int8_t trio_int8_t; +typedef int16_t trio_int16_t; +typedef int32_t trio_int32_t; +typedef int64_t trio_int64_t; +# else +# if defined(TRIO_COMPILER_SUPPORTS_VISUALC_INT) +typedef trio_longlong_t trio_intmax_t; +typedef trio_ulonglong_t trio_uintmax_t; +typedef __int8 trio_int8_t; +typedef __int16 trio_int16_t; +typedef __int32 trio_int32_t; +typedef __int64 trio_int64_t; +# else +typedef trio_longlong_t trio_intmax_t; +typedef trio_ulonglong_t trio_uintmax_t; +# if defined(TRIO_INT8_T) +typedef TRIO_INT8_T trio_int8_t; +# else +typedef TRIO_SIGNED char trio_int8_t; +# endif +# if defined(TRIO_INT16_T) +typedef TRIO_INT16_T trio_int16_t; +# else +typedef TRIO_SIGNED short trio_int16_t; +# endif +# if defined(TRIO_INT32_T) +typedef TRIO_INT32_T trio_int32_t; +# else +typedef TRIO_SIGNED int trio_int32_t; +# endif +# if defined(TRIO_INT64_T) +typedef TRIO_INT64_T trio_int64_t; +# else +typedef trio_longlong_t trio_int64_t; +# endif +# endif +# endif +#endif + +#if defined(HAVE_FLOORL) +# define trio_floor(x) floorl((x)) +#else +# define trio_floor(x) floor((double)(x)) +#endif + +#if defined(HAVE_CEILL) +# define trio_ceil(x) ceill((x)) +#else +# define trio_ceil(x) ceil((double)(x)) +#endif + +#if defined(HAVE_FMODL) +# define trio_fmod(x,y) fmodl((x),(y)) +#else +# define trio_fmod(x,y) fmod((double)(x),(double)(y)) +#endif + +#if defined(HAVE_POWL) +# define trio_pow(x,y) powl((x),(y)) +#else +# define trio_pow(x,y) pow((double)(x),(double)(y)) +#endif + +#if defined(HAVE_LOG10L) +# define trio_log10(x) log10l((x)) +#else +# define trio_log10(x) log10((double)(x)) +#endif + +#if TRIO_FEATURE_FLOAT +# define TRIO_FABS(x) (((x) < 0.0) ? -(x) : (x)) +#endif + +/************************************************************************* + * Internal Definitions + */ + +#if TRIO_FEATURE_FLOAT + +# if !defined(DECIMAL_DIG) +# define DECIMAL_DIG DBL_DIG +# endif + +/* Long double sizes */ +# ifdef LDBL_DIG +# define MAX_MANTISSA_DIGITS LDBL_DIG +# define MAX_EXPONENT_DIGITS 4 +# define MAX_DOUBLE_DIGITS LDBL_MAX_10_EXP +# else +# define MAX_MANTISSA_DIGITS DECIMAL_DIG +# define MAX_EXPONENT_DIGITS 3 +# define MAX_DOUBLE_DIGITS DBL_MAX_10_EXP +# endif + +# if defined(TRIO_COMPILER_ANCIENT) || !defined(LDBL_DIG) +# undef LDBL_DIG +# undef LDBL_MANT_DIG +# undef LDBL_EPSILON +# define LDBL_DIG DBL_DIG +# define LDBL_MANT_DIG DBL_MANT_DIG +# define LDBL_EPSILON DBL_EPSILON +# endif + +#endif /* TRIO_FEATURE_FLOAT */ + +/* The maximal number of digits is for base 2 */ +#define MAX_CHARS_IN(x) (sizeof(x) * CHAR_BIT) +/* The width of a pointer. The number of bits in a hex digit is 4 */ +#define POINTER_WIDTH ((sizeof("0x") - 1) + sizeof(trio_pointer_t) * CHAR_BIT / 4) + +#if TRIO_FEATURE_FLOAT +/* Infinite and Not-A-Number for floating-point */ +# define INFINITE_LOWER "inf" +# define INFINITE_UPPER "INF" +# define LONG_INFINITE_LOWER "infinite" +# define LONG_INFINITE_UPPER "INFINITE" +# define NAN_LOWER "nan" +# define NAN_UPPER "NAN" +#endif + +/* Various constants */ +enum { + TYPE_PRINT = 1, +#if TRIO_FEATURE_SCANF + TYPE_SCAN = 2, +#endif + + /* Flags. FLAGS_LAST must be less than ULONG_MAX */ + FLAGS_NEW = 0, + FLAGS_STICKY = 1, + FLAGS_SPACE = 2 * FLAGS_STICKY, + FLAGS_SHOWSIGN = 2 * FLAGS_SPACE, + FLAGS_LEFTADJUST = 2 * FLAGS_SHOWSIGN, + FLAGS_ALTERNATIVE = 2 * FLAGS_LEFTADJUST, + FLAGS_SHORT = 2 * FLAGS_ALTERNATIVE, + FLAGS_SHORTSHORT = 2 * FLAGS_SHORT, + FLAGS_LONG = 2 * FLAGS_SHORTSHORT, + FLAGS_QUAD = 2 * FLAGS_LONG, + FLAGS_LONGDOUBLE = 2 * FLAGS_QUAD, + FLAGS_SIZE_T = 2 * FLAGS_LONGDOUBLE, + FLAGS_PTRDIFF_T = 2 * FLAGS_SIZE_T, + FLAGS_INTMAX_T = 2 * FLAGS_PTRDIFF_T, + FLAGS_NILPADDING = 2 * FLAGS_INTMAX_T, + FLAGS_UNSIGNED = 2 * FLAGS_NILPADDING, + FLAGS_UPPER = 2 * FLAGS_UNSIGNED, + FLAGS_WIDTH = 2 * FLAGS_UPPER, + FLAGS_WIDTH_PARAMETER = 2 * FLAGS_WIDTH, + FLAGS_PRECISION = 2 * FLAGS_WIDTH_PARAMETER, + FLAGS_PRECISION_PARAMETER = 2 * FLAGS_PRECISION, + FLAGS_BASE = 2 * FLAGS_PRECISION_PARAMETER, + FLAGS_BASE_PARAMETER = 2 * FLAGS_BASE, + FLAGS_FLOAT_E = 2 * FLAGS_BASE_PARAMETER, + FLAGS_FLOAT_G = 2 * FLAGS_FLOAT_E, + FLAGS_QUOTE = 2 * FLAGS_FLOAT_G, + FLAGS_WIDECHAR = 2 * FLAGS_QUOTE, + FLAGS_IGNORE = 2 * FLAGS_WIDECHAR, + FLAGS_IGNORE_PARAMETER = 2 * FLAGS_IGNORE, + FLAGS_VARSIZE_PARAMETER = 2 * FLAGS_IGNORE_PARAMETER, + FLAGS_FIXED_SIZE = 2 * FLAGS_VARSIZE_PARAMETER, + FLAGS_LAST = FLAGS_FIXED_SIZE, + /* Reused flags */ + FLAGS_EXCLUDE = FLAGS_SHORT, + FLAGS_USER_DEFINED = FLAGS_IGNORE, + FLAGS_USER_DEFINED_PARAMETER = FLAGS_IGNORE_PARAMETER, + FLAGS_ROUNDING = FLAGS_INTMAX_T, + /* Compounded flags */ + FLAGS_ALL_VARSIZES = FLAGS_LONG | FLAGS_QUAD | FLAGS_INTMAX_T | FLAGS_PTRDIFF_T | FLAGS_SIZE_T, + FLAGS_ALL_SIZES = FLAGS_ALL_VARSIZES | FLAGS_SHORTSHORT | FLAGS_SHORT, + + NO_POSITION = -1, + NO_WIDTH = 0, + NO_PRECISION = -1, + NO_SIZE = -1, + + /* Do not change these */ + NO_BASE = -1, + MIN_BASE = 2, + MAX_BASE = 36, + BASE_BINARY = 2, + BASE_OCTAL = 8, + BASE_DECIMAL = 10, + BASE_HEX = 16, + + /* Maximal number of allowed parameters */ + MAX_PARAMETERS = 64, + /* Maximal number of characters in class */ + MAX_CHARACTER_CLASS = UCHAR_MAX + 1, + +#if TRIO_FEATURE_USER_DEFINED + /* Maximal string lengths for user-defined specifiers */ + MAX_USER_NAME = 64, + MAX_USER_DATA = 256, +#endif + + /* Maximal length of locale separator strings */ + MAX_LOCALE_SEPARATOR_LENGTH = MB_LEN_MAX, + /* Maximal number of integers in grouping */ + MAX_LOCALE_GROUPS = 64 +}; + +#define NO_GROUPING ((int)CHAR_MAX) + +/* Fundamental formatting parameter types */ +#define FORMAT_SENTINEL -1 /* marks end of parameters array */ +#define FORMAT_UNKNOWN 0 +#define FORMAT_INT 1 +#define FORMAT_DOUBLE 2 +#define FORMAT_CHAR 3 +#define FORMAT_STRING 4 +#define FORMAT_POINTER 5 +#define FORMAT_COUNT 6 +#define FORMAT_PARAMETER 7 +#define FORMAT_GROUP 8 +#define FORMAT_ERRNO 9 +#define FORMAT_USER_DEFINED 10 + +/* Character constants */ +#define CHAR_IDENTIFIER '%' +#define CHAR_ALT_IDENTIFIER '$' +#define CHAR_BACKSLASH '\\' +#define CHAR_QUOTE '\"' +#define CHAR_ADJUST ' ' + +#if TRIO_EXTENSION +/* Character class expressions */ +# define CLASS_ALNUM "[:alnum:]" +# define CLASS_ALPHA "[:alpha:]" +# define CLASS_BLANK "[:blank:]" +# define CLASS_CNTRL "[:cntrl:]" +# define CLASS_DIGIT "[:digit:]" +# define CLASS_GRAPH "[:graph:]" +# define CLASS_LOWER "[:lower:]" +# define CLASS_PRINT "[:print:]" +# define CLASS_PUNCT "[:punct:]" +# define CLASS_SPACE "[:space:]" +# define CLASS_UPPER "[:upper:]" +# define CLASS_XDIGIT "[:xdigit:]" +#endif + +/* + * SPECIFIERS: + * + * + * a Hex-float + * A Hex-float + * c Character + * C Widechar character (wint_t) + * d Decimal + * e Float + * E Float + * F Float + * F Float + * g Float + * G Float + * i Integer + * m Error message + * n Count + * o Octal + * p Pointer + * s String + * S Widechar string (wchar_t *) + * u Unsigned + * x Hex + * X Hex + * [] Group + * <> User-defined + * + * Reserved: + * + * D Binary Coded Decimal %D(length,precision) (OS/390) + */ +#define SPECIFIER_CHAR 'c' +#define SPECIFIER_STRING 's' +#define SPECIFIER_DECIMAL 'd' +#define SPECIFIER_INTEGER 'i' +#define SPECIFIER_UNSIGNED 'u' +#define SPECIFIER_OCTAL 'o' +#define SPECIFIER_HEX 'x' +#define SPECIFIER_HEX_UPPER 'X' +#if TRIO_FEATURE_FLOAT +# define SPECIFIER_FLOAT_E 'e' +# define SPECIFIER_FLOAT_E_UPPER 'E' +# define SPECIFIER_FLOAT_F 'f' +# define SPECIFIER_FLOAT_F_UPPER 'F' +# define SPECIFIER_FLOAT_G 'g' +# define SPECIFIER_FLOAT_G_UPPER 'G' +#endif +#define SPECIFIER_POINTER 'p' +#if TRIO_FEATURE_SCANF +# define SPECIFIER_GROUP '[' +# define SPECIFIER_UNGROUP ']' +#endif +#define SPECIFIER_COUNT 'n' +#if TRIO_UNIX98 +# define SPECIFIER_CHAR_UPPER 'C' +# define SPECIFIER_STRING_UPPER 'S' +#endif +#define SPECIFIER_HEXFLOAT 'a' +#define SPECIFIER_HEXFLOAT_UPPER 'A' +#define SPECIFIER_ERRNO 'm' +#if TRIO_FEATURE_BINARY +# define SPECIFIER_BINARY 'b' +# define SPECIFIER_BINARY_UPPER 'B' +#endif +#if TRIO_FEATURE_USER_DEFINED +# define SPECIFIER_USER_DEFINED_BEGIN '<' +# define SPECIFIER_USER_DEFINED_END '>' +# define SPECIFIER_USER_DEFINED_SEPARATOR ':' +# define SPECIFIER_USER_DEFINED_EXTRA '|' +#endif + +/* + * QUALIFIERS: + * + * + * Numbers = d,i,o,u,x,X + * Float = a,A,e,E,f,F,g,G + * String = s + * Char = c + * + * + * 9$ Position + * Use the 9th parameter. 9 can be any number between 1 and + * the maximal argument + * + * 9 Width + * Set width to 9. 9 can be any number, but must not be postfixed + * by '$' + * + * h Short + * Numbers: + * (unsigned) short int + * + * hh Short short + * Numbers: + * (unsigned) char + * + * l Long + * Numbers: + * (unsigned) long int + * String: + * as the S specifier + * Char: + * as the C specifier + * + * ll Long Long + * Numbers: + * (unsigned) long long int + * + * L Long Double + * Float + * long double + * + * # Alternative + * Float: + * Decimal-point is always present + * String: + * non-printable characters are handled as \number + * + * Spacing + * + * + Sign + * + * - Alignment + * + * . Precision + * + * * Parameter + * print: use parameter + * scan: no parameter (ignore) + * + * q Quad + * + * Z size_t + * + * w Widechar + * + * ' Thousands/quote + * Numbers: + * Integer part grouped in thousands + * Binary numbers: + * Number grouped in nibbles (4 bits) + * String: + * Quoted string + * + * j intmax_t + * t prtdiff_t + * z size_t + * + * ! Sticky + * @ Parameter (for both print and scan) + * + * I n-bit Integer + * Numbers: + * The following options exists + * I8 = 8-bit integer + * I16 = 16-bit integer + * I32 = 32-bit integer + * I64 = 64-bit integer + */ +#define QUALIFIER_POSITION '$' +#define QUALIFIER_SHORT 'h' +#define QUALIFIER_LONG 'l' +#define QUALIFIER_LONG_UPPER 'L' +#define QUALIFIER_ALTERNATIVE '#' +#define QUALIFIER_SPACE ' ' +#define QUALIFIER_PLUS '+' +#define QUALIFIER_MINUS '-' +#define QUALIFIER_DOT '.' +#define QUALIFIER_STAR '*' +#define QUALIFIER_CIRCUMFLEX '^' /* For scanlists */ +#define QUALIFIER_SIZE_T 'z' +#define QUALIFIER_PTRDIFF_T 't' +#define QUALIFIER_INTMAX_T 'j' +#define QUALIFIER_QUAD 'q' +#define QUALIFIER_SIZE_T_UPPER 'Z' +#if TRIO_MISC +# define QUALIFIER_WIDECHAR 'w' +#endif +#define QUALIFIER_FIXED_SIZE 'I' +#define QUALIFIER_QUOTE '\'' +#define QUALIFIER_STICKY '!' +#define QUALIFIER_VARSIZE '&' /* This should remain undocumented */ +#define QUALIFIER_ROUNDING_UPPER 'R' +#if TRIO_EXTENSION +# define QUALIFIER_PARAM '@' /* Experimental */ +# define QUALIFIER_COLON ':' /* For scanlists */ +# define QUALIFIER_EQUAL '=' /* For scanlists */ +#endif + + +/************************************************************************* + * + * Internal Structures + * + *************************************************************************/ + +/* Parameters */ +typedef struct { + /* An indication of which entry in the data union is used */ + int type; + /* The flags */ + trio_flags_t flags; + /* The width qualifier */ + int width; + /* The precision qualifier */ + int precision; + /* The base qualifier */ + int base; + /* Base from specifier */ + int baseSpecifier; + /* The size for the variable size qualifier */ + int varsize; + /* Offset of the first character of the specifier */ + int beginOffset; + /* Offset of the first character after the specifier */ + int endOffset; + /* Position in the argument list that this parameter refers to */ + int position; + /* The data from the argument list */ + union { + char *string; +#if TRIO_FEATURE_WIDECHAR + trio_wchar_t *wstring; +#endif + trio_pointer_t pointer; + union { + trio_intmax_t as_signed; + trio_uintmax_t as_unsigned; + } number; +#if TRIO_FEATURE_FLOAT + double doubleNumber; + double *doublePointer; + trio_long_double_t longdoubleNumber; + trio_long_double_t *longdoublePointer; +#endif + int errorNumber; + } data; +#if TRIO_FEATURE_USER_DEFINED + /* For the user-defined specifier */ + union { + char namespace[MAX_USER_NAME]; + int handler; /* if flags & FLAGS_USER_DEFINED_PARAMETER */ + } user_defined; + char user_data[MAX_USER_DATA]; +#endif +} trio_parameter_t; + +/* Container for customized functions */ +typedef struct { + union { + trio_outstream_t out; + trio_instream_t in; + } stream; + trio_pointer_t closure; +} trio_custom_t; + +/* General trio "class" */ +typedef struct _trio_class_t { + /* + * The function to write characters to a stream. + */ + void (*OutStream) TRIO_PROTO((struct _trio_class_t *, int)); + /* + * The function to read characters from a stream. + */ + void (*InStream) TRIO_PROTO((struct _trio_class_t *, int *)); + /* + * The function to undo read characters from a stream. + */ + void (*UndoStream) TRIO_PROTO((struct _trio_class_t *)); + /* + * The current location in the stream. + */ + trio_pointer_t location; + /* + * The character currently being processed. + */ + int current; + /* + * The number of characters that would have been written/read + * if there had been sufficient space. + */ + int processed; + union { + /* + * The number of characters that are actually written. Processed and + * committed will only differ for the *nprintf functions. + */ + int committed; + /* + * The number of look-ahead characters read. + */ + int cached; + } actually; + /* + * The upper limit of characters that may be written/read. + */ + int max; + /* + * The last output error that was detected. + */ + int error; +} trio_class_t; + +/* References (for user-defined callbacks) */ +typedef struct _trio_reference_t { + trio_class_t *data; + trio_parameter_t *parameter; +} trio_reference_t; + +#if TRIO_FEATURE_USER_DEFINED +/* Registered entries (for user-defined callbacks) */ +typedef struct _trio_userdef_t { + struct _trio_userdef_t *next; + trio_callback_t callback; + char *name; +} trio_userdef_t; +#endif + +/************************************************************************* + * + * Internal Variables + * + *************************************************************************/ + +static TRIO_CONST char rcsid[] = "@(#)$Id$"; + +#if TRIO_FEATURE_FLOAT +/* + * Need this to workaround a parser bug in HP C/iX compiler that fails + * to resolves macro definitions that includes type 'long double', + * e.g: va_arg(arg_ptr, long double) + */ +# if defined(TRIO_PLATFORM_MPEIX) +static TRIO_CONST trio_long_double_t ___dummy_long_double = 0; +# endif +#endif + +static TRIO_CONST char internalNullString[] = "(nil)"; + +#if defined(USE_LOCALE) +static struct lconv *internalLocaleValues = NULL; +#endif + +/* + * UNIX98 says "in a locale where the radix character is not defined, + * the radix character defaults to a period (.)" + */ +#if TRIO_FEATURE_FLOAT || TRIO_FEATURE_LOCALE || defined(USE_LOCALE) +static int internalDecimalPointLength = 1; +static char internalDecimalPoint = '.'; +static char internalDecimalPointString[MAX_LOCALE_SEPARATOR_LENGTH + 1] = "."; +#endif +#if TRIO_FEATURE_QUOTE || TRIO_FEATURE_LOCALE || TRIO_EXTENSION +static int internalThousandSeparatorLength = 1; +static char internalThousandSeparator[MAX_LOCALE_SEPARATOR_LENGTH + 1] = ","; +static char internalGrouping[MAX_LOCALE_GROUPS] = { (char)NO_GROUPING }; +#endif + +static TRIO_CONST char internalDigitsLower[] = "0123456789abcdefghijklmnopqrstuvwxyz"; +static TRIO_CONST char internalDigitsUpper[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +#if TRIO_FEATURE_SCANF +static BOOLEAN_T internalDigitsUnconverted = TRUE; +static int internalDigitArray[128]; +# if TRIO_EXTENSION +static BOOLEAN_T internalCollationUnconverted = TRUE; +static char internalCollationArray[MAX_CHARACTER_CLASS][MAX_CHARACTER_CLASS]; +# endif +#endif + +#if TRIO_FEATURE_USER_DEFINED +static TRIO_VOLATILE trio_callback_t internalEnterCriticalRegion = NULL; +static TRIO_VOLATILE trio_callback_t internalLeaveCriticalRegion = NULL; +static trio_userdef_t *internalUserDef = NULL; +#endif + + +/************************************************************************* + * + * Internal Functions + * + ************************************************************************/ + +#if defined(TRIO_EMBED_NAN) +# include "trionan.c" +#endif + +#if defined(TRIO_EMBED_STRING) +# include "triostr.c" +#endif + +/************************************************************************* + * TrioInitializeParameter + * + * Description: + * Initialize a trio_parameter_t struct. + */ +TRIO_PRIVATE void +TrioInitializeParameter +TRIO_ARGS1((parameter), + trio_parameter_t *parameter) +{ + parameter->type = FORMAT_UNKNOWN; + parameter->flags = 0; + parameter->width = 0; + parameter->precision = 0; + parameter->base = 0; + parameter->baseSpecifier = 0; + parameter->varsize = 0; + parameter->beginOffset = 0; + parameter->endOffset = 0; + parameter->position = 0; + parameter->data.pointer = 0; +#if TRIO_FEATURE_USER_DEFINED + parameter->user_defined.handler = 0; + parameter->user_data[0] = 0; +#endif +} + +/************************************************************************* + * TrioCopyParameter + * + * Description: + * Copies one trio_parameter_t struct to another. + */ +TRIO_PRIVATE void +TrioCopyParameter +TRIO_ARGS2((target, source), + trio_parameter_t *target, + TRIO_CONST trio_parameter_t *source) +{ +#if TRIO_FEATURE_USER_DEFINED + size_t i; +#endif + + target->type = source->type; + target->flags = source->flags; + target->width = source->width; + target->precision = source->precision; + target->base = source->base; + target->baseSpecifier = source->baseSpecifier; + target->varsize = source->varsize; + target->beginOffset = source->beginOffset; + target->endOffset = source->endOffset; + target->position = source->position; + target->data = source->data; + +#if TRIO_FEATURE_USER_DEFINED + target->user_defined = source->user_defined; + + for (i = 0U; i < sizeof(target->user_data); ++i) + { + if ((target->user_data[i] = source->user_data[i]) == NIL) + break; + } +#endif +} + +/************************************************************************* + * TrioIsQualifier + * + * Description: + * Remember to add all new qualifiers to this function. + * QUALIFIER_POSITION must not be added. + */ +TRIO_PRIVATE BOOLEAN_T +TrioIsQualifier +TRIO_ARGS1((character), + TRIO_CONST char character) +{ + /* QUALIFIER_POSITION is not included */ + switch (character) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case QUALIFIER_PLUS: + case QUALIFIER_MINUS: + case QUALIFIER_SPACE: + case QUALIFIER_DOT: + case QUALIFIER_STAR: + case QUALIFIER_ALTERNATIVE: + case QUALIFIER_SHORT: + case QUALIFIER_LONG: + case QUALIFIER_CIRCUMFLEX: + case QUALIFIER_LONG_UPPER: + case QUALIFIER_SIZE_T: + case QUALIFIER_PTRDIFF_T: + case QUALIFIER_INTMAX_T: + case QUALIFIER_QUAD: + case QUALIFIER_SIZE_T_UPPER: +#if defined(QUALIFIER_WIDECHAR) + case QUALIFIER_WIDECHAR: +#endif + case QUALIFIER_QUOTE: + case QUALIFIER_STICKY: + case QUALIFIER_VARSIZE: +#if defined(QUALIFIER_PARAM) + case QUALIFIER_PARAM: +#endif + case QUALIFIER_FIXED_SIZE: + case QUALIFIER_ROUNDING_UPPER: + return TRUE; + default: + return FALSE; + } +} + +/************************************************************************* + * TrioSetLocale + */ +#if defined(USE_LOCALE) +TRIO_PRIVATE void +TrioSetLocale(TRIO_NOARGS) +{ + internalLocaleValues = (struct lconv *)localeconv(); + if (internalLocaleValues) + { + if ((internalLocaleValues->decimal_point) && + (internalLocaleValues->decimal_point[0] != NIL)) + { + internalDecimalPointLength = trio_length(internalLocaleValues->decimal_point); + if (internalDecimalPointLength == 1) + { + internalDecimalPoint = internalLocaleValues->decimal_point[0]; + } + else + { + internalDecimalPoint = NIL; + trio_copy_max(internalDecimalPointString, + sizeof(internalDecimalPointString), + internalLocaleValues->decimal_point); + } + } +# if TRIO_EXTENSION + if ((internalLocaleValues->thousands_sep) && + (internalLocaleValues->thousands_sep[0] != NIL)) + { + trio_copy_max(internalThousandSeparator, + sizeof(internalThousandSeparator), + internalLocaleValues->thousands_sep); + internalThousandSeparatorLength = trio_length(internalThousandSeparator); + } +# endif +# if TRIO_EXTENSION + if ((internalLocaleValues->grouping) && + (internalLocaleValues->grouping[0] != NIL)) + { + trio_copy_max(internalGrouping, + sizeof(internalGrouping), + internalLocaleValues->grouping); + } +# endif + } +} +#endif /* defined(USE_LOCALE) */ + +#if TRIO_FEATURE_FLOAT && TRIO_FEATURE_QUOTE +TRIO_PRIVATE int +TrioCalcThousandSeparatorLength +TRIO_ARGS1((digits), + int digits) +{ + int count = 0; + int step = NO_GROUPING; + char *groupingPointer = internalGrouping; + + while (digits > 0) + { + if (*groupingPointer == CHAR_MAX) + { + /* Disable grouping */ + break; /* while */ + } + else if (*groupingPointer == 0) + { + /* Repeat last group */ + if (step == NO_GROUPING) + { + /* Error in locale */ + break; /* while */ + } + } + else + { + step = *groupingPointer++; + } + if (digits > step) + count += internalThousandSeparatorLength; + digits -= step; + } + return count; +} +#endif /* TRIO_FEATURE_FLOAT && TRIO_FEATURE_QUOTE */ + +#if TRIO_FEATURE_QUOTE +TRIO_PRIVATE BOOLEAN_T +TrioFollowedBySeparator +TRIO_ARGS1((position), + int position) +{ + int step = 0; + char *groupingPointer = internalGrouping; + + position--; + if (position == 0) + return FALSE; + while (position > 0) + { + if (*groupingPointer == CHAR_MAX) + { + /* Disable grouping */ + break; /* while */ + } + else if (*groupingPointer != 0) + { + step = *groupingPointer++; + } + if (step == 0) + break; + position -= step; + } + return (position == 0); +} +#endif /* TRIO_FEATURE_QUOTE */ + +/************************************************************************* + * TrioGetPosition + * + * Get the %n$ position. + */ +TRIO_PRIVATE int +TrioGetPosition +TRIO_ARGS2((format, offsetPointer), + TRIO_CONST char *format, + int *offsetPointer) +{ +#if TRIO_FEATURE_POSITIONAL + char *tmpformat; + int number = 0; + int offset = *offsetPointer; + + number = (int)trio_to_long(&format[offset], &tmpformat, BASE_DECIMAL); + offset = (int)(tmpformat - format); + if ((number != 0) && (QUALIFIER_POSITION == format[offset++])) + { + *offsetPointer = offset; + /* + * number is decreased by 1, because n$ starts from 1, whereas + * the array it is indexing starts from 0. + */ + return number - 1; + } +#endif + return NO_POSITION; +} + +/************************************************************************* + * TrioFindNamespace + * + * Find registered user-defined specifier. + * The prev argument is used for optimization only. + */ +#if TRIO_FEATURE_USER_DEFINED +TRIO_PRIVATE trio_userdef_t * +TrioFindNamespace +TRIO_ARGS2((name, prev), + TRIO_CONST char *name, + trio_userdef_t **prev) +{ + trio_userdef_t *def; + + if (internalEnterCriticalRegion) + (void)internalEnterCriticalRegion(NULL); + + for (def = internalUserDef; def; def = def->next) + { + /* Case-sensitive string comparison */ + if (trio_equal_case(def->name, name)) + break; + + if (prev) + *prev = def; + } + + if (internalLeaveCriticalRegion) + (void)internalLeaveCriticalRegion(NULL); + + return def; +} +#endif + +/************************************************************************* + * TrioPower + * + * Description: + * Calculate pow(base, exponent), where number and exponent are integers. + */ +#if TRIO_FEATURE_FLOAT +TRIO_PRIVATE trio_long_double_t +TrioPower +TRIO_ARGS2((number, exponent), + int number, + int exponent) +{ + trio_long_double_t result; + + if (number == 10) + { + switch (exponent) + { + /* Speed up calculation of common cases */ + case 0: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E-1); + break; + case 1: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+0); + break; + case 2: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+1); + break; + case 3: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+2); + break; + case 4: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+3); + break; + case 5: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+4); + break; + case 6: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+5); + break; + case 7: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+6); + break; + case 8: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+7); + break; + case 9: + result = (trio_long_double_t)number * TRIO_SUFFIX_LONG(1E+8); + break; + default: + result = trio_pow((trio_long_double_t)number, + (trio_long_double_t)exponent); + break; + } + } + else + { + return trio_pow((trio_long_double_t)number, + (trio_long_double_t)exponent); + } + return result; +} +#endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * TrioLogarithm + */ +#if TRIO_FEATURE_FLOAT +TRIO_PRIVATE trio_long_double_t +TrioLogarithm +TRIO_ARGS2((number, base), + trio_long_double_t number, + int base) +{ + trio_long_double_t result; + + if (number <= 0.0) + { + /* xlC crashes on log(0) */ + result = (number == 0.0) ? trio_ninf() : trio_nan(); + } + else + { + if (base == 10) + { + result = trio_log10(number); + } + else + { + result = trio_log10(number) / trio_log10((double)base); + } + } + return result; +} +#endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * TrioLogarithmBase + */ +#if TRIO_FEATURE_FLOAT +TRIO_PRIVATE double +TrioLogarithmBase +TRIO_ARGS1((base), + int base) +{ + switch (base) + { + case BASE_BINARY : return 1.0; + case BASE_OCTAL : return 3.0; + case BASE_DECIMAL: return 3.321928094887362345; + case BASE_HEX : return 4.0; + default : return TrioLogarithm((double)base, 2); + } +} +#endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * TrioParseQualifiers + * + * Description: + * Parse the qualifiers of a potential conversion specifier + */ +TRIO_PRIVATE int +TrioParseQualifiers +TRIO_ARGS4((type, format, offset, parameter), + int type, + TRIO_CONST char *format, + int offset, + trio_parameter_t *parameter) +{ + char ch; + int dots = 0; /* Count number of dots in modifier part */ + char *tmpformat; + + parameter->beginOffset = offset - 1; + parameter->flags = FLAGS_NEW; + parameter->position = TrioGetPosition(format, &offset); + + /* Default values */ + parameter->width = NO_WIDTH; + parameter->precision = NO_PRECISION; + parameter->base = NO_BASE; + parameter->varsize = NO_SIZE; + + while (TrioIsQualifier(format[offset])) + { + ch = format[offset++]; + + switch (ch) + { + case QUALIFIER_SPACE: + parameter->flags |= FLAGS_SPACE; + break; + + case QUALIFIER_PLUS: + parameter->flags |= FLAGS_SHOWSIGN; + break; + + case QUALIFIER_MINUS: + parameter->flags |= FLAGS_LEFTADJUST; + parameter->flags &= ~FLAGS_NILPADDING; + break; + + case QUALIFIER_ALTERNATIVE: + parameter->flags |= FLAGS_ALTERNATIVE; + break; + + case QUALIFIER_DOT: + if (dots == 0) /* Precision */ + { + dots++; + + /* Skip if no precision */ + if (QUALIFIER_DOT == format[offset]) + break; + + /* After the first dot we have the precision */ + parameter->flags |= FLAGS_PRECISION; + if ((QUALIFIER_STAR == format[offset]) +#if defined(QUALIFIER_PARAM) + || (QUALIFIER_PARAM == format[offset]) +#endif + ) + { + offset++; + parameter->flags |= FLAGS_PRECISION_PARAMETER; + parameter->precision = TrioGetPosition(format, &offset); + } + else + { + parameter->precision = trio_to_long(&format[offset], + &tmpformat, + BASE_DECIMAL); + offset = (int)(tmpformat - format); + } + } + else if (dots == 1) /* Base */ + { + dots++; + + /* After the second dot we have the base */ + parameter->flags |= FLAGS_BASE; + if ((QUALIFIER_STAR == format[offset]) +#if defined(QUALIFIER_PARAM) + || (QUALIFIER_PARAM == format[offset]) +#endif + ) + { + offset++; + parameter->flags |= FLAGS_BASE_PARAMETER; + parameter->base = TrioGetPosition(format, &offset); + } + else + { + parameter->base = trio_to_long(&format[offset], + &tmpformat, + BASE_DECIMAL); + if (parameter->base > MAX_BASE) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + offset = (int)(tmpformat - format); + } + } + else + { + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + } + break; /* QUALIFIER_DOT */ + +#if defined(QUALIFIER_PARAM) + case QUALIFIER_PARAM: + parameter->type = TYPE_PRINT; + /* FALLTHROUGH */ +#endif + case QUALIFIER_STAR: + /* This has different meanings for print and scan */ + if (TYPE_PRINT == type) + { + /* Read with from parameter */ + int width = TrioGetPosition(format, &offset); + parameter->flags |= (FLAGS_WIDTH | FLAGS_WIDTH_PARAMETER); + if (NO_POSITION != width) + parameter->width = width; + /* else keep parameter->width = NO_WIDTH which != NO_POSITION */ + } +#if TRIO_FEATURE_SCANF + else + { + /* Scan, but do not store result */ + parameter->flags |= FLAGS_IGNORE; + } +#endif + break; /* QUALIFIER_STAR */ + + case '0': + if (! (parameter->flags & FLAGS_LEFTADJUST)) + parameter->flags |= FLAGS_NILPADDING; + /* FALLTHROUGH */ + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + parameter->flags |= FLAGS_WIDTH; + /* + * &format[offset - 1] is used to "rewind" the read + * character from format + */ + parameter->width = trio_to_long(&format[offset - 1], + &tmpformat, + BASE_DECIMAL); + offset = (int)(tmpformat - format); + break; + + case QUALIFIER_SHORT: + if (parameter->flags & FLAGS_SHORTSHORT) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + else if (parameter->flags & FLAGS_SHORT) + parameter->flags |= FLAGS_SHORTSHORT; + else + parameter->flags |= FLAGS_SHORT; + break; + + case QUALIFIER_LONG: + if (parameter->flags & FLAGS_QUAD) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + else if (parameter->flags & FLAGS_LONG) + parameter->flags |= FLAGS_QUAD; + else + parameter->flags |= FLAGS_LONG; + break; + +#if TRIO_FEATURE_LONGDOUBLE + case QUALIFIER_LONG_UPPER: + parameter->flags |= FLAGS_LONGDOUBLE; + break; +#endif + +#if TRIO_FEATURE_SIZE_T + case QUALIFIER_SIZE_T: + parameter->flags |= FLAGS_SIZE_T; + /* Modify flags for later truncation of number */ + if (sizeof(size_t) == sizeof(trio_ulonglong_t)) + parameter->flags |= FLAGS_QUAD; + else if (sizeof(size_t) == sizeof(long)) + parameter->flags |= FLAGS_LONG; + break; +#endif + +#if TRIO_FEATURE_PTRDIFF_T + case QUALIFIER_PTRDIFF_T: + parameter->flags |= FLAGS_PTRDIFF_T; + if (sizeof(ptrdiff_t) == sizeof(trio_ulonglong_t)) + parameter->flags |= FLAGS_QUAD; + else if (sizeof(ptrdiff_t) == sizeof(long)) + parameter->flags |= FLAGS_LONG; + break; +#endif + +#if TRIO_FEATURE_INTMAX_T + case QUALIFIER_INTMAX_T: + parameter->flags |= FLAGS_INTMAX_T; + if (sizeof(trio_intmax_t) == sizeof(trio_ulonglong_t)) + parameter->flags |= FLAGS_QUAD; + else if (sizeof(trio_intmax_t) == sizeof(long)) + parameter->flags |= FLAGS_LONG; + break; +#endif + +#if TRIO_FEATURE_QUAD + case QUALIFIER_QUAD: + parameter->flags |= FLAGS_QUAD; + break; +#endif + +#if TRIO_FEATURE_FIXED_SIZE + case QUALIFIER_FIXED_SIZE: + if (parameter->flags & FLAGS_FIXED_SIZE) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + + if (parameter->flags & (FLAGS_ALL_SIZES | + FLAGS_LONGDOUBLE | + FLAGS_WIDECHAR | + FLAGS_VARSIZE_PARAMETER)) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + + if ((format[offset] == '6') && + (format[offset + 1] == '4')) + { + parameter->varsize = sizeof(trio_int64_t); + offset += 2; + } + else if ((format[offset] == '3') && + (format[offset + 1] == '2')) + { + parameter->varsize = sizeof(trio_int32_t); + offset += 2; + } + else if ((format[offset] == '1') && + (format[offset + 1] == '6')) + { + parameter->varsize = sizeof(trio_int16_t); + offset += 2; + } + else if (format[offset] == '8') + { + parameter->varsize = sizeof(trio_int8_t); + offset++; + } + else + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + + parameter->flags |= FLAGS_FIXED_SIZE; + break; +#endif /* TRIO_FEATURE_FIXED_SIZE */ + +#if defined(QUALIFIER_WIDECHAR) + case QUALIFIER_WIDECHAR: + parameter->flags |= FLAGS_WIDECHAR; + break; +#endif + +#if TRIO_FEATURE_SIZE_T_UPPER + case QUALIFIER_SIZE_T_UPPER: + break; +#endif + +#if TRIO_FEATURE_QUOTE + case QUALIFIER_QUOTE: + parameter->flags |= FLAGS_QUOTE; + break; +#endif + +#if TRIO_FEATURE_STICKY + case QUALIFIER_STICKY: + parameter->flags |= FLAGS_STICKY; + break; +#endif + +#if TRIO_FEATURE_VARSIZE + case QUALIFIER_VARSIZE: + parameter->flags |= FLAGS_VARSIZE_PARAMETER; + break; +#endif + +#if TRIO_FEATURE_ROUNDING + case QUALIFIER_ROUNDING_UPPER: + parameter->flags |= FLAGS_ROUNDING; + break; +#endif + + default: + /* Bail out completely to make the error more obvious */ + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + } + } /* while qualifier */ + + parameter->endOffset = offset; + + return 0; +} + +/************************************************************************* + * TrioParseSpecifier + * + * Description: + * Parse the specifier part of a potential conversion specifier + */ +TRIO_PRIVATE int +TrioParseSpecifier +TRIO_ARGS4((type, format, offset, parameter), + int type, + TRIO_CONST char *format, + int offset, + trio_parameter_t *parameter) +{ + parameter->baseSpecifier = NO_BASE; + + switch (format[offset++]) + { +#if defined(SPECIFIER_CHAR_UPPER) + case SPECIFIER_CHAR_UPPER: + parameter->flags |= FLAGS_WIDECHAR; + /* FALLTHROUGH */ +#endif + case SPECIFIER_CHAR: + if (parameter->flags & FLAGS_LONG) + parameter->flags |= FLAGS_WIDECHAR; + else if (parameter->flags & FLAGS_SHORT) + parameter->flags &= ~FLAGS_WIDECHAR; + parameter->type = FORMAT_CHAR; + break; + +#if defined(SPECIFIER_STRING_UPPER) + case SPECIFIER_STRING_UPPER: + parameter->flags |= FLAGS_WIDECHAR; + /* FALLTHROUGH */ +#endif + case SPECIFIER_STRING: + if (parameter->flags & FLAGS_LONG) + parameter->flags |= FLAGS_WIDECHAR; + else if (parameter->flags & FLAGS_SHORT) + parameter->flags &= ~FLAGS_WIDECHAR; + parameter->type = FORMAT_STRING; + break; + +#if defined(SPECIFIER_GROUP) + case SPECIFIER_GROUP: + if (TYPE_SCAN == type) + { + int depth = 1; + parameter->type = FORMAT_GROUP; + if (format[offset] == QUALIFIER_CIRCUMFLEX) + offset++; + if (format[offset] == SPECIFIER_UNGROUP) + offset++; + if (format[offset] == QUALIFIER_MINUS) + offset++; + /* Skip nested brackets */ + while (format[offset] != NIL) + { + if (format[offset] == SPECIFIER_GROUP) + { + depth++; + } + else if (format[offset] == SPECIFIER_UNGROUP) + { + if (--depth <= 0) + { + offset++; + break; + } + } + offset++; + } + } + break; +#endif /* defined(SPECIFIER_GROUP) */ + + case SPECIFIER_INTEGER: + parameter->type = FORMAT_INT; + break; + + case SPECIFIER_UNSIGNED: + parameter->flags |= FLAGS_UNSIGNED; + parameter->type = FORMAT_INT; + break; + + case SPECIFIER_DECIMAL: + parameter->baseSpecifier = BASE_DECIMAL; + parameter->type = FORMAT_INT; + break; + + case SPECIFIER_OCTAL: + parameter->flags |= FLAGS_UNSIGNED; + parameter->baseSpecifier = BASE_OCTAL; + parameter->type = FORMAT_INT; + break; + +#if TRIO_FEATURE_BINARY + case SPECIFIER_BINARY_UPPER: + parameter->flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_BINARY: + parameter->flags |= FLAGS_NILPADDING; + parameter->baseSpecifier = BASE_BINARY; + parameter->type = FORMAT_INT; + break; +#endif + + case SPECIFIER_HEX_UPPER: + parameter->flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_HEX: + parameter->flags |= FLAGS_UNSIGNED; + parameter->baseSpecifier = BASE_HEX; + parameter->type = FORMAT_INT; + break; + +#if defined(SPECIFIER_FLOAT_E) +# if defined(SPECIFIER_FLOAT_E_UPPER) + case SPECIFIER_FLOAT_E_UPPER: + parameter->flags |= FLAGS_UPPER; + /* FALLTHROUGH */ +# endif + case SPECIFIER_FLOAT_E: + parameter->flags |= FLAGS_FLOAT_E; + parameter->type = FORMAT_DOUBLE; + break; +#endif + +#if defined(SPECIFIER_FLOAT_G) +# if defined(SPECIFIER_FLOAT_G_UPPER) + case SPECIFIER_FLOAT_G_UPPER: + parameter->flags |= FLAGS_UPPER; + /* FALLTHROUGH */ +# endif + case SPECIFIER_FLOAT_G: + parameter->flags |= FLAGS_FLOAT_G; + parameter->type = FORMAT_DOUBLE; + break; +#endif + +#if defined(SPECIFIER_FLOAT_F) +# if defined(SPECIFIER_FLOAT_F_UPPER) + case SPECIFIER_FLOAT_F_UPPER: + parameter->flags |= FLAGS_UPPER; + /* FALLTHROUGH */ +# endif + case SPECIFIER_FLOAT_F: + parameter->type = FORMAT_DOUBLE; + break; +#endif + +#if defined(TRIO_COMPILER_VISUALC) +# pragma warning( push ) +# pragma warning( disable : 4127 ) /* Conditional expression is constant */ +#endif + case SPECIFIER_POINTER: + if (sizeof(trio_pointer_t) == sizeof(trio_ulonglong_t)) + parameter->flags |= FLAGS_QUAD; + else if (sizeof(trio_pointer_t) == sizeof(long)) + parameter->flags |= FLAGS_LONG; + parameter->type = FORMAT_POINTER; + break; +#if defined(TRIO_COMPILER_VISUALC) +# pragma warning( pop ) +#endif + + case SPECIFIER_COUNT: + parameter->type = FORMAT_COUNT; + break; + +#if TRIO_FEATURE_HEXFLOAT + case SPECIFIER_HEXFLOAT_UPPER: + parameter->flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_HEXFLOAT: + parameter->baseSpecifier = BASE_HEX; + parameter->type = FORMAT_DOUBLE; + break; +#endif + +#if TRIO_FEATURE_ERRNO + case SPECIFIER_ERRNO: + parameter->type = FORMAT_ERRNO; + break; +#endif + +#if TRIO_FEATURE_USER_DEFINED + case SPECIFIER_USER_DEFINED_BEGIN: + { + unsigned int max; + int without_namespace = TRUE; + char* tmpformat = (char *)&format[offset]; + int ch; + + parameter->type = FORMAT_USER_DEFINED; + parameter->user_defined.namespace[0] = NIL; + + while ((ch = format[offset]) != NIL) + { + offset++; + if ((ch == SPECIFIER_USER_DEFINED_END) || (ch == SPECIFIER_USER_DEFINED_EXTRA)) + { + if (without_namespace) + /* No namespace, handler will be passed as an argument */ + parameter->flags |= FLAGS_USER_DEFINED_PARAMETER; + + /* Copy the user data */ + max = (unsigned int)(&format[offset] - tmpformat); + if (max > MAX_USER_DATA) + max = MAX_USER_DATA; + trio_copy_max(parameter->user_data, max, tmpformat); + + /* Skip extra data (which is only there to keep the compiler happy) */ + while ((ch != NIL) && (ch != SPECIFIER_USER_DEFINED_END)) + ch = format[offset++]; + + break; /* while */ + } + + if (ch == SPECIFIER_USER_DEFINED_SEPARATOR) + { + without_namespace = FALSE; + /* Copy the namespace for later looking-up */ + max = (int)(&format[offset] - tmpformat); + if (max > MAX_USER_NAME) + max = MAX_USER_NAME; + trio_copy_max(parameter->user_defined.namespace, max, tmpformat); + tmpformat = (char *)&format[offset]; + } + } + + if (ch != SPECIFIER_USER_DEFINED_END) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + } + break; +#endif /* TRIO_FEATURE_USER_DEFINED */ + + default: + /* Bail out completely to make the error more obvious */ + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + } + + parameter->endOffset = offset; + + return 0; +} + +/************************************************************************* + * TrioParse + * + * Description: + * Parse the format string + */ +TRIO_PRIVATE int +TrioParse +TRIO_ARGS6((type, format, parameters, arglist, argfunc, argarray), + int type, + TRIO_CONST char *format, + trio_parameter_t *parameters, + va_list arglist, + trio_argfunc_t argfunc, + trio_pointer_t *argarray) +{ + /* Count the number of times a parameter is referenced */ + unsigned short usedEntries[MAX_PARAMETERS]; + /* Parameter counters */ + int parameterPosition; + int maxParam = -1; + /* Utility variables */ + int offset; /* Offset into formatting string */ + BOOLEAN_T positional; /* Does the specifier have a positional? */ +#if TRIO_FEATURE_STICKY + BOOLEAN_T gotSticky = FALSE; /* Are there any sticky modifiers at all? */ +#endif + /* + * indices specifies the order in which the parameters must be + * read from the va_args (this is necessary to handle positionals) + */ + int indices[MAX_PARAMETERS]; + int pos = 0; + /* Various variables */ +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + int charlen; +#endif + int save_errno; + int i = -1; + int num; + trio_parameter_t workParameter; + int status; + + /* Both must be set or none must be set */ + assert(((argfunc == NULL) && (argarray == NULL)) || + ((argfunc != NULL) && (argarray != NULL))); + + /* + * The 'parameters' array is not initialized, but we need to + * know which entries we have used. + */ + memset(usedEntries, 0, sizeof(usedEntries)); + + save_errno = errno; + offset = 0; + parameterPosition = 0; +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + charlen = mblen(NULL, 0); +#endif + + while (format[offset]) + { + TrioInitializeParameter(&workParameter); + +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + if (! isascii(format[offset])) + { + /* + * Multibyte characters cannot be legal specifiers or + * modifiers, so we skip over them. + */ + charlen = mblen(&format[offset], MB_LEN_MAX); + offset += (charlen > 0) ? charlen : 1; + continue; /* while */ + } +#endif /* TRIO_COMPILER_SUPPORTS_MULTIBYTE */ + + switch(format[offset++]) { + + case CHAR_IDENTIFIER: + { + if (CHAR_IDENTIFIER == format[offset]) + { + /* skip double "%" */ + offset++; + continue; /* while */ + } + + status = TrioParseQualifiers(type, format, offset, &workParameter); + if (status < 0) + return status; /* Return qualifier syntax error */ + + status = TrioParseSpecifier(type, format, workParameter.endOffset, &workParameter); + if (status < 0) + return status; /* Return specifier syntax error */ + } + break; + +#if TRIO_EXTENSION + case CHAR_ALT_IDENTIFIER: + { + status = TrioParseQualifiers(type, format, offset, &workParameter); + if (status < 0) + continue; /* False alert, not a user defined specifier */ + + status = TrioParseSpecifier(type, format, workParameter.endOffset, &workParameter); + if ((status < 0) || (FORMAT_USER_DEFINED != workParameter.type)) + continue; /* False alert, not a user defined specifier */ + } + break; +#endif + + default: + continue; /* while */ + } + + /* now handle the parsed conversion specification */ + positional = (NO_POSITION != workParameter.position); + + /* + * Parameters only need the type and value. The value is + * read later. + */ + if (workParameter.flags & FLAGS_WIDTH_PARAMETER) + { + if (workParameter.width == NO_WIDTH) + { + workParameter.width = parameterPosition++; + } + else + { + if (! positional) + workParameter.position = workParameter.width + 1; + } + + usedEntries[workParameter.width] += 1; + if (workParameter.width > maxParam) + maxParam = workParameter.width; + parameters[pos].type = FORMAT_PARAMETER; + parameters[pos].flags = 0; + indices[workParameter.width] = pos; + workParameter.width = pos++; + } + if (workParameter.flags & FLAGS_PRECISION_PARAMETER) + { + if (workParameter.precision == NO_PRECISION) + { + workParameter.precision = parameterPosition++; + } + else + { + if (! positional) + workParameter.position = workParameter.precision + 1; + } + + usedEntries[workParameter.precision] += 1; + if (workParameter.precision > maxParam) + maxParam = workParameter.precision; + parameters[pos].type = FORMAT_PARAMETER; + parameters[pos].flags = 0; + indices[workParameter.precision] = pos; + workParameter.precision = pos++; + } + if (workParameter.flags & FLAGS_BASE_PARAMETER) + { + if (workParameter.base == NO_BASE) + { + workParameter.base = parameterPosition++; + } + else + { + if (! positional) + workParameter.position = workParameter.base + 1; + } + + usedEntries[workParameter.base] += 1; + if (workParameter.base > maxParam) + maxParam = workParameter.base; + parameters[pos].type = FORMAT_PARAMETER; + parameters[pos].flags = 0; + indices[workParameter.base] = pos; + workParameter.base = pos++; + } +#if TRIO_FEATURE_VARSIZE + if (workParameter.flags & FLAGS_VARSIZE_PARAMETER) + { + workParameter.varsize = parameterPosition++; + + usedEntries[workParameter.varsize] += 1; + if (workParameter.varsize > maxParam) + maxParam = workParameter.varsize; + parameters[pos].type = FORMAT_PARAMETER; + parameters[pos].flags = 0; + indices[workParameter.varsize] = pos; + workParameter.varsize = pos++; + } +#endif +#if TRIO_FEATURE_USER_DEFINED + if (workParameter.flags & FLAGS_USER_DEFINED_PARAMETER) + { + workParameter.user_defined.handler = parameterPosition++; + + usedEntries[workParameter.user_defined.handler] += 1; + if (workParameter.user_defined.handler > maxParam) + maxParam = workParameter.user_defined.handler; + parameters[pos].type = FORMAT_PARAMETER; + parameters[pos].flags = FLAGS_USER_DEFINED; + indices[workParameter.user_defined.handler] = pos; + workParameter.user_defined.handler = pos++; + } +#endif + + if (NO_POSITION == workParameter.position) + { + workParameter.position = parameterPosition++; + } + + if (workParameter.position > maxParam) + maxParam = workParameter.position; + + if (workParameter.position >= MAX_PARAMETERS) + { + /* Bail out completely to make the error more obvious */ + return TRIO_ERROR_RETURN(TRIO_ETOOMANY, offset); + } + + indices[workParameter.position] = pos; + + /* Count the number of times this entry has been used */ + usedEntries[workParameter.position] += 1; + + /* Find last sticky parameters */ +#if TRIO_FEATURE_STICKY + if (workParameter.flags & FLAGS_STICKY) + { + gotSticky = TRUE; + } + else if (gotSticky) + { + for (i = pos - 1; i >= 0; i--) + { + if (parameters[i].type == FORMAT_PARAMETER) + continue; + if ((parameters[i].flags & FLAGS_STICKY) && + (parameters[i].type == workParameter.type)) + { + /* Do not overwrite current qualifiers */ + workParameter.flags |= (parameters[i].flags & (unsigned long)~FLAGS_STICKY); + if (workParameter.width == NO_WIDTH) + workParameter.width = parameters[i].width; + if (workParameter.precision == NO_PRECISION) + workParameter.precision = parameters[i].precision; + if (workParameter.base == NO_BASE) + workParameter.base = parameters[i].base; + break; + } + } + } +#endif + + if (workParameter.base == NO_BASE) + workParameter.base = BASE_DECIMAL; + + offset = workParameter.endOffset; + + TrioCopyParameter(¶meters[pos++], &workParameter); + } /* while format characters left */ + + parameters[pos].type = FORMAT_SENTINEL; /* end parameter array with sentinel */ + parameters[pos].beginOffset = offset; + + for (num = 0; num <= maxParam; num++) + { + if (usedEntries[num] != 1) + { + if (usedEntries[num] == 0) /* gap detected */ + return TRIO_ERROR_RETURN(TRIO_EGAP, num); + else /* double references detected */ + return TRIO_ERROR_RETURN(TRIO_EDBLREF, num); + } + + i = indices[num]; + + /* + * FORMAT_PARAMETERS are only present if they must be read, + * so it makes no sense to check the ignore flag (besides, + * the flags variable is not set for that particular type) + */ + if ((parameters[i].type != FORMAT_PARAMETER) && + (parameters[i].flags & FLAGS_IGNORE)) + continue; /* for all arguments */ + + /* + * The stack arguments are read according to ANSI C89 + * default argument promotions: + * + * char = int + * short = int + * unsigned char = unsigned int + * unsigned short = unsigned int + * float = double + * + * In addition to the ANSI C89 these types are read (the + * default argument promotions of C99 has not been + * considered yet) + * + * long long + * long double + * size_t + * ptrdiff_t + * intmax_t + */ + switch (parameters[i].type) + { + case FORMAT_GROUP: + case FORMAT_STRING: +#if TRIO_FEATURE_WIDECHAR + if (parameters[i].flags & FLAGS_WIDECHAR) + { + parameters[i].data.wstring = (argfunc == NULL) + ? va_arg(arglist, trio_wchar_t *) + : (trio_wchar_t *)(argfunc(argarray, num, TRIO_TYPE_PWCHAR)); + } + else +#endif + { + parameters[i].data.string = (argfunc == NULL) + ? va_arg(arglist, char *) + : (char *)(argfunc(argarray, num, TRIO_TYPE_PCHAR)); + } + break; + +#if TRIO_FEATURE_USER_DEFINED + case FORMAT_USER_DEFINED: +#endif + case FORMAT_POINTER: + case FORMAT_COUNT: + case FORMAT_UNKNOWN: + parameters[i].data.pointer = (argfunc == NULL) + ? va_arg(arglist, trio_pointer_t ) + : argfunc(argarray, num, TRIO_TYPE_POINTER); + break; + + case FORMAT_CHAR: + case FORMAT_INT: +#if TRIO_FEATURE_SCANF + if (TYPE_SCAN == type) + { + if (argfunc == NULL) + parameters[i].data.pointer = + (trio_pointer_t)va_arg(arglist, trio_pointer_t); + else + { + if (parameters[i].type == FORMAT_CHAR) + parameters[i].data.pointer = + (trio_pointer_t)((char *)argfunc(argarray, num, TRIO_TYPE_CHAR)); + else if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.pointer = + (trio_pointer_t)((short *)argfunc(argarray, num, TRIO_TYPE_SHORT)); + else + parameters[i].data.pointer = + (trio_pointer_t)((int *)argfunc(argarray, num, TRIO_TYPE_INT)); + } + } + else +#endif /* TRIO_FEATURE_SCANF */ + { +#if TRIO_FEATURE_VARSIZE || TRIO_FEATURE_FIXED_SIZE + if (parameters[i].flags + & (FLAGS_VARSIZE_PARAMETER | FLAGS_FIXED_SIZE)) + { + int varsize; + if (parameters[i].flags & FLAGS_VARSIZE_PARAMETER) + { + /* + * Variable sizes are mapped onto the fixed sizes, in + * accordance with integer promotion. + * + * Please note that this may not be portable, as we + * only guess the size, not the layout of the numbers. + * For example, if int is little-endian, and long is + * big-endian, then this will fail. + */ + varsize = (int)parameters[parameters[i].varsize].data.number.as_unsigned; + } + else + { + /* Used for the I modifiers */ + varsize = parameters[i].varsize; + } + parameters[i].flags &= ~FLAGS_ALL_VARSIZES; + + if (varsize <= (int)sizeof(int)) + ; + else if (varsize <= (int)sizeof(long)) + parameters[i].flags |= FLAGS_LONG; +#if TRIO_FEATURE_INTMAX_T + else if (varsize <= (int)sizeof(trio_longlong_t)) + parameters[i].flags |= FLAGS_QUAD; + else + parameters[i].flags |= FLAGS_INTMAX_T; +#else + else + parameters[i].flags |= FLAGS_QUAD; +#endif + } +#endif /* TRIO_FEATURE_VARSIZE */ +#if TRIO_FEATURE_SIZE_T || TRIO_FEATURE_SIZE_T_UPPER + if (parameters[i].flags & FLAGS_SIZE_T) + parameters[i].data.number.as_unsigned = (argfunc == NULL) + ? (trio_uintmax_t)va_arg(arglist, size_t) + : (trio_uintmax_t)(*((size_t *)argfunc(argarray, num, TRIO_TYPE_SIZE))); + else +#endif +#if TRIO_FEATURE_PTRDIFF_T + if (parameters[i].flags & FLAGS_PTRDIFF_T) + parameters[i].data.number.as_unsigned = (argfunc == NULL) + ? (trio_uintmax_t)va_arg(arglist, ptrdiff_t) + : (trio_uintmax_t)(*((ptrdiff_t *)argfunc(argarray, num, TRIO_TYPE_PTRDIFF))); + else +#endif +#if TRIO_FEATURE_INTMAX_T + if (parameters[i].flags & FLAGS_INTMAX_T) + parameters[i].data.number.as_unsigned = (argfunc == NULL) + ? (trio_uintmax_t)va_arg(arglist, trio_intmax_t) + : (trio_uintmax_t)(*((trio_intmax_t *)argfunc(argarray, num, TRIO_TYPE_UINTMAX))); + else +#endif + if (parameters[i].flags & FLAGS_QUAD) + parameters[i].data.number.as_unsigned = (argfunc == NULL) + ? (trio_uintmax_t)va_arg(arglist, trio_ulonglong_t) + : (trio_uintmax_t)(*((trio_ulonglong_t *)argfunc(argarray, num, TRIO_TYPE_ULONGLONG))); + else if (parameters[i].flags & FLAGS_LONG) + parameters[i].data.number.as_unsigned = (argfunc == NULL) + ? (trio_uintmax_t)va_arg(arglist, long) + : (trio_uintmax_t)(*((long *)argfunc(argarray, num, TRIO_TYPE_LONG))); + else + { + if (argfunc == NULL) + parameters[i].data.number.as_unsigned = (trio_uintmax_t)va_arg(arglist, int); + else + { + if (parameters[i].type == FORMAT_CHAR) + parameters[i].data.number.as_unsigned = + (trio_uintmax_t)(*((char *)argfunc(argarray, num, TRIO_TYPE_CHAR))); + else if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.number.as_unsigned = + (trio_uintmax_t)(*((short *)argfunc(argarray, num, TRIO_TYPE_SHORT))); + else + parameters[i].data.number.as_unsigned = + (trio_uintmax_t)(*((int *)argfunc(argarray, num, TRIO_TYPE_INT))); + } + } + } + break; + + case FORMAT_PARAMETER: + /* + * The parameter for the user-defined specifier is a pointer, + * whereas the rest (width, precision, base) uses an integer. + */ + if (parameters[i].flags & FLAGS_USER_DEFINED) + parameters[i].data.pointer = (argfunc == NULL) + ? va_arg(arglist, trio_pointer_t ) + : argfunc(argarray, num, TRIO_TYPE_POINTER); + else + parameters[i].data.number.as_unsigned = (argfunc == NULL) + ? (trio_uintmax_t)va_arg(arglist, int) + : (trio_uintmax_t)(*((int *)argfunc(argarray, num, TRIO_TYPE_INT))); + break; + +#if TRIO_FEATURE_FLOAT + case FORMAT_DOUBLE: +# if TRIO_FEATURE_SCANF + if (TYPE_SCAN == type) + { + if (parameters[i].flags & FLAGS_LONGDOUBLE) + parameters[i].data.longdoublePointer = (argfunc == NULL) + ? va_arg(arglist, trio_long_double_t *) + : (trio_long_double_t *)argfunc(argarray, num, TRIO_TYPE_LONGDOUBLE); + else + { + if (parameters[i].flags & FLAGS_LONG) + parameters[i].data.doublePointer = (argfunc == NULL) + ? va_arg(arglist, double *) + : (double *)argfunc(argarray, num, TRIO_TYPE_DOUBLE); + else + parameters[i].data.doublePointer = (argfunc == NULL) + ? (double *)va_arg(arglist, float *) + : (double *)argfunc(argarray, num, TRIO_TYPE_DOUBLE); + } + } + else +# endif /* TRIO_FEATURE_SCANF */ + { + if (parameters[i].flags & FLAGS_LONGDOUBLE) + parameters[i].data.longdoubleNumber = (argfunc == NULL) + ? va_arg(arglist, trio_long_double_t) + : (trio_long_double_t)(*((trio_long_double_t *)argfunc(argarray, num, TRIO_TYPE_LONGDOUBLE))); + else + { + if (argfunc == NULL) + parameters[i].data.longdoubleNumber = + (trio_long_double_t)va_arg(arglist, double); + else + { + if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.longdoubleNumber = + (trio_long_double_t)(*((float *)argfunc(argarray, num, TRIO_TYPE_FLOAT))); + else + parameters[i].data.longdoubleNumber = + (trio_long_double_t)(*((double *)argfunc(argarray, num, TRIO_TYPE_DOUBLE))); + } + } + } + break; +#endif /* TRIO_FEATURE_FLOAT */ + +#if TRIO_FEATURE_ERRNO + case FORMAT_ERRNO: + parameters[i].data.errorNumber = save_errno; + break; +#endif + + default: + break; + } + } /* for all specifiers */ + return num; +} + + +/************************************************************************* + * + * FORMATTING + * + ************************************************************************/ + + +/************************************************************************* + * TrioWriteNumber + * + * Description: + * Output a number. + * The complexity of this function is a result of the complexity + * of the dependencies of the flags. + */ +TRIO_PRIVATE void +TrioWriteNumber +TRIO_ARGS6((self, number, flags, width, precision, base), + trio_class_t *self, + trio_uintmax_t number, + trio_flags_t flags, + int width, + int precision, + int base) +{ + BOOLEAN_T isNegative; + BOOLEAN_T isNumberZero; + BOOLEAN_T isPrecisionZero; + BOOLEAN_T ignoreNumber; + char buffer[MAX_CHARS_IN(trio_uintmax_t) * (1 + MAX_LOCALE_SEPARATOR_LENGTH) + 1]; + char *bufferend; + char *pointer; + TRIO_CONST char *digits; + int i; +#if TRIO_FEATURE_QUOTE + int length; + char *p; +#endif + int count; + int digitOffset; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + assert(((base >= MIN_BASE) && (base <= MAX_BASE)) || (base == NO_BASE)); + + digits = (flags & FLAGS_UPPER) ? internalDigitsUpper : internalDigitsLower; + if (base == NO_BASE) + base = BASE_DECIMAL; + + isNumberZero = (number == 0); + isPrecisionZero = (precision == 0); + ignoreNumber = (isNumberZero + && isPrecisionZero + && !((flags & FLAGS_ALTERNATIVE) && (base == BASE_OCTAL))); + + if (flags & FLAGS_UNSIGNED) + { + isNegative = FALSE; + flags &= ~FLAGS_SHOWSIGN; + } + else + { + isNegative = ((trio_intmax_t)number < 0); + if (isNegative) + number = -((trio_intmax_t)number); + } + + if (flags & FLAGS_QUAD) + number &= (trio_ulonglong_t)-1; + else if (flags & FLAGS_LONG) + number &= (unsigned long)-1; + else + number &= (unsigned int)-1; + + /* Build number */ + pointer = bufferend = &buffer[sizeof(buffer) - 1]; + *pointer-- = NIL; + for (i = 1; i < (int)sizeof(buffer); i++) + { + digitOffset = number % base; + *pointer-- = digits[digitOffset]; + number /= base; + if (number == 0) + break; + +#if TRIO_FEATURE_QUOTE + if ((flags & FLAGS_QUOTE) && TrioFollowedBySeparator(i + 1)) + { + /* + * We are building the number from the least significant + * to the most significant digit, so we have to copy the + * thousand separator backwards + */ + length = internalThousandSeparatorLength; + if (((int)(pointer - buffer) - length) > 0) + { + p = &internalThousandSeparator[length - 1]; + while (length-- > 0) + *pointer-- = *p--; + } + } +#endif + } + + if (! ignoreNumber) + { + /* Adjust width */ + width -= (bufferend - pointer) - 1; + } + + /* Adjust precision */ + if (NO_PRECISION != precision) + { + precision -= (bufferend - pointer) - 1; + if (precision < 0) + precision = 0; + flags |= FLAGS_NILPADDING; + } + + /* Calculate padding */ + count = (! ((flags & FLAGS_LEFTADJUST) || (precision == NO_PRECISION))) + ? precision + : 0; + + /* Adjust width further */ + if (isNegative || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) + width--; + if ((flags & FLAGS_ALTERNATIVE) && !isNumberZero) + { + switch (base) + { + case BASE_BINARY: + case BASE_HEX: + width -= 2; + break; + case BASE_OCTAL: + if (!(flags & FLAGS_NILPADDING) || (count == 0)) + width--; + break; + default: + break; + } + } + + /* Output prefixes spaces if needed */ + if (! ((flags & FLAGS_LEFTADJUST) || + ((flags & FLAGS_NILPADDING) && (precision == NO_PRECISION)))) + { + while (width-- > count) + self->OutStream(self, CHAR_ADJUST); + } + + /* width has been adjusted for signs and alternatives */ + if (isNegative) + self->OutStream(self, '-'); + else if (flags & FLAGS_SHOWSIGN) + self->OutStream(self, '+'); + else if (flags & FLAGS_SPACE) + self->OutStream(self, ' '); + + /* Prefix is not written when the value is zero */ + if ((flags & FLAGS_ALTERNATIVE) && !isNumberZero) + { + switch (base) + { + case BASE_BINARY: + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'B' : 'b'); + break; + + case BASE_OCTAL: + if (!(flags & FLAGS_NILPADDING) || (count == 0)) + self->OutStream(self, '0'); + break; + + case BASE_HEX: + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x'); + break; + + default: + break; + } /* switch base */ + } + + /* Output prefixed zero padding if needed */ + if (flags & FLAGS_NILPADDING) + { + if (precision == NO_PRECISION) + precision = width; + while (precision-- > 0) + { + self->OutStream(self, '0'); + width--; + } + } + + if (! ignoreNumber) + { + /* Output the number itself */ + while (*(++pointer)) + { + self->OutStream(self, *pointer); + } + } + + /* Output trailing spaces if needed */ + if (flags & FLAGS_LEFTADJUST) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } +} + +/************************************************************************* + * TrioWriteStringCharacter + * + * Description: + * Output a single character of a string + */ +TRIO_PRIVATE void +TrioWriteStringCharacter +TRIO_ARGS3((self, ch, flags), + trio_class_t *self, + int ch, + trio_flags_t flags) +{ + if (flags & FLAGS_ALTERNATIVE) + { + if (! isprint(ch)) + { + /* + * Non-printable characters are converted to C escapes or + * \number, if no C escape exists. + */ + self->OutStream(self, CHAR_BACKSLASH); + switch (ch) + { + case '\007': self->OutStream(self, 'a'); break; + case '\b': self->OutStream(self, 'b'); break; + case '\f': self->OutStream(self, 'f'); break; + case '\n': self->OutStream(self, 'n'); break; + case '\r': self->OutStream(self, 'r'); break; + case '\t': self->OutStream(self, 't'); break; + case '\v': self->OutStream(self, 'v'); break; + case '\\': self->OutStream(self, '\\'); break; + default: + self->OutStream(self, 'x'); + TrioWriteNumber(self, (trio_uintmax_t)ch, + FLAGS_UNSIGNED | FLAGS_NILPADDING, + 2, 2, BASE_HEX); + break; + } + } + else if (ch == CHAR_BACKSLASH) + { + self->OutStream(self, CHAR_BACKSLASH); + self->OutStream(self, CHAR_BACKSLASH); + } + else + { + self->OutStream(self, ch); + } + } + else + { + self->OutStream(self, ch); + } +} + +/************************************************************************* + * TrioWriteString + * + * Description: + * Output a string + */ +TRIO_PRIVATE void +TrioWriteString +TRIO_ARGS5((self, string, flags, width, precision), + trio_class_t *self, + TRIO_CONST char *string, + trio_flags_t flags, + int width, + int precision) +{ + int length; + int ch; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + + if (string == NULL) + { + string = internalNullString; + length = sizeof(internalNullString) - 1; +#if TRIO_FEATURE_QUOTE + /* Disable quoting for the null pointer */ + flags &= (~FLAGS_QUOTE); +#endif + width = 0; + } + else + { + if (precision == 0) + { + length = trio_length(string); + } + else + { + length = trio_length_max(string, precision); + } + } + if ((NO_PRECISION != precision) && + (precision < length)) + { + length = precision; + } + width -= length; + +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); +#endif + + if (! (flags & FLAGS_LEFTADJUST)) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } + + while (length-- > 0) + { + /* The ctype parameters must be an unsigned char (or EOF) */ + ch = (int)((unsigned char)(*string++)); + TrioWriteStringCharacter(self, ch, flags); + } + + if (flags & FLAGS_LEFTADJUST) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); +#endif +} + +/************************************************************************* + * TrioWriteWideStringCharacter + * + * Description: + * Output a wide string as a multi-byte sequence + */ +#if TRIO_FEATURE_WIDECHAR +TRIO_PRIVATE int +TrioWriteWideStringCharacter +TRIO_ARGS4((self, wch, flags, width), + trio_class_t *self, + trio_wchar_t wch, + trio_flags_t flags, + int width) +{ + int size; + int i; + int ch; + char *string; + char buffer[MB_LEN_MAX + 1]; + + if (width == NO_WIDTH) + width = sizeof(buffer); + + size = wctomb(buffer, wch); + if ((size <= 0) || (size > width) || (buffer[0] == NIL)) + return 0; + + string = buffer; + i = size; + while ((width >= i) && (width-- > 0) && (i-- > 0)) + { + /* The ctype parameters must be an unsigned char (or EOF) */ + ch = (int)((unsigned char)(*string++)); + TrioWriteStringCharacter(self, ch, flags); + } + return size; +} +#endif /* TRIO_FEATURE_WIDECHAR */ + +/************************************************************************* + * TrioWriteWideString + * + * Description: + * Output a wide character string as a multi-byte string + */ +#if TRIO_FEATURE_WIDECHAR +TRIO_PRIVATE void +TrioWriteWideString +TRIO_ARGS5((self, wstring, flags, width, precision), + trio_class_t *self, + TRIO_CONST trio_wchar_t *wstring, + trio_flags_t flags, + int width, + int precision) +{ + int length; + int size; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + /* Required by TrioWriteWideStringCharacter */ + (void)mblen(NULL, 0); +#endif + + if (wstring == NULL) + { + TrioWriteString(self, NULL, flags, width, precision); + return; + } + + if (NO_PRECISION == precision) + { + length = INT_MAX; + } + else + { + length = precision; + width -= length; + } + +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); +#endif + + if (! (flags & FLAGS_LEFTADJUST)) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } + + while (length > 0) + { + size = TrioWriteWideStringCharacter(self, *wstring++, flags, length); + if (size == 0) + break; /* while */ + length -= size; + } + + if (flags & FLAGS_LEFTADJUST) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); +#endif +} +#endif /* TRIO_FEATURE_WIDECHAR */ + +/************************************************************************* + * TrioWriteDouble + * + * http://wwwold.dkuug.dk/JTC1/SC22/WG14/www/docs/dr_211.htm + * + * "5.2.4.2.2 paragraph #4 + * + * The accuracy [...] is implementation defined, as is the accuracy + * of the conversion between floating-point internal representations + * and string representations performed by the libray routine in + * " + */ +/* FIXME: handle all instances of constant long-double number (L) + * and *l() math functions. + */ +#if TRIO_FEATURE_FLOAT +TRIO_PRIVATE void +TrioWriteDouble +TRIO_ARGS6((self, number, flags, width, precision, base), + trio_class_t *self, + trio_long_double_t number, + trio_flags_t flags, + int width, + int precision, + int base) +{ + trio_long_double_t integerNumber; + trio_long_double_t fractionNumber; + trio_long_double_t workNumber; + int integerDigits; + int fractionDigits; + int exponentDigits; + int workDigits; + int baseDigits; + int integerThreshold; + int fractionThreshold; + int expectedWidth; + int exponent = 0; + unsigned int uExponent = 0; + int exponentBase; + trio_long_double_t dblBase; + trio_long_double_t dblFractionBase; + trio_long_double_t integerAdjust; + trio_long_double_t fractionAdjust; + trio_long_double_t workFractionNumber; + trio_long_double_t workFractionAdjust; + int fractionDigitsInspect; + BOOLEAN_T isNegative; + BOOLEAN_T isExponentNegative = FALSE; + BOOLEAN_T requireTwoDigitExponent; + BOOLEAN_T isHex; + TRIO_CONST char *digits; +# if TRIO_FEATURE_QUOTE + char *groupingPointer; +# endif + int i; + int offset; + BOOLEAN_T hasOnlyZeroes; + int leadingFractionZeroes = -1; + register int trailingZeroes; + BOOLEAN_T keepTrailingZeroes; + BOOLEAN_T keepDecimalPoint; + trio_long_double_t epsilon; + trio_long_double_t epsilonCorrection; + BOOLEAN_T adjustNumber = FALSE; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + assert(((base >= MIN_BASE) && (base <= MAX_BASE)) || (base == NO_BASE)); + + /* Determine sign and look for special quantities */ + switch (trio_fpclassify_and_signbit(number, &isNegative)) + { + case TRIO_FP_NAN: + TrioWriteString(self, + (flags & FLAGS_UPPER) + ? NAN_UPPER + : NAN_LOWER, + flags, width, precision); + return; + + case TRIO_FP_INFINITE: + if (isNegative) + { + /* Negative infinity */ + TrioWriteString(self, + (flags & FLAGS_UPPER) + ? "-" INFINITE_UPPER + : "-" INFINITE_LOWER, + flags, width, precision); + return; + } + else + { + /* Positive infinity */ + TrioWriteString(self, + (flags & FLAGS_UPPER) + ? INFINITE_UPPER + : INFINITE_LOWER, + flags, width, precision); + return; + } + + default: + /* Finitude */ + break; + } + + /* Normal numbers */ + if (flags & FLAGS_LONGDOUBLE) + { + baseDigits = (base == 10) + ? LDBL_DIG + : (int)trio_floor(LDBL_MANT_DIG / TrioLogarithmBase(base)); + epsilon = LDBL_EPSILON; + } + else if (flags & FLAGS_SHORT) + { + baseDigits = (base == BASE_DECIMAL) + ? FLT_DIG + : (int)trio_floor(FLT_MANT_DIG / TrioLogarithmBase(base)); + epsilon = FLT_EPSILON; + } + else + { + baseDigits = (base == BASE_DECIMAL) + ? DBL_DIG + : (int)trio_floor(DBL_MANT_DIG / TrioLogarithmBase(base)); + epsilon = DBL_EPSILON; + } + + digits = (flags & FLAGS_UPPER) ? internalDigitsUpper : internalDigitsLower; + isHex = (base == BASE_HEX); + if (base == NO_BASE) + base = BASE_DECIMAL; + dblBase = (trio_long_double_t)base; + /* + * Some log10() functions can "err by almost 3 ulps" according to + * http://www.cs.berkeley.edu/~wkahan/LOG10HAF.TXT + */ + epsilonCorrection = 3 * epsilon; + keepTrailingZeroes = !( (flags & FLAGS_ROUNDING) || + ( (flags & FLAGS_FLOAT_G) && + !(flags & FLAGS_ALTERNATIVE) ) ); + +# if TRIO_FEATURE_ROUNDING + if (flags & FLAGS_ROUNDING) + { + precision = baseDigits; + } +# endif + + if (precision == NO_PRECISION) + { + if (isHex) + { + keepTrailingZeroes = FALSE; + precision = FLT_MANT_DIG; + } + else + { + precision = FLT_DIG; + } + } + + if (isNegative) + { + number = -number; + } + + if (isHex) + { + flags |= FLAGS_FLOAT_E; + } + + reprocess: + + if (flags & FLAGS_FLOAT_G) + { + if (precision == 0) + precision = 1; + + if ( (number < TRIO_SUFFIX_LONG(1.0E-4)) || + (number >= TrioPower(base, (trio_long_double_t)precision)) ) + { + /* Use scientific notation */ + flags |= FLAGS_FLOAT_E; + } + else if (number < 1.0) + { + /* + * Use normal notation. If the integer part of the number is + * zero, then adjust the precision to include leading fractional + * zeros. + */ + workNumber = TrioLogarithm(number, base); + workNumber = TRIO_FABS(workNumber); + if (workNumber - trio_floor(workNumber) < epsilon) + workNumber--; + leadingFractionZeroes = (int)trio_floor(workNumber); + } + } + + if (flags & FLAGS_FLOAT_E) + { + /* Scale the number */ + workNumber = TrioLogarithm(number, base); + if (trio_isinf(workNumber) == -1) + { + exponent = 0; + /* Undo setting */ + if (flags & FLAGS_FLOAT_G) + flags &= ~FLAGS_FLOAT_E; + } + else + { + exponent = (int)trio_floor(workNumber + epsilonCorrection); + workNumber = number; + /* + * We want to apply A / 10^B but the equivalent A * 10^-B gives better + * accuracy on platforms with true long double support. + */ +#if defined(TRIO_DOUBLE_DOUBLE) + workNumber /= TrioPower(dblBase, (trio_long_double_t)exponent); +#else + workNumber *= TrioPower(dblBase, (trio_long_double_t)-exponent); +#endif + if (trio_isinf(workNumber)) { + /* + * Scaling is done it two steps to avoid problems with subnormal + * numbers. + */ + workNumber /= TrioPower(dblBase, (trio_long_double_t)(exponent / 2)); + workNumber /= TrioPower(dblBase, (trio_long_double_t)(exponent - (exponent / 2))); + } + number = workNumber; + isExponentNegative = (exponent < 0); + uExponent = (isExponentNegative) ? -exponent : exponent; + if (isHex) + uExponent *= 4; /* log16(2) */ +#if TRIO_FEATURE_QUOTE + /* No thousand separators */ + flags &= ~FLAGS_QUOTE; +#endif + } + } + + integerNumber = trio_floor(number); + fractionNumber = number - integerNumber; + + /* + * Truncated number. + * + * Precision is number of significant digits for FLOAT_G and number of + * fractional digits for others. + */ + integerDigits = 1; + if (integerNumber > epsilon) + { + integerDigits += (int)(TrioLogarithm(integerNumber, base) + epsilonCorrection); + } + + fractionDigits = precision; + if (flags & FLAGS_FLOAT_G) + { + if (leadingFractionZeroes > 0) + { + fractionDigits += leadingFractionZeroes; + } + if ((integerNumber > epsilon) || (number <= epsilon)) + { + fractionDigits -= integerDigits; + } + } + + dblFractionBase = TrioPower(base, fractionDigits); + + if (integerNumber < 1.0) + { + workNumber = number * dblFractionBase + TRIO_SUFFIX_LONG(0.5); + if (trio_floor(number * dblFractionBase) != trio_floor(workNumber)) + { + adjustNumber = TRUE; + /* Remove a leading fraction zero if fraction is rounded up */ + if ((int)(TrioLogarithm(number * dblFractionBase, base) + epsilonCorrection) != + (int)(TrioLogarithm(workNumber, base) + epsilonCorrection)) + { + --leadingFractionZeroes; + } + } + workNumber /= dblFractionBase; + } + else + { + workNumber = number + TRIO_SUFFIX_LONG(0.5) / dblFractionBase; + adjustNumber = (trio_floor(number) != trio_floor(workNumber)); + } + if (adjustNumber) + { + if ((flags & FLAGS_FLOAT_G) && !(flags & FLAGS_FLOAT_E)) + { + /* The adjustment may require a change to scientific notation */ + if ( (workNumber < TRIO_SUFFIX_LONG(1.0E-4)) || + (workNumber >= TrioPower(base, (trio_long_double_t)precision)) ) + { + /* Use scientific notation */ + flags |= FLAGS_FLOAT_E; + goto reprocess; + } + } + + if (flags & FLAGS_FLOAT_E) + { + workDigits = 1 + (TrioLogarithm(trio_floor(workNumber), base) + epsilonCorrection); + if (integerDigits == workDigits) + { + /* Adjust if the same number of digits are used */ + number += TRIO_SUFFIX_LONG(0.5) / dblFractionBase; + integerNumber = trio_floor(number); + fractionNumber = number - integerNumber; + } + else + { + /* Adjust if number was rounded up one digit (ie. 0.99 to 1.00) */ + exponent++; + isExponentNegative = (exponent < 0); + uExponent = (isExponentNegative) ? -exponent : exponent; + if (isHex) + uExponent *= 4; /* log16(2) */ + workNumber = (number + TRIO_SUFFIX_LONG(0.5) / dblFractionBase) / dblBase; + integerNumber = trio_floor(workNumber); + fractionNumber = workNumber - integerNumber; + } + } + else + { + if (workNumber > 1.0) + { + /* Adjust if number was rounded up one digit (ie. 99 to 100) */ + integerNumber = trio_floor(workNumber); + fractionNumber = 0.0; + integerDigits = (integerNumber > epsilon) + ? 1 + (int)(TrioLogarithm(integerNumber, base) + epsilonCorrection) + : 1; + if (flags & FLAGS_FLOAT_G) + { + if (flags & FLAGS_ALTERNATIVE) + { + fractionDigits = precision; + if ((integerNumber > epsilon) || (number <= epsilon)) + { + fractionDigits -= integerDigits; + } + } + else + { + fractionDigits = 0; + } + } + } + else + { + integerNumber = trio_floor(workNumber); + fractionNumber = workNumber - integerNumber; + if (flags & FLAGS_FLOAT_G) + { + if (flags & FLAGS_ALTERNATIVE) + { + fractionDigits = precision; + if (leadingFractionZeroes > 0) + { + fractionDigits += leadingFractionZeroes; + } + if ((integerNumber > epsilon) || (number <= epsilon)) + { + fractionDigits -= integerDigits; + } + } + } + } + } + } + + /* Estimate accuracy */ + integerAdjust = fractionAdjust = TRIO_SUFFIX_LONG(0.5); +# if TRIO_FEATURE_ROUNDING + if (flags & FLAGS_ROUNDING) + { + if (integerDigits > baseDigits) + { + integerThreshold = baseDigits; + fractionDigits = 0; + dblFractionBase = 1.0; + fractionThreshold = 0; + precision = 0; /* Disable decimal-point */ + integerAdjust = TrioPower(base, integerDigits - integerThreshold - 1); + fractionAdjust = 0.0; + } + else + { + integerThreshold = integerDigits; + fractionThreshold = fractionDigits - integerThreshold; + fractionAdjust = 1.0; + } + } + else +# endif + { + integerThreshold = INT_MAX; + fractionThreshold = INT_MAX; + } + + /* + * Calculate expected width. + * sign + integer part + thousands separators + decimal point + * + fraction + exponent + */ + fractionAdjust /= dblFractionBase; + hasOnlyZeroes = (trio_floor((fractionNumber + fractionAdjust) * + dblFractionBase) < epsilon); + keepDecimalPoint = ( (flags & FLAGS_ALTERNATIVE) || + !((precision == 0) || + (!keepTrailingZeroes && hasOnlyZeroes)) ); + + expectedWidth = integerDigits + fractionDigits; + + if (!keepTrailingZeroes) + { + trailingZeroes = 0; + workFractionNumber = fractionNumber; + workFractionAdjust = fractionAdjust; + fractionDigitsInspect = fractionDigits; + + if (integerDigits > integerThreshold) + { + fractionDigitsInspect = 0; + } + else if (fractionThreshold <= fractionDigits) + { + fractionDigitsInspect = fractionThreshold + 1; + } + + trailingZeroes = fractionDigits - fractionDigitsInspect; + for (i = 0; i < fractionDigitsInspect; i++) + { + workFractionNumber *= dblBase; + workFractionAdjust *= dblBase; + workNumber = trio_floor(workFractionNumber + workFractionAdjust); + workFractionNumber -= workNumber; + offset = (int)trio_fmod(workNumber, dblBase); + if (offset == 0) + { + trailingZeroes++; + } + else + { + trailingZeroes = 0; + } + } + expectedWidth -= trailingZeroes; + } + + if (keepDecimalPoint) + { + expectedWidth += internalDecimalPointLength; + } + +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + { + expectedWidth += TrioCalcThousandSeparatorLength(integerDigits); + } +#endif + + if (isNegative || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) + { + expectedWidth += sizeof("-") - 1; + } + + exponentDigits = 0; + if (flags & FLAGS_FLOAT_E) + { + exponentDigits = (uExponent == 0) + ? 1 + : (int)trio_ceil(TrioLogarithm((double)(uExponent + 1), + (isHex) ? 10 : base)); + } + requireTwoDigitExponent = ((base == BASE_DECIMAL) && (exponentDigits == 1)); + if (exponentDigits > 0) + { + expectedWidth += exponentDigits; + expectedWidth += (requireTwoDigitExponent + ? sizeof("E+0") - 1 + : sizeof("E+") - 1); + } + + if (isHex) + { + expectedWidth += sizeof("0X") - 1; + } + + /* Output prefixing */ + if (flags & FLAGS_NILPADDING) + { + /* Leading zeros must be after sign */ + if (isNegative) + self->OutStream(self, '-'); + else if (flags & FLAGS_SHOWSIGN) + self->OutStream(self, '+'); + else if (flags & FLAGS_SPACE) + self->OutStream(self, ' '); + if (isHex) + { + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x'); + } + if (!(flags & FLAGS_LEFTADJUST)) + { + for (i = expectedWidth; i < width; i++) + { + self->OutStream(self, '0'); + } + } + } + else + { + /* Leading spaces must be before sign */ + if (!(flags & FLAGS_LEFTADJUST)) + { + for (i = expectedWidth; i < width; i++) + { + self->OutStream(self, CHAR_ADJUST); + } + } + if (isNegative) + self->OutStream(self, '-'); + else if (flags & FLAGS_SHOWSIGN) + self->OutStream(self, '+'); + else if (flags & FLAGS_SPACE) + self->OutStream(self, ' '); + if (isHex) + { + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x'); + } + } + + /* Output the integer part and thousand separators */ + for (i = 0; i < integerDigits; i++) + { + workNumber = trio_floor(((integerNumber + integerAdjust) + / TrioPower(base, integerDigits - i - 1))); + if (i > integerThreshold) + { + /* Beyond accuracy */ + self->OutStream(self, digits[0]); + } + else + { + self->OutStream(self, digits[(int)trio_fmod(workNumber, dblBase)]); + } + +#if TRIO_FEATURE_QUOTE + if (((flags & (FLAGS_FLOAT_E | FLAGS_QUOTE)) == FLAGS_QUOTE) + && TrioFollowedBySeparator(integerDigits - i)) + { + for (groupingPointer = internalThousandSeparator; + *groupingPointer != NIL; + groupingPointer++) + { + self->OutStream(self, *groupingPointer); + } + } +#endif + } + + /* Insert decimal point and build the fraction part */ + trailingZeroes = 0; + + if (keepDecimalPoint) + { + if (internalDecimalPoint) + { + self->OutStream(self, internalDecimalPoint); + } + else + { + for (i = 0; i < internalDecimalPointLength; i++) + { + self->OutStream(self, internalDecimalPointString[i]); + } + } + } + + for (i = 0; i < fractionDigits; i++) + { + if ((integerDigits > integerThreshold) || (i > fractionThreshold)) + { + /* Beyond accuracy */ + trailingZeroes++; + } + else + { + fractionNumber *= dblBase; + fractionAdjust *= dblBase; + workNumber = trio_floor(fractionNumber + fractionAdjust); + if (workNumber > fractionNumber) + { + /* fractionNumber should never become negative */ + fractionNumber = 0.0; + fractionAdjust = 0.0; + } + else + { + fractionNumber -= workNumber; + } + offset = (int)trio_fmod(workNumber, dblBase); + if (offset == 0) + { + trailingZeroes++; + } + else + { + while (trailingZeroes > 0) + { + /* Not trailing zeroes after all */ + self->OutStream(self, digits[0]); + trailingZeroes--; + } + self->OutStream(self, digits[offset]); + } + } + } + + if (keepTrailingZeroes) + { + while (trailingZeroes > 0) + { + self->OutStream(self, digits[0]); + trailingZeroes--; + } + } + + /* Output exponent */ + if (exponentDigits > 0) + { + self->OutStream(self, + isHex + ? ((flags & FLAGS_UPPER) ? 'P' : 'p') + : ((flags & FLAGS_UPPER) ? 'E' : 'e')); + self->OutStream(self, (isExponentNegative) ? '-' : '+'); + + /* The exponent must contain at least two digits */ + if (requireTwoDigitExponent) + self->OutStream(self, '0'); + + if (isHex) + base = 10; + exponentBase = (int)TrioPower(base, exponentDigits - 1); + for (i = 0; i < exponentDigits; i++) + { + self->OutStream(self, digits[(uExponent / exponentBase) % base]); + exponentBase /= base; + } + } + /* Output trailing spaces */ + if (flags & FLAGS_LEFTADJUST) + { + for (i = expectedWidth; i < width; i++) + { + self->OutStream(self, CHAR_ADJUST); + } + } +} +#endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * TrioFormatProcess + * + * Description: + * This is the main engine for formatting output + */ +TRIO_PRIVATE int +TrioFormatProcess +TRIO_ARGS3((data, format, parameters), + trio_class_t *data, + TRIO_CONST char *format, + trio_parameter_t *parameters) +{ + int i; +#if TRIO_FEATURE_ERRNO + TRIO_CONST char *string; +#endif + trio_pointer_t pointer; + trio_flags_t flags; + int width; + int precision; + int base; + int offset; + + offset = 0; + i = 0; + + for (;;) + { + /* Skip the parameter entries */ + while (parameters[i].type == FORMAT_PARAMETER) + i++; + + /* Copy non conversion-specifier part of format string */ + while (offset < parameters[i].beginOffset) + { + if (CHAR_IDENTIFIER == format[offset] && CHAR_IDENTIFIER == format[offset + 1]) + { + data->OutStream(data, CHAR_IDENTIFIER); + offset += 2; + } + else + { + data->OutStream(data, format[offset++]); + } + } + + /* Abort if we reached end of format string */ + if (parameters[i].type == FORMAT_SENTINEL) + break; + + /* Ouput parameter */ + flags = parameters[i].flags; + + /* Find width */ + width = parameters[i].width; + if (flags & FLAGS_WIDTH_PARAMETER) + { + /* Get width from parameter list */ + width = (int)parameters[width].data.number.as_signed; + if (width < 0) + { + /* + * A negative width is the same as the - flag and + * a positive width. + */ + flags |= FLAGS_LEFTADJUST; + flags &= ~FLAGS_NILPADDING; + width = -width; + } + } + + /* Find precision */ + if (flags & FLAGS_PRECISION) + { + precision = parameters[i].precision; + if (flags & FLAGS_PRECISION_PARAMETER) + { + /* Get precision from parameter list */ + precision = (int)parameters[precision].data.number.as_signed; + if (precision < 0) + { + /* + * A negative precision is the same as no + * precision + */ + precision = NO_PRECISION; + } + } + } + else + { + precision = NO_PRECISION; + } + + /* Find base */ + if (NO_BASE != parameters[i].baseSpecifier) + { + /* Base from specifier has priority */ + base = parameters[i].baseSpecifier; + } + else if (flags & FLAGS_BASE_PARAMETER) + { + /* Get base from parameter list */ + base = parameters[i].base; + base = (int)parameters[base].data.number.as_signed; + } + else + { + /* Use base from format string */ + base = parameters[i].base; + } + + switch (parameters[i].type) + { + case FORMAT_CHAR: +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + data->OutStream(data, CHAR_QUOTE); +#endif + if (! (flags & FLAGS_LEFTADJUST)) + { + while (--width > 0) + data->OutStream(data, CHAR_ADJUST); + } +#if TRIO_FEATURE_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + TrioWriteWideStringCharacter(data, + (trio_wchar_t)parameters[i].data.number.as_signed, + flags, + NO_WIDTH); + } + else +#endif + { + TrioWriteStringCharacter(data, + (int)parameters[i].data.number.as_signed, + flags); + } + + if (flags & FLAGS_LEFTADJUST) + { + while(--width > 0) + data->OutStream(data, CHAR_ADJUST); + } +#if TRIO_FEATURE_QUOTE + if (flags & FLAGS_QUOTE) + data->OutStream(data, CHAR_QUOTE); +#endif + + break; /* FORMAT_CHAR */ + + case FORMAT_INT: + TrioWriteNumber(data, + parameters[i].data.number.as_unsigned, + flags, + width, + precision, + base); + + break; /* FORMAT_INT */ + +#if TRIO_FEATURE_FLOAT + case FORMAT_DOUBLE: + TrioWriteDouble(data, + parameters[i].data.longdoubleNumber, + flags, + width, + precision, + base); + break; /* FORMAT_DOUBLE */ +#endif + + case FORMAT_STRING: +#if TRIO_FEATURE_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + TrioWriteWideString(data, + parameters[i].data.wstring, + flags, + width, + precision); + } + else +#endif + { + TrioWriteString(data, + parameters[i].data.string, + flags, + width, + precision); + } + break; /* FORMAT_STRING */ + + case FORMAT_POINTER: + { + trio_reference_t reference; + + reference.data = data; + reference.parameter = ¶meters[i]; + trio_print_pointer(&reference, parameters[i].data.pointer); + } + break; /* FORMAT_POINTER */ + + case FORMAT_COUNT: + pointer = parameters[i].data.pointer; + if (NULL != pointer) + { + /* + * C99 paragraph 7.19.6.1.8 says "the number of + * characters written to the output stream so far by + * this call", which is data->actually.committed + */ +#if TRIO_FEATURE_SIZE_T || TRIO_FEATURE_SIZE_T_UPPER + if (flags & FLAGS_SIZE_T) + *(size_t *)pointer = (size_t)data->actually.committed; + else +#endif +#if TRIO_FEATURE_PTRDIFF_T + if (flags & FLAGS_PTRDIFF_T) + *(ptrdiff_t *)pointer = (ptrdiff_t)data->actually.committed; + else +#endif +#if TRIO_FEATURE_INTMAX_T + if (flags & FLAGS_INTMAX_T) + *(trio_intmax_t *)pointer = (trio_intmax_t)data->actually.committed; + else +#endif + if (flags & FLAGS_QUAD) + { + *(trio_ulonglong_t *)pointer = (trio_ulonglong_t)data->actually.committed; + } + else if (flags & FLAGS_LONG) + { + *(long int *)pointer = (long int)data->actually.committed; + } + else if (flags & FLAGS_SHORT) + { + *(short int *)pointer = (short int)data->actually.committed; + } + else + { + *(int *)pointer = (int)data->actually.committed; + } + } + break; /* FORMAT_COUNT */ + + case FORMAT_PARAMETER: + break; /* FORMAT_PARAMETER */ + +#if TRIO_FEATURE_ERRNO + case FORMAT_ERRNO: + string = trio_error(parameters[i].data.errorNumber); + if (string) + { + TrioWriteString(data, + string, + flags, + width, + precision); + } + else + { + data->OutStream(data, '#'); + TrioWriteNumber(data, + (trio_uintmax_t)parameters[i].data.errorNumber, + flags, + width, + precision, + BASE_DECIMAL); + } + break; /* FORMAT_ERRNO */ +#endif /* TRIO_FEATURE_ERRNO */ + +#if TRIO_FEATURE_USER_DEFINED + case FORMAT_USER_DEFINED: + { + trio_reference_t reference; + trio_userdef_t *def = NULL; + + if (parameters[i].flags & FLAGS_USER_DEFINED_PARAMETER) + { + /* Use handle */ + if ((i > 0) || + (parameters[i - 1].type == FORMAT_PARAMETER)) + def = (trio_userdef_t *)parameters[i - 1].data.pointer; + } + else + { + /* Look up namespace */ + def = TrioFindNamespace(parameters[i].user_defined.namespace, NULL); + } + if (def) + { + reference.data = data; + reference.parameter = ¶meters[i]; + def->callback(&reference); + } + } + break; +#endif /* TRIO_FEATURE_USER_DEFINED */ + + default: + break; + } /* switch parameter type */ + + /* Prepare for next */ + offset = parameters[i].endOffset; + i++; + } + + return data->processed; +} + +/************************************************************************* + * TrioFormatRef + */ +#if TRIO_EXTENSION +TRIO_PRIVATE int +TrioFormatRef +TRIO_ARGS5((reference, format, arglist, argfunc, argarray), + trio_reference_t *reference, + TRIO_CONST char *format, + va_list arglist, + trio_argfunc_t argfunc, + trio_pointer_t *argarray) +{ + int status; + trio_parameter_t parameters[MAX_PARAMETERS]; + + status = TrioParse(TYPE_PRINT, format, parameters, arglist, argfunc, argarray); + if (status < 0) + return status; + + status = TrioFormatProcess(reference->data, format, parameters); + if (reference->data->error != 0) + { + status = reference->data->error; + } + return status; +} +#endif /* TRIO_EXTENSION */ + +/************************************************************************* + * TrioFormat + */ +TRIO_PRIVATE int +TrioFormat +TRIO_ARGS7((destination, destinationSize, OutStream, format, arglist, argfunc, argarray), + trio_pointer_t destination, + size_t destinationSize, + void (*OutStream) TRIO_PROTO((trio_class_t *, int)), + TRIO_CONST char *format, + va_list arglist, + trio_argfunc_t argfunc, + trio_pointer_t *argarray) +{ + int status; + trio_class_t data; + trio_parameter_t parameters[MAX_PARAMETERS]; + + assert(VALID(OutStream)); + assert(VALID(format)); + + memset(&data, 0, sizeof(data)); + data.OutStream = OutStream; + data.location = destination; + data.max = destinationSize; + data.error = 0; + +#if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +#endif + + status = TrioParse(TYPE_PRINT, format, parameters, arglist, argfunc, argarray); + if (status < 0) + return status; + + status = TrioFormatProcess(&data, format, parameters); + if (data.error != 0) + { + status = data.error; + } + return status; +} + +/************************************************************************* + * TrioOutStreamFile + */ +#if TRIO_FEATURE_FILE || TRIO_FEATURE_STDIO +TRIO_PRIVATE void +TrioOutStreamFile +TRIO_ARGS2((self, output), + trio_class_t *self, + int output) +{ + FILE *file; + + assert(VALID(self)); + assert(VALID(self->location)); + + file = (FILE *)self->location; + self->processed++; + if (fputc(output, file) == EOF) + { + self->error = TRIO_ERROR_RETURN(TRIO_EOF, 0); + } + else + { + self->actually.committed++; + } +} +#endif /* TRIO_FEATURE_FILE || TRIO_FEATURE_STDIO */ + +/************************************************************************* + * TrioOutStreamFileDescriptor + */ +#if TRIO_FEATURE_FD +TRIO_PRIVATE void +TrioOutStreamFileDescriptor +TRIO_ARGS2((self, output), + trio_class_t *self, + int output) +{ + int fd; + char ch; + + assert(VALID(self)); + + fd = *((int *)self->location); + ch = (char)output; + self->processed++; + if (write(fd, &ch, sizeof(char)) == -1) + { + self->error = TRIO_ERROR_RETURN(TRIO_ERRNO, 0); + } + else + { + self->actually.committed++; + } +} +#endif /* TRIO_FEATURE_FD */ + +/************************************************************************* + * TrioOutStreamCustom + */ +#if TRIO_FEATURE_CLOSURE +TRIO_PRIVATE void +TrioOutStreamCustom +TRIO_ARGS2((self, output), + trio_class_t *self, + int output) +{ + int status; + trio_custom_t *data; + + assert(VALID(self)); + assert(VALID(self->location)); + + data = (trio_custom_t *)self->location; + if (data->stream.out) + { + status = (data->stream.out)(data->closure, output); + if (status >= 0) + { + self->actually.committed++; + } + else + { + if (self->error == 0) + { + self->error = TRIO_ERROR_RETURN(TRIO_ECUSTOM, -status); + } + } + } + self->processed++; +} +#endif /* TRIO_FEATURE_CLOSURE */ + +/************************************************************************* + * TrioOutStreamString + */ +TRIO_PRIVATE void +TrioOutStreamString +TRIO_ARGS2((self, output), + trio_class_t *self, + int output) +{ + char **buffer; + + assert(VALID(self)); + assert(VALID(self->location)); + + buffer = (char **)self->location; + **buffer = (char)output; + (*buffer)++; + self->processed++; + self->actually.committed++; +} + +/************************************************************************* + * TrioOutStreamStringMax + */ +TRIO_PRIVATE void +TrioOutStreamStringMax +TRIO_ARGS2((self, output), + trio_class_t *self, + int output) +{ + char **buffer; + + assert(VALID(self)); + assert(VALID(self->location)); + + buffer = (char **)self->location; + + if (self->processed < self->max) + { + **buffer = (char)output; + (*buffer)++; + self->actually.committed++; + } + self->processed++; +} + +/************************************************************************* + * TrioOutStreamStringDynamic + */ +#if TRIO_FEATURE_DYNAMICSTRING +TRIO_PRIVATE void +TrioOutStreamStringDynamic +TRIO_ARGS2((self, output), + trio_class_t *self, + int output) +{ + assert(VALID(self)); + assert(VALID(self->location)); + + if (self->error == 0) + { + trio_xstring_append_char((trio_string_t *)self->location, + (char)output); + self->actually.committed++; + } + /* The processed variable must always be increased */ + self->processed++; +} +#endif /* TRIO_FEATURE_DYNAMICSTRING */ + +/************************************************************************* + * TrioArrayGetter + */ +TRIO_PRIVATE +trio_pointer_t TrioArrayGetter(trio_pointer_t context, int index, int type) +{ + /* Utility function for the printfv family */ + trio_pointer_t *argarray = (trio_pointer_t *)context; + return argarray[index]; +} + +/************************************************************************* + * + * Formatted printing functions + * + ************************************************************************/ + +/** @addtogroup Printf + @{ +*/ + +/************************************************************************* + * printf + */ + +/** + Print to standard output stream. + + @param format Formatting string. + @param ... Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_STDIO +TRIO_PUBLIC int +trio_printf +TRIO_VARGS2((format, va_alist), + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioFormat(stdout, 0, TrioOutStreamFile, format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_STDIO */ + +/** + Print to standard output stream. + + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_STDIO +TRIO_PUBLIC int +trio_vprintf +TRIO_ARGS2((format, args), + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioFormat(stdout, 0, TrioOutStreamFile, format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_STDIO */ + +/** + Print to standard output stream. + + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_STDIO +TRIO_PUBLIC int +trio_printfv +TRIO_ARGS2((format, args), + TRIO_CONST char *format, + trio_pointer_t * args) +{ + static va_list unused; + + assert(VALID(format)); + + return TrioFormat(stdout, 0, TrioOutStreamFile, format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_STDIO */ + +/************************************************************************* + * fprintf + */ + +/** + Print to file. + + @param file File pointer. + @param format Formatting string. + @param ... Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_FILE +TRIO_PUBLIC int +trio_fprintf +TRIO_VARGS3((file, format, va_alist), + FILE *file, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(file)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioFormat(file, 0, TrioOutStreamFile, format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_FILE */ + +/** + Print to file. + + @param file File pointer. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_FILE +TRIO_PUBLIC int +trio_vfprintf +TRIO_ARGS3((file, format, args), + FILE *file, + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(file)); + assert(VALID(format)); + + return TrioFormat(file, 0, TrioOutStreamFile, format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_FILE */ + +/** + Print to file. + + @param file File pointer. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_FILE +TRIO_PUBLIC int +trio_fprintfv +TRIO_ARGS3((file, format, args), + FILE *file, + TRIO_CONST char *format, + trio_pointer_t * args) +{ + static va_list unused; + + assert(VALID(file)); + assert(VALID(format)); + + return TrioFormat(file, 0, TrioOutStreamFile, format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_FILE */ + +/************************************************************************* + * dprintf + */ + +/** + Print to file descriptor. + + @param fd File descriptor. + @param format Formatting string. + @param ... Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_FD +TRIO_PUBLIC int +trio_dprintf +TRIO_VARGS3((fd, format, va_alist), + int fd, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioFormat(&fd, 0, TrioOutStreamFileDescriptor, format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_FD */ + +/** + Print to file descriptor. + + @param fd File descriptor. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_FD +TRIO_PUBLIC int +trio_vdprintf +TRIO_ARGS3((fd, format, args), + int fd, + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioFormat(&fd, 0, TrioOutStreamFileDescriptor, format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_FD */ + +/** + Print to file descriptor. + + @param fd File descriptor. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_FD +TRIO_PUBLIC int +trio_dprintfv +TRIO_ARGS3((fd, format, args), + int fd, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + + assert(VALID(format)); + + return TrioFormat(&fd, 0, TrioOutStreamFileDescriptor, format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_FD */ + +/************************************************************************* + * cprintf + */ +#if TRIO_FEATURE_CLOSURE +TRIO_PUBLIC int +trio_cprintf +TRIO_VARGS4((stream, closure, format, va_alist), + trio_outstream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + data.stream.out = stream; + data.closure = closure; + status = TrioFormat(&data, 0, TrioOutStreamCustom, format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_CLOSURE */ + +#if TRIO_FEATURE_CLOSURE +TRIO_PUBLIC int +trio_vcprintf +TRIO_ARGS4((stream, closure, format, args), + trio_outstream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + va_list args) +{ + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + + data.stream.out = stream; + data.closure = closure; + return TrioFormat(&data, 0, TrioOutStreamCustom, format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_CLOSURE */ + +#if TRIO_FEATURE_CLOSURE +TRIO_PUBLIC int +trio_cprintfv +TRIO_ARGS4((stream, closure, format, args), + trio_outstream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + + data.stream.out = stream; + data.closure = closure; + return TrioFormat(&data, 0, TrioOutStreamCustom, format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_CLOSURE */ + +#if TRIO_FEATURE_CLOSURE && TRIO_FEATURE_ARGFUNC +TRIO_PUBLIC int +trio_cprintff +TRIO_ARGS5((stream, closure, format, argfunc, context), + trio_outstream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + trio_argfunc_t argfunc, + trio_pointer_t context) +{ + static va_list unused; + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + assert(VALID(argfunc)); + + data.stream.out = stream; + data.closure = closure; + return TrioFormat(&data, 0, TrioOutStreamCustom, format, + unused, argfunc, (trio_pointer_t *)context); +} +#endif /* TRIO_FEATURE_CLOSURE && TRIO_FEATURE_ARGFUNC */ + +/************************************************************************* + * sprintf + */ + +/** + Print to string. + + @param buffer Output string. + @param format Formatting string. + @param ... Arguments. + @return Number of printed characters. + */ +TRIO_PUBLIC int +trio_sprintf +TRIO_VARGS3((buffer, format, va_alist), + char *buffer, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(buffer)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioFormat(&buffer, 0, TrioOutStreamString, format, args, NULL, NULL); + *buffer = NIL; /* Terminate with NIL character */ + TRIO_VA_END(args); + return status; +} + +/** + Print to string. + + @param buffer Output string. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +TRIO_PUBLIC int +trio_vsprintf +TRIO_ARGS3((buffer, format, args), + char *buffer, + TRIO_CONST char *format, + va_list args) +{ + int status; + + assert(VALID(buffer)); + assert(VALID(format)); + + status = TrioFormat(&buffer, 0, TrioOutStreamString, format, args, NULL, NULL); + *buffer = NIL; + return status; +} + +/** + Print to string. + + @param buffer Output string. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +TRIO_PUBLIC int +trio_sprintfv +TRIO_ARGS3((buffer, format, args), + char *buffer, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + int status; + + assert(VALID(buffer)); + assert(VALID(format)); + + status = TrioFormat(&buffer, 0, TrioOutStreamString, format, + unused, TrioArrayGetter, args); + *buffer = NIL; + return status; +} + +/************************************************************************* + * snprintf + */ + +/** + Print at most @p max characters to string. + + @param buffer Output string. + @param max Maximum number of characters to print. + @param format Formatting string. + @param ... Arguments. + @return Number of printed characters. + */ +TRIO_PUBLIC int +trio_snprintf +TRIO_VARGS4((buffer, max, format, va_alist), + char *buffer, + size_t max, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(buffer) || (max == 0)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioFormat(&buffer, max > 0 ? max - 1 : 0, + TrioOutStreamStringMax, format, args, NULL, NULL); + if (max > 0) + *buffer = NIL; + TRIO_VA_END(args); + return status; +} + +/** + Print at most @p max characters to string. + + @param buffer Output string. + @param max Maximum number of characters to print. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +TRIO_PUBLIC int +trio_vsnprintf +TRIO_ARGS4((buffer, max, format, args), + char *buffer, + size_t max, + TRIO_CONST char *format, + va_list args) +{ + int status; + + assert(VALID(buffer) || (max == 0)); + assert(VALID(format)); + + status = TrioFormat(&buffer, max > 0 ? max - 1 : 0, + TrioOutStreamStringMax, format, args, NULL, NULL); + if (max > 0) + *buffer = NIL; + return status; +} + +/** + Print at most @p max characters to string. + + @param buffer Output string. + @param max Maximum number of characters to print. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +TRIO_PUBLIC int +trio_snprintfv +TRIO_ARGS4((buffer, max, format, args), + char *buffer, + size_t max, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + int status; + + assert(VALID(buffer) || (max == 0)); + assert(VALID(format)); + + status = TrioFormat(&buffer, max > 0 ? max - 1 : 0, + TrioOutStreamStringMax, format, + unused, TrioArrayGetter, args); + if (max > 0) + *buffer = NIL; + return status; +} + +/************************************************************************* + * snprintfcat + * Appends the new string to the buffer string overwriting the '\0' + * character at the end of buffer. + */ +#if TRIO_EXTENSION +TRIO_PUBLIC int +trio_snprintfcat +TRIO_VARGS4((buffer, max, format, va_alist), + char *buffer, + size_t max, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + size_t buf_len; + + TRIO_VA_START(args, format); + + assert(VALID(buffer)); + assert(VALID(format)); + + buf_len = trio_length(buffer); + buffer = &buffer[buf_len]; + + status = TrioFormat(&buffer, max - 1 - buf_len, + TrioOutStreamStringMax, format, args, NULL, NULL); + TRIO_VA_END(args); + *buffer = NIL; + return status; +} +#endif + +#if TRIO_EXTENSION +TRIO_PUBLIC int +trio_vsnprintfcat +TRIO_ARGS4((buffer, max, format, args), + char *buffer, + size_t max, + TRIO_CONST char *format, + va_list args) +{ + int status; + size_t buf_len; + + assert(VALID(buffer)); + assert(VALID(format)); + + buf_len = trio_length(buffer); + buffer = &buffer[buf_len]; + status = TrioFormat(&buffer, max - 1 - buf_len, + TrioOutStreamStringMax, format, args, NULL, NULL); + *buffer = NIL; + return status; +} +#endif + +/************************************************************************* + * trio_aprintf + */ + +#if TRIO_DEPRECATED && TRIO_FEATURE_DYNAMICSTRING +TRIO_PUBLIC char * +trio_aprintf +TRIO_VARGS2((format, va_alist), + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + va_list args; + trio_string_t *info; + char *result = NULL; + + assert(VALID(format)); + + info = trio_xstring_duplicate(""); + if (info) + { + TRIO_VA_START(args, format); + (void)TrioFormat(info, 0, TrioOutStreamStringDynamic, + format, args, NULL, NULL); + TRIO_VA_END(args); + + trio_string_terminate(info); + result = trio_string_extract(info); + trio_string_destroy(info); + } + return result; +} +#endif /* TRIO_DEPRECATED && TRIO_FEATURE_DYNAMICSTRING */ + +#if TRIO_DEPRECATED && TRIO_FEATURE_DYNAMICSTRING +TRIO_PUBLIC char * +trio_vaprintf +TRIO_ARGS2((format, args), + TRIO_CONST char *format, + va_list args) +{ + trio_string_t *info; + char *result = NULL; + + assert(VALID(format)); + + info = trio_xstring_duplicate(""); + if (info) + { + (void)TrioFormat(info, 0, TrioOutStreamStringDynamic, + format, args, NULL, NULL); + trio_string_terminate(info); + result = trio_string_extract(info); + trio_string_destroy(info); + } + return result; +} +#endif /* TRIO_DEPRECATED && TRIO_FEATURE_DYNAMICSTRING */ + +/** + Allocate and print to string. + The memory allocated and returned by @p result must be freed by the + calling application. + + @param result Output string. + @param format Formatting string. + @param ... Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_DYNAMICSTRING +TRIO_PUBLIC int +trio_asprintf +TRIO_VARGS3((result, format, va_alist), + char **result, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + va_list args; + int status; + trio_string_t *info; + + assert(VALID(format)); + + *result = NULL; + + info = trio_xstring_duplicate(""); + if (info == NULL) + { + status = TRIO_ERROR_RETURN(TRIO_ENOMEM, 0); + } + else + { + TRIO_VA_START(args, format); + status = TrioFormat(info, 0, TrioOutStreamStringDynamic, + format, args, NULL, NULL); + TRIO_VA_END(args); + if (status >= 0) + { + trio_string_terminate(info); + *result = trio_string_extract(info); + } + trio_string_destroy(info); + } + return status; +} +#endif /* TRIO_FEATURE_DYNAMICSTRING */ + +/** + Allocate and print to string. + The memory allocated and returned by @p result must be freed by the + calling application. + + @param result Output string. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_DYNAMICSTRING +TRIO_PUBLIC int +trio_vasprintf +TRIO_ARGS3((result, format, args), + char **result, + TRIO_CONST char *format, + va_list args) +{ + int status; + trio_string_t *info; + + assert(VALID(format)); + + *result = NULL; + + info = trio_xstring_duplicate(""); + if (info == NULL) + { + status = TRIO_ERROR_RETURN(TRIO_ENOMEM, 0); + } + else + { + status = TrioFormat(info, 0, TrioOutStreamStringDynamic, + format, args, NULL, NULL); + if (status >= 0) + { + trio_string_terminate(info); + *result = trio_string_extract(info); + } + trio_string_destroy(info); + } + return status; +} +#endif /* TRIO_FEATURE_DYNAMICSTRING */ + +/** + Allocate and print to string. + The memory allocated and returned by @p result must be freed by the + calling application. + + @param result Output string. + @param format Formatting string. + @param args Arguments. + @return Number of printed characters. + */ +#if TRIO_FEATURE_DYNAMICSTRING +TRIO_PUBLIC int +trio_asprintfv +TRIO_ARGS3((result, format, args), + char **result, + TRIO_CONST char *format, + trio_pointer_t * args) +{ + static va_list unused; + int status; + trio_string_t *info; + + assert(VALID(format)); + + *result = NULL; + + info = trio_xstring_duplicate(""); + if (info == NULL) + { + status = TRIO_ERROR_RETURN(TRIO_ENOMEM, 0); + } + else + { + status = TrioFormat(info, 0, TrioOutStreamStringDynamic, format, + unused, TrioArrayGetter, args); + if (status >= 0) + { + trio_string_terminate(info); + *result = trio_string_extract(info); + } + trio_string_destroy(info); + } + return status; +} +#endif /* TRIO_FEATURE_DYNAMICSTRING */ + +#if defined(TRIO_DOCUMENTATION) +# include "doc/doc_printf.h" +#endif + +/** @} End of Printf documentation module */ + +/************************************************************************* + * + * CALLBACK + * + ************************************************************************/ + +#if defined(TRIO_DOCUMENTATION) +# include "doc/doc_register.h" +#endif +/** + @addtogroup UserDefined + @{ +*/ + +#if TRIO_FEATURE_USER_DEFINED + +/************************************************************************* + * trio_register + */ + +/** + Register new user-defined specifier. + + @param callback + @param name + @return Handle. + */ +TRIO_PUBLIC trio_pointer_t +trio_register +TRIO_ARGS2((callback, name), + trio_callback_t callback, + TRIO_CONST char *name) +{ + trio_userdef_t *def; + trio_userdef_t *prev = NULL; + + if (callback == NULL) + return NULL; + + if (name) + { + /* Handle built-in namespaces */ + if (name[0] == ':') + { + if (trio_equal(name, ":enter")) + { + internalEnterCriticalRegion = callback; + } + else if (trio_equal(name, ":leave")) + { + internalLeaveCriticalRegion = callback; + } + return NULL; + } + + /* Bail out if namespace is too long */ + if (trio_length(name) >= MAX_USER_NAME) + return NULL; + + /* Bail out if namespace already is registered */ + def = TrioFindNamespace(name, &prev); + if (def) + return NULL; + } + + def = (trio_userdef_t *)TRIO_MALLOC(sizeof(trio_userdef_t)); + if (def) + { + if (internalEnterCriticalRegion) + (void)internalEnterCriticalRegion(NULL); + + if (name) + { + /* Link into internal list */ + if (prev == NULL) + internalUserDef = def; + else + prev->next = def; + } + /* Initialize */ + def->callback = callback; + def->name = (name == NULL) + ? NULL + : trio_duplicate(name); + def->next = NULL; + + if (internalLeaveCriticalRegion) + (void)internalLeaveCriticalRegion(NULL); + } + return (trio_pointer_t)def; +} + +/** + Unregister an existing user-defined specifier. + + @param handle + */ +TRIO_PUBLIC +void +trio_unregister +TRIO_ARGS1((handle), + trio_pointer_t handle) +{ + trio_userdef_t *self = (trio_userdef_t *)handle; + trio_userdef_t *def; + trio_userdef_t *prev = NULL; + + assert(VALID(self)); + + if (self->name) + { + def = TrioFindNamespace(self->name, &prev); + if (def) + { + if (internalEnterCriticalRegion) + (void)internalEnterCriticalRegion(NULL); + + if (prev == NULL) + internalUserDef = internalUserDef->next; + else + prev->next = def->next; + + if (internalLeaveCriticalRegion) + (void)internalLeaveCriticalRegion(NULL); + } + trio_destroy(self->name); + } + TRIO_FREE(self); +} + +/************************************************************************* + * trio_get_format + */ +TRIO_PUBLIC +TRIO_CONST char * +trio_get_format +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ +#if TRIO_FEATURE_USER_DEFINED + assert(((trio_reference_t *)ref)->parameter->type == FORMAT_USER_DEFINED); +#endif + + return (((trio_reference_t *)ref)->parameter->user_data); +} + +/************************************************************************* + * trio_get_argument + */ +TRIO_PUBLIC +TRIO_CONST trio_pointer_t +trio_get_argument +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ +#if TRIO_FEATURE_USER_DEFINED + assert(((trio_reference_t *)ref)->parameter->type == FORMAT_USER_DEFINED); +#endif + + return ((trio_reference_t *)ref)->parameter->data.pointer; +} + +/************************************************************************* + * trio_get_width / trio_set_width + */ +TRIO_PUBLIC +int +trio_get_width +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return ((trio_reference_t *)ref)->parameter->width; +} + +TRIO_PUBLIC +void +trio_set_width +TRIO_ARGS2((ref, width), + trio_pointer_t ref, + int width) +{ + ((trio_reference_t *)ref)->parameter->width = width; +} + +/************************************************************************* + * trio_get_precision / trio_set_precision + */ +TRIO_PUBLIC +int +trio_get_precision +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->precision); +} + +TRIO_PUBLIC +void +trio_set_precision +TRIO_ARGS2((ref, precision), + trio_pointer_t ref, + int precision) +{ + ((trio_reference_t *)ref)->parameter->precision = precision; +} + +/************************************************************************* + * trio_get_base / trio_set_base + */ +TRIO_PUBLIC +int +trio_get_base +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->base); +} + +TRIO_PUBLIC +void +trio_set_base +TRIO_ARGS2((ref, base), + trio_pointer_t ref, + int base) +{ + ((trio_reference_t *)ref)->parameter->base = base; +} + +/************************************************************************* + * trio_get_long / trio_set_long + */ +TRIO_PUBLIC +int +trio_get_long +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_LONG) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_long +TRIO_ARGS2((ref, is_long), + trio_pointer_t ref, + int is_long) +{ + if (is_long) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_LONG; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_LONG; +} + +/************************************************************************* + * trio_get_longlong / trio_set_longlong + */ +TRIO_PUBLIC +int +trio_get_longlong +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_QUAD) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_longlong +TRIO_ARGS2((ref, is_longlong), + trio_pointer_t ref, + int is_longlong) +{ + if (is_longlong) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_QUAD; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_QUAD; +} + +/************************************************************************* + * trio_get_longdouble / trio_set_longdouble + */ +# if TRIO_FEATURE_FLOAT +TRIO_PUBLIC +int +trio_get_longdouble +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_LONGDOUBLE) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_longdouble +TRIO_ARGS2((ref, is_longdouble), + trio_pointer_t ref, + int is_longdouble) +{ + if (is_longdouble) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_LONGDOUBLE; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_LONGDOUBLE; +} +# endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * trio_get_short / trio_set_short + */ +TRIO_PUBLIC +int +trio_get_short +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_SHORT) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_short +TRIO_ARGS2((ref, is_short), + trio_pointer_t ref, + int is_short) +{ + if (is_short) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_SHORT; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_SHORT; +} + +/************************************************************************* + * trio_get_shortshort / trio_set_shortshort + */ +TRIO_PUBLIC +int +trio_get_shortshort +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_SHORTSHORT) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_shortshort +TRIO_ARGS2((ref, is_shortshort), + trio_pointer_t ref, + int is_shortshort) +{ + if (is_shortshort) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_SHORTSHORT; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_SHORTSHORT; +} + +/************************************************************************* + * trio_get_alternative / trio_set_alternative + */ +TRIO_PUBLIC +int +trio_get_alternative +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_ALTERNATIVE) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_alternative +TRIO_ARGS2((ref, is_alternative), + trio_pointer_t ref, + int is_alternative) +{ + if (is_alternative) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_ALTERNATIVE; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_ALTERNATIVE; +} + +/************************************************************************* + * trio_get_alignment / trio_set_alignment + */ +TRIO_PUBLIC +int +trio_get_alignment +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_LEFTADJUST) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_alignment +TRIO_ARGS2((ref, is_leftaligned), + trio_pointer_t ref, + int is_leftaligned) +{ + if (is_leftaligned) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_LEFTADJUST; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_LEFTADJUST; +} + +/************************************************************************* + * trio_get_spacing /trio_set_spacing + */ +TRIO_PUBLIC +int +trio_get_spacing +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_SPACE) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_spacing +TRIO_ARGS2((ref, is_space), + trio_pointer_t ref, + int is_space) +{ + if (is_space) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_SPACE; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_SPACE; +} + +/************************************************************************* + * trio_get_sign / trio_set_sign + */ +TRIO_PUBLIC +int +trio_get_sign +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_SHOWSIGN) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_sign +TRIO_ARGS2((ref, is_sign), + trio_pointer_t ref, + int is_sign) +{ + if (is_sign) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_SHOWSIGN; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_SHOWSIGN; +} + +/************************************************************************* + * trio_get_padding / trio_set_padding + */ +TRIO_PUBLIC +int +trio_get_padding +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_NILPADDING) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_padding +TRIO_ARGS2((ref, is_padding), + trio_pointer_t ref, + int is_padding) +{ + if (is_padding) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_NILPADDING; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_NILPADDING; +} + +/************************************************************************* + * trio_get_quote / trio_set_quote + */ +# if TRIO_FEATURE_QUOTE +TRIO_PUBLIC +int +trio_get_quote +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_QUOTE) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_quote +TRIO_ARGS2((ref, is_quote), + trio_pointer_t ref, + int is_quote) +{ + if (is_quote) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_QUOTE; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_QUOTE; +} +#endif /* TRIO_FEATURE_QUOTE */ + +/************************************************************************* + * trio_get_upper / trio_set_upper + */ +TRIO_PUBLIC +int +trio_get_upper +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_UPPER) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_upper +TRIO_ARGS2((ref, is_upper), + trio_pointer_t ref, + int is_upper) +{ + if (is_upper) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_UPPER; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_UPPER; +} + +/************************************************************************* + * trio_get_largest / trio_set_largest + */ +#if TRIO_FEATURE_INTMAX_T +TRIO_PUBLIC +int +trio_get_largest +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_INTMAX_T) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_largest +TRIO_ARGS2((ref, is_largest), + trio_pointer_t ref, + int is_largest) +{ + if (is_largest) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_INTMAX_T; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_INTMAX_T; +} +#endif /* TRIO_FEATURE_INTMAX_T */ + +/************************************************************************* + * trio_get_ptrdiff / trio_set_ptrdiff + */ +#if TRIO_FEATURE_PTRDIFF_T +TRIO_PUBLIC +int +trio_get_ptrdiff +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_PTRDIFF_T) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_ptrdiff +TRIO_ARGS2((ref, is_ptrdiff), + trio_pointer_t ref, + int is_ptrdiff) +{ + if (is_ptrdiff) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_PTRDIFF_T; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_PTRDIFF_T; +} +#endif /* TRIO_FEATURE_PTRDIFF_T */ + +/************************************************************************* + * trio_get_size / trio_set_size + */ +#if TRIO_FEATURE_SIZE_T +TRIO_PUBLIC +int +trio_get_size +TRIO_ARGS1((ref), + trio_pointer_t ref) +{ + return (((trio_reference_t *)ref)->parameter->flags & FLAGS_SIZE_T) + ? TRUE + : FALSE; +} + +TRIO_PUBLIC +void +trio_set_size +TRIO_ARGS2((ref, is_size), + trio_pointer_t ref, + int is_size) +{ + if (is_size) + ((trio_reference_t *)ref)->parameter->flags |= FLAGS_SIZE_T; + else + ((trio_reference_t *)ref)->parameter->flags &= ~FLAGS_SIZE_T; +} +#endif /* TRIO_FEATURE_SIZE_T */ + +/************************************************************************* + * trio_print_int + */ +TRIO_PUBLIC +void +trio_print_int +TRIO_ARGS2((ref, number), + trio_pointer_t ref, + int number) +{ + trio_reference_t *self = (trio_reference_t *)ref; + + TrioWriteNumber(self->data, + (trio_uintmax_t)number, + self->parameter->flags, + self->parameter->width, + self->parameter->precision, + self->parameter->base); +} + +/************************************************************************* + * trio_print_uint + */ +TRIO_PUBLIC +void +trio_print_uint +TRIO_ARGS2((ref, number), + trio_pointer_t ref, + unsigned int number) +{ + trio_reference_t *self = (trio_reference_t *)ref; + + TrioWriteNumber(self->data, + (trio_uintmax_t)number, + self->parameter->flags | FLAGS_UNSIGNED, + self->parameter->width, + self->parameter->precision, + self->parameter->base); +} + +/************************************************************************* + * trio_print_double + */ +#if TRIO_FEATURE_FLOAT +TRIO_PUBLIC +void +trio_print_double +TRIO_ARGS2((ref, number), + trio_pointer_t ref, + double number) +{ + trio_reference_t *self = (trio_reference_t *)ref; + + TrioWriteDouble(self->data, + number, + self->parameter->flags, + self->parameter->width, + self->parameter->precision, + self->parameter->base); +} +#endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * trio_print_string + */ +TRIO_PUBLIC +void +trio_print_string +TRIO_ARGS2((ref, string), + trio_pointer_t ref, + TRIO_CONST char *string) +{ + trio_reference_t *self = (trio_reference_t *)ref; + + TrioWriteString(self->data, + string, + self->parameter->flags, + self->parameter->width, + self->parameter->precision); +} + +/************************************************************************* + * trio_print_ref + */ +TRIO_PUBLIC +int +trio_print_ref +TRIO_VARGS3((ref, format, va_alist), + trio_pointer_t ref, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list arglist; + + assert(VALID(format)); + + TRIO_VA_START(arglist, format); + status = TrioFormatRef((trio_reference_t *)ref, format, arglist, NULL, NULL); + TRIO_VA_END(arglist); + return status; +} + +/************************************************************************* + * trio_vprint_ref + */ +TRIO_PUBLIC +int +trio_vprint_ref +TRIO_ARGS3((ref, format, arglist), + trio_pointer_t ref, + TRIO_CONST char *format, + va_list arglist) +{ + assert(VALID(format)); + + return TrioFormatRef((trio_reference_t *)ref, format, arglist, NULL, NULL); +} + +/************************************************************************* + * trio_printv_ref + */ +TRIO_PUBLIC +int +trio_printv_ref +TRIO_ARGS3((ref, format, argarray), + trio_pointer_t ref, + TRIO_CONST char *format, + trio_pointer_t *argarray) +{ + static va_list unused; + + assert(VALID(format)); + + return TrioFormatRef((trio_reference_t *)ref, format, + unused, TrioArrayGetter, argarray); +} + +#endif + +/************************************************************************* + * trio_print_pointer + */ +TRIO_PUBLIC +void +trio_print_pointer +TRIO_ARGS2((ref, pointer), + trio_pointer_t ref, + trio_pointer_t pointer) +{ + trio_reference_t *self = (trio_reference_t *)ref; + trio_flags_t flags; + trio_uintmax_t number; + + if (NULL == pointer) + { + TRIO_CONST char *string = internalNullString; + while (*string) + self->data->OutStream(self->data, *string++); + } + else + { + /* + * The subtraction of the null pointer is a workaround + * to avoid a compiler warning. The performance overhead + * is negligible (and likely to be removed by an + * optimizing compiler). The (char *) casting is done + * to please ANSI C++. + */ + number = (trio_uintmax_t)((char *)pointer - (char *)0); + /* Shrink to size of pointer */ + number &= (trio_uintmax_t)-1; + flags = self->parameter->flags; + flags |= (FLAGS_UNSIGNED | FLAGS_ALTERNATIVE | + FLAGS_NILPADDING); + TrioWriteNumber(self->data, + number, + flags, + POINTER_WIDTH, + NO_PRECISION, + BASE_HEX); + } +} + +/** @} End of UserDefined documentation module */ + +/************************************************************************* + * + * LOCALES + * + ************************************************************************/ + +/************************************************************************* + * trio_locale_set_decimal_point + * + * Decimal point can only be one character. The input argument is a + * string to enable multibyte characters. At most MB_LEN_MAX characters + * will be used. + */ +#if TRIO_FEATURE_LOCALE +TRIO_PUBLIC void +trio_locale_set_decimal_point +TRIO_ARGS1((decimalPoint), + char *decimalPoint) +{ +#if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +#endif + internalDecimalPointLength = trio_length(decimalPoint); + if (internalDecimalPointLength == 1) + { + internalDecimalPoint = *decimalPoint; + } + else + { + internalDecimalPoint = NIL; + trio_copy_max(internalDecimalPointString, + sizeof(internalDecimalPointString), + decimalPoint); + } +} +#endif + +/************************************************************************* + * trio_locale_set_thousand_separator + * + * See trio_locale_set_decimal_point + */ +#if TRIO_FEATURE_LOCALE || TRIO_EXTENSION +TRIO_PUBLIC void +trio_locale_set_thousand_separator +TRIO_ARGS1((thousandSeparator), + char *thousandSeparator) +{ +# if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +# endif + trio_copy_max(internalThousandSeparator, + sizeof(internalThousandSeparator), + thousandSeparator); + internalThousandSeparatorLength = trio_length(internalThousandSeparator); +} +#endif + +/************************************************************************* + * trio_locale_set_grouping + * + * Array of bytes. Reversed order. + * + * CHAR_MAX : No further grouping + * 0 : Repeat last group for the remaining digits (not necessary + * as C strings are zero-terminated) + * n : Set current group to n + * + * Same order as the grouping attribute in LC_NUMERIC. + */ +#if TRIO_FEATURE_LOCALE || TRIO_EXTENSION +TRIO_PUBLIC void +trio_locale_set_grouping +TRIO_ARGS1((grouping), + char *grouping) +{ +# if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +# endif + trio_copy_max(internalGrouping, + sizeof(internalGrouping), + grouping); +} +#endif + + +/************************************************************************* + * + * SCANNING + * + ************************************************************************/ + +#if TRIO_FEATURE_SCANF + +/************************************************************************* + * TrioSkipWhitespaces + */ +TRIO_PRIVATE int +TrioSkipWhitespaces +TRIO_ARGS1((self), + trio_class_t *self) +{ + int ch; + + ch = self->current; + while (isspace(ch)) + { + self->InStream(self, &ch); + } + return ch; +} + +/************************************************************************* + * TrioGetCollation + */ +#if TRIO_EXTENSION +TRIO_PRIVATE void +TrioGetCollation(TRIO_NOARGS) +{ + int i; + int j; + int k; + char first[2]; + char second[2]; + + /* This is computationally expensive */ + first[1] = NIL; + second[1] = NIL; + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + { + k = 0; + first[0] = (char)i; + for (j = 0; j < MAX_CHARACTER_CLASS; j++) + { + second[0] = (char)j; + if (trio_equal_locale(first, second)) + internalCollationArray[i][k++] = (char)j; + } + internalCollationArray[i][k] = NIL; + } +} +#endif + +/************************************************************************* + * TrioGetCharacterClass + * + * FIXME: + * multibyte + */ +TRIO_PRIVATE int +TrioGetCharacterClass +TRIO_ARGS4((format, offsetPointer, flagsPointer, characterclass), + TRIO_CONST char *format, + int *offsetPointer, + trio_flags_t *flagsPointer, + int *characterclass) +{ + int offset = *offsetPointer; + int i; + char ch; + char range_begin; + char range_end; + + *flagsPointer &= ~FLAGS_EXCLUDE; + + if (format[offset] == QUALIFIER_CIRCUMFLEX) + { + *flagsPointer |= FLAGS_EXCLUDE; + offset++; + } + /* + * If the ungroup character is at the beginning of the scanlist, + * it will be part of the class, and a second ungroup character + * must follow to end the group. + */ + if (format[offset] == SPECIFIER_UNGROUP) + { + characterclass[(int)SPECIFIER_UNGROUP]++; + offset++; + } + /* + * Minus is used to specify ranges. To include minus in the class, + * it must be at the beginning of the list + */ + if (format[offset] == QUALIFIER_MINUS) + { + characterclass[(int)QUALIFIER_MINUS]++; + offset++; + } + /* Collect characters */ + for (ch = format[offset]; + (ch != SPECIFIER_UNGROUP) && (ch != NIL); + ch = format[++offset]) + { + switch (ch) + { + case QUALIFIER_MINUS: /* Scanlist ranges */ + + /* + * Both C99 and UNIX98 describes ranges as implementation- + * defined. + * + * We support the following behaviour (although this may + * change as we become wiser) + * - only increasing ranges, ie. [a-b] but not [b-a] + * - transitive ranges, ie. [a-b-c] == [a-c] + * - trailing minus, ie. [a-] is interpreted as an 'a' + * and a '-' + * - duplicates (although we can easily convert these + * into errors) + */ + range_begin = format[offset - 1]; + range_end = format[++offset]; + if (range_end == SPECIFIER_UNGROUP) + { + /* Trailing minus is included */ + characterclass[(int)ch]++; + ch = range_end; + break; /* for */ + } + if (range_end == NIL) + return TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + if (range_begin > range_end) + return TRIO_ERROR_RETURN(TRIO_ERANGE, offset); + + for (i = (int)range_begin; i <= (int)range_end; i++) + characterclass[i]++; + + ch = range_end; + break; + +#if TRIO_EXTENSION + + case SPECIFIER_GROUP: + + switch (format[offset + 1]) + { + case QUALIFIER_DOT: /* Collating symbol */ + /* + * FIXME: This will be easier to implement when multibyte + * characters have been implemented. Until now, we ignore + * this feature. + */ + for (i = offset + 2; ; i++) + { + if (format[i] == NIL) + /* Error in syntax */ + return -1; + else if (format[i] == QUALIFIER_DOT) + break; /* for */ + } + if (format[++i] != SPECIFIER_UNGROUP) + return -1; + + offset = i; + break; + + case QUALIFIER_EQUAL: /* Equivalence class expressions */ + { + unsigned int j; + unsigned int k; + + if (internalCollationUnconverted) + { + /* Lazy evaluation of collation array */ + TrioGetCollation(); + internalCollationUnconverted = FALSE; + } + for (i = offset + 2; ; i++) + { + if (format[i] == NIL) + /* Error in syntax */ + return -1; + else if (format[i] == QUALIFIER_EQUAL) + break; /* for */ + else + { + /* Mark any equivalent character */ + k = (unsigned int)format[i]; + for (j = 0; internalCollationArray[k][j] != NIL; j++) + characterclass[(int)internalCollationArray[k][j]]++; + } + } + if (format[++i] != SPECIFIER_UNGROUP) + return -1; + + offset = i; + } + break; + + case QUALIFIER_COLON: /* Character class expressions */ + + if (trio_equal_max(CLASS_ALNUM, sizeof(CLASS_ALNUM) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isalnum(i)) + characterclass[i]++; + offset += sizeof(CLASS_ALNUM) - 1; + } + else if (trio_equal_max(CLASS_ALPHA, sizeof(CLASS_ALPHA) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isalpha(i)) + characterclass[i]++; + offset += sizeof(CLASS_ALPHA) - 1; + } + else if (trio_equal_max(CLASS_CNTRL, sizeof(CLASS_CNTRL) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (iscntrl(i)) + characterclass[i]++; + offset += sizeof(CLASS_CNTRL) - 1; + } + else if (trio_equal_max(CLASS_DIGIT, sizeof(CLASS_DIGIT) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isdigit(i)) + characterclass[i]++; + offset += sizeof(CLASS_DIGIT) - 1; + } + else if (trio_equal_max(CLASS_GRAPH, sizeof(CLASS_GRAPH) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isgraph(i)) + characterclass[i]++; + offset += sizeof(CLASS_GRAPH) - 1; + } + else if (trio_equal_max(CLASS_LOWER, sizeof(CLASS_LOWER) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (islower(i)) + characterclass[i]++; + offset += sizeof(CLASS_LOWER) - 1; + } + else if (trio_equal_max(CLASS_PRINT, sizeof(CLASS_PRINT) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isprint(i)) + characterclass[i]++; + offset += sizeof(CLASS_PRINT) - 1; + } + else if (trio_equal_max(CLASS_PUNCT, sizeof(CLASS_PUNCT) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (ispunct(i)) + characterclass[i]++; + offset += sizeof(CLASS_PUNCT) - 1; + } + else if (trio_equal_max(CLASS_SPACE, sizeof(CLASS_SPACE) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isspace(i)) + characterclass[i]++; + offset += sizeof(CLASS_SPACE) - 1; + } + else if (trio_equal_max(CLASS_UPPER, sizeof(CLASS_UPPER) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isupper(i)) + characterclass[i]++; + offset += sizeof(CLASS_UPPER) - 1; + } + else if (trio_equal_max(CLASS_XDIGIT, sizeof(CLASS_XDIGIT) - 1, + &format[offset])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isxdigit(i)) + characterclass[i]++; + offset += sizeof(CLASS_XDIGIT) - 1; + } + else + { + characterclass[(int)ch]++; + } + break; + + default: + characterclass[(int)ch]++; + break; + } + break; + +#endif /* TRIO_EXTENSION */ + + default: + characterclass[(int)ch]++; + break; + } + } + return 0; +} + +/************************************************************************* + * TrioReadNumber + * + * We implement our own number conversion in preference of strtol and + * strtoul, because we must handle 'long long' and thousand separators. + */ +TRIO_PRIVATE BOOLEAN_T +TrioReadNumber +TRIO_ARGS5((self, target, flags, width, base), + trio_class_t *self, + trio_uintmax_t *target, + trio_flags_t flags, + int width, + int base) +{ + trio_uintmax_t number = 0; + int digit; + int count; + BOOLEAN_T isNegative = FALSE; + BOOLEAN_T gotNumber = FALSE; + int j; + + assert(VALID(self)); + assert(VALID(self->InStream)); + assert((base >= MIN_BASE && base <= MAX_BASE) || (base == NO_BASE)); + + if (internalDigitsUnconverted) + { + /* Lazy evaluation of digits array */ + memset(internalDigitArray, -1, sizeof(internalDigitArray)); + for (j = 0; j < (int)sizeof(internalDigitsLower) - 1; j++) + { + internalDigitArray[(int)internalDigitsLower[j]] = j; + internalDigitArray[(int)internalDigitsUpper[j]] = j; + } + internalDigitsUnconverted = FALSE; + } + + TrioSkipWhitespaces(self); + + /* Leading sign */ + if (self->current == '+') + { + self->InStream(self, NULL); + } + else if (self->current == '-') + { + self->InStream(self, NULL); + isNegative = TRUE; + } + + count = self->processed; + + if (flags & FLAGS_ALTERNATIVE) + { + switch (base) + { + case NO_BASE: + case BASE_OCTAL: + case BASE_HEX: + case BASE_BINARY: + if (self->current == '0') + { + self->InStream(self, NULL); + if (self->current) + { + if ((base == BASE_HEX) && + (trio_to_upper(self->current) == 'X')) + { + self->InStream(self, NULL); + } + else if ((base == BASE_BINARY) && + (trio_to_upper(self->current) == 'B')) + { + self->InStream(self, NULL); + } + } + } + else + return FALSE; + break; + default: + break; + } + } + + while (((width == NO_WIDTH) || (self->processed - count < width)) && + (! ((self->current == EOF) || isspace(self->current)))) + { + if (isascii(self->current)) + { + digit = internalDigitArray[self->current]; + /* Abort if digit is not allowed in the specified base */ + if ((digit == -1) || (digit >= base)) + break; + } +#if TRIO_FEATURE_QUOTE + else if (flags & FLAGS_QUOTE) + { + /* Compare with thousands separator */ + for (j = 0; internalThousandSeparator[j] && self->current; j++) + { + if (internalThousandSeparator[j] != self->current) + break; + + self->InStream(self, NULL); + } + if (internalThousandSeparator[j]) + break; /* Mismatch */ + else + continue; /* Match */ + } +#endif + else + break; + + number *= base; + number += digit; + gotNumber = TRUE; /* we need at least one digit */ + + self->InStream(self, NULL); + } + + /* Was anything read at all? */ + if (!gotNumber) + return FALSE; + + if (target) + *target = (isNegative) ? (trio_uintmax_t)(-((trio_intmax_t)number)) : number; + return TRUE; +} + +/************************************************************************* + * TrioReadChar + */ +TRIO_PRIVATE int +TrioReadChar +TRIO_ARGS4((self, target, flags, width), + trio_class_t *self, + char *target, + trio_flags_t flags, + int width) +{ + int i; + char ch; + trio_uintmax_t number; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + for (i = 0; + (self->current != EOF) && (i < width); + i++) + { + ch = (char)self->current; + self->InStream(self, NULL); + if ((flags & FLAGS_ALTERNATIVE) && (ch == CHAR_BACKSLASH)) + { + switch (self->current) + { + case '\\': ch = '\\'; break; + case 'a': ch = '\007'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + default: + if (isdigit(self->current)) + { + /* Read octal number */ + if (!TrioReadNumber(self, &number, 0, 3, BASE_OCTAL)) + return 0; + ch = (char)number; + } + else if (trio_to_upper(self->current) == 'X') + { + /* Read hexadecimal number */ + self->InStream(self, NULL); + if (!TrioReadNumber(self, &number, 0, 2, BASE_HEX)) + return 0; + ch = (char)number; + } + else + { + ch = (char)self->current; + } + break; + } + } + + if (target) + target[i] = ch; + } + return i + 1; +} + +/************************************************************************* + * TrioReadString + */ +TRIO_PRIVATE BOOLEAN_T +TrioReadString +TRIO_ARGS4((self, target, flags, width), + trio_class_t *self, + char *target, + trio_flags_t flags, + int width) +{ + int i; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + TrioSkipWhitespaces(self); + + /* + * Continue until end of string is reached, a whitespace is encountered, + * or width is exceeded + */ + for (i = 0; + ((width == NO_WIDTH) || (i < width)) && + (! ((self->current == EOF) || isspace(self->current))); + i++) + { + if (TrioReadChar(self, (target ? &target[i] : 0), flags, 1) == 0) + break; /* for */ + } + if (target) + target[i] = NIL; + return TRUE; +} + +/************************************************************************* + * TrioReadWideChar + */ +#if TRIO_FEATURE_WIDECHAR +TRIO_PRIVATE int +TrioReadWideChar +TRIO_ARGS4((self, target, flags, width), + trio_class_t *self, + trio_wchar_t *target, + trio_flags_t flags, + int width) +{ + int i; + int j; + int size; + int amount = 0; + trio_wchar_t wch; + char buffer[MB_LEN_MAX + 1]; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + for (i = 0; + (self->current != EOF) && (i < width); + i++) + { + if (isascii(self->current)) + { + if (TrioReadChar(self, buffer, flags, 1) == 0) + return 0; + buffer[1] = NIL; + } + else + { + /* + * Collect a multibyte character, by enlarging buffer until + * it contains a fully legal multibyte character, or the + * buffer is full. + */ + j = 0; + do + { + buffer[j++] = (char)self->current; + buffer[j] = NIL; + self->InStream(self, NULL); + } + while ((j < (int)sizeof(buffer)) && (mblen(buffer, (size_t)j) != j)); + } + if (target) + { + size = mbtowc(&wch, buffer, sizeof(buffer)); + if (size > 0) + target[i] = wch; + } + amount += size; + self->InStream(self, NULL); + } + return amount; +} +#endif /* TRIO_FEATURE_WIDECHAR */ + +/************************************************************************* + * TrioReadWideString + */ +#if TRIO_FEATURE_WIDECHAR +TRIO_PRIVATE BOOLEAN_T +TrioReadWideString +TRIO_ARGS4((self, target, flags, width), + trio_class_t *self, + trio_wchar_t *target, + trio_flags_t flags, + int width) +{ + int i; + int size; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + TrioSkipWhitespaces(self); + +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + /* Required by TrioReadWideChar */ + (void)mblen(NULL, 0); +#endif + + /* + * Continue until end of string is reached, a whitespace is encountered, + * or width is exceeded + */ + for (i = 0; + ((width == NO_WIDTH) || (i < width)) && + (! ((self->current == EOF) || isspace(self->current))); + ) + { + size = TrioReadWideChar(self, &target[i], flags, 1); + if (size == 0) + break; /* for */ + + i += size; + } + if (target) + target[i] = WCONST('\0'); + return TRUE; +} +#endif /* TRIO_FEATURE_WIDECHAR */ + +/************************************************************************* + * TrioReadGroup + * + * Reads non-empty character groups. + * + * FIXME: characterclass does not work with multibyte characters + */ +TRIO_PRIVATE BOOLEAN_T +TrioReadGroup +TRIO_ARGS5((self, target, characterclass, flags, width), + trio_class_t *self, + char *target, + int *characterclass, + trio_flags_t flags, + int width) +{ + int ch; + int i; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + ch = self->current; + for (i = 0; + ((width == NO_WIDTH) || (i < width)) && + (! ((ch == EOF) || + (((flags & FLAGS_EXCLUDE) != 0) ^ (characterclass[ch] == 0)))); + i++) + { + if (target) + target[i] = (char)ch; + self->InStream(self, &ch); + } + + if (i == 0) + return FALSE; + + /* Terminate the string if input saved */ + if (target) + target[i] = NIL; + return TRUE; +} + +/************************************************************************* + * TrioReadDouble + * + * FIXME: + * add long double + * handle base + */ +#if TRIO_FEATURE_FLOAT +TRIO_PRIVATE BOOLEAN_T +TrioReadDouble +TRIO_ARGS4((self, target, flags, width), + trio_class_t *self, + trio_pointer_t target, + trio_flags_t flags, + int width) +{ + int ch; + char doubleString[512]; + int offset = 0; + int start; +# if TRIO_FEATURE_QUOTE + int j; +# endif + BOOLEAN_T isHex = FALSE; + trio_long_double_t infinity; + + doubleString[0] = 0; + + if ((width == NO_WIDTH) || (width > (int)sizeof(doubleString) - 1)) + width = sizeof(doubleString) - 1; + + TrioSkipWhitespaces(self); + + /* + * Read entire double number from stream. trio_to_double requires + * a string as input, but InStream can be anything, so we have to + * collect all characters. + */ + ch = self->current; + if ((ch == '+') || (ch == '-')) + { + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + width--; + } + + start = offset; + switch (ch) + { + case 'n': + case 'N': + /* Not-a-number */ + if (offset != 0) + break; + /* FALLTHROUGH */ + case 'i': + case 'I': + /* Infinity */ + while (isalpha(ch) && (offset - start < width)) + { + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + } + doubleString[offset] = NIL; + + /* Case insensitive string comparison */ + if (trio_equal(&doubleString[start], INFINITE_UPPER) || + trio_equal(&doubleString[start], LONG_INFINITE_UPPER)) + { + infinity = ((start == 1) && (doubleString[0] == '-')) + ? trio_ninf() + : trio_pinf(); + if (flags & FLAGS_LONGDOUBLE) + { + *((trio_long_double_t *)target) = infinity; + } + else if (flags & FLAGS_LONG) + { + *((double *)target) = infinity; + } + else + { + *((float *)target) = infinity; + } + return TRUE; + } + if (trio_equal(doubleString, NAN_UPPER)) + { + /* NaN must not have a preceeding + nor - */ + if (flags & FLAGS_LONGDOUBLE) + { + *((trio_long_double_t *)target) = trio_nan(); + } + else if (flags & FLAGS_LONG) + { + *((double *)target) = trio_nan(); + } + else + { + *((float *)target) = trio_nan(); + } + return TRUE; + } + return FALSE; + + case '0': + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + if (trio_to_upper(ch) == 'X') + { + isHex = TRUE; + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + } + break; + + default: + break; + } + + while ((ch != EOF) && (offset - start < width)) + { + /* Integer part */ + if (isHex ? isxdigit(ch) : isdigit(ch)) + { + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + } +# if TRIO_FEATURE_QUOTE + else if (flags & FLAGS_QUOTE) + { + /* Compare with thousands separator */ + for (j = 0; internalThousandSeparator[j] && self->current; j++) + { + if (internalThousandSeparator[j] != self->current) + break; + + self->InStream(self, &ch); + } + if (internalThousandSeparator[j]) + break; /* Mismatch */ + else + continue; /* Match */ + } +# endif + else + break; /* while */ + } + if (ch == '.') + { + /* Decimal part */ + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + while ((isHex ? isxdigit(ch) : isdigit(ch)) && + (offset - start < width)) + { + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + } + } + if (isHex ? (trio_to_upper(ch) == 'P') : (trio_to_upper(ch) == 'E')) + { + /* Exponent */ + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + if ((ch == '+') || (ch == '-')) + { + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + } + while (isdigit(ch) && (offset - start < width)) + { + doubleString[offset++] = (char)ch; + self->InStream(self, &ch); + } + } + + if ((offset == start) || (*doubleString == NIL)) + return FALSE; + + doubleString[offset] = 0; + + if (flags & FLAGS_LONGDOUBLE) + { + *((trio_long_double_t *)target) = trio_to_long_double(doubleString, NULL); + } + else if (flags & FLAGS_LONG) + { + *((double *)target) = trio_to_double(doubleString, NULL); + } + else + { + *((float *)target) = trio_to_float(doubleString, NULL); + } + return TRUE; +} +#endif /* TRIO_FEATURE_FLOAT */ + +/************************************************************************* + * TrioReadPointer + */ +TRIO_PRIVATE BOOLEAN_T +TrioReadPointer +TRIO_ARGS3((self, target, flags), + trio_class_t *self, + trio_pointer_t *target, + trio_flags_t flags) +{ + trio_uintmax_t number; + char buffer[sizeof(internalNullString)]; + + flags |= (FLAGS_UNSIGNED | FLAGS_ALTERNATIVE | FLAGS_NILPADDING); + + if (TrioReadNumber(self, + &number, + flags, + POINTER_WIDTH, + BASE_HEX)) + { + if (target) + { +#if defined(TRIO_COMPILER_GCC) || defined(TRIO_COMPILER_MIPSPRO) + /* + * The strange assignment of number is a workaround for a compiler + * warning + */ + *target = &((char *)0)[number]; +#else + *target = (trio_pointer_t)number; +#endif + } + return TRUE; + } + else if (TrioReadString(self, + (flags & FLAGS_IGNORE) + ? NULL + : buffer, + 0, + sizeof(internalNullString) - 1)) + { + if (trio_equal_case(buffer, internalNullString)) + { + if (target) + *target = NULL; + return TRUE; + } + } + return FALSE; +} + +/************************************************************************* + * TrioScanProcess + */ +TRIO_PRIVATE int +TrioScanProcess +TRIO_ARGS3((data, format, parameters), + trio_class_t *data, + TRIO_CONST char *format, + trio_parameter_t *parameters) +{ + int status; + int assignment; + int ch; + int offset; /* Offset of format string */ + int i; /* Offset of current parameter */ + trio_flags_t flags; + int width; + int base; + trio_pointer_t pointer; + + /* Return on empty format string */ + if (format[0] == NIL) + return 0; + + status = 0; + assignment = 0; + i = 0; + offset = 0; + data->InStream(data, &ch); + + for (;;) + { + /* Skip the parameter entries */ + while (parameters[i].type == FORMAT_PARAMETER) + { + assert(i <= MAX_PARAMETERS); + i++; + } + + /* Compare non conversion-specifier part of format string */ + while (offset < parameters[i].beginOffset) + { + if ((CHAR_IDENTIFIER == format[offset]) && + (CHAR_IDENTIFIER == format[offset + 1])) + { + /* Two % in format matches one % in input stream */ + if (CHAR_IDENTIFIER == ch) + { + data->InStream(data, &ch); + offset += 2; + continue; /* while format chars left */ + } + else + { + status = TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + goto end; + } + } + else /* Not an % identifier */ + { + if (isspace((int)format[offset])) + { + /* Whitespaces may match any amount of whitespaces */ + ch = TrioSkipWhitespaces(data); + } + else if (ch == format[offset]) + { + data->InStream(data, &ch); + } + else + { + status = assignment; + goto end; + } + + offset++; + } + } + + if (parameters[i].type == FORMAT_SENTINEL) + break; + + if ((EOF == ch) && (parameters[i].type != FORMAT_COUNT)) + { + status = (assignment > 0) ? assignment : EOF; + goto end; + } + + flags = parameters[i].flags; + + /* Find width */ + width = parameters[i].width; + if (flags & FLAGS_WIDTH_PARAMETER) + { + /* Get width from parameter list */ + width = (int)parameters[width].data.number.as_signed; + } + + /* Find base */ + if (NO_BASE != parameters[i].baseSpecifier) + { + /* Base from specifier has priority */ + base = parameters[i].baseSpecifier; + } + else if (flags & FLAGS_BASE_PARAMETER) + { + /* Get base from parameter list */ + base = parameters[i].base; + base = (int)parameters[base].data.number.as_signed; + } + else + { + /* Use base from format string */ + base = parameters[i].base; + } + + switch (parameters[i].type) + { + case FORMAT_INT: + { + trio_uintmax_t number; + + if (0 == base) + base = BASE_DECIMAL; + + if (!TrioReadNumber(data, + &number, + flags, + width, + base)) + { + status = assignment; + goto end; + } + + if (!(flags & FLAGS_IGNORE)) + { + assignment++; + + pointer = parameters[i].data.pointer; +#if TRIO_FEATURE_SIZE_T || TRIO_FEATURE_SIZE_T_UPPER + if (flags & FLAGS_SIZE_T) + *(size_t *)pointer = (size_t)number; + else +#endif +#if TRIO_FEATURE_PTRDIFF_T + if (flags & FLAGS_PTRDIFF_T) + *(ptrdiff_t *)pointer = (ptrdiff_t)number; + else +#endif +#if TRIO_FEATURE_INTMAX_T + if (flags & FLAGS_INTMAX_T) + *(trio_intmax_t *)pointer = (trio_intmax_t)number; + else +#endif + if (flags & FLAGS_QUAD) + *(trio_ulonglong_t *)pointer = (trio_ulonglong_t)number; + else if (flags & FLAGS_LONG) + *(long int *)pointer = (long int)number; + else if (flags & FLAGS_SHORT) + *(short int *)pointer = (short int)number; + else + *(int *)pointer = (int)number; + } + } + break; /* FORMAT_INT */ + + case FORMAT_STRING: +#if TRIO_FEATURE_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + if (!TrioReadWideString(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.wstring, + flags, + width)) + { + status = assignment; + goto end; + } + } + else +#endif + { + if (!TrioReadString(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.string, + flags, + width)) + { + status = assignment; + goto end; + } + } + if (!(flags & FLAGS_IGNORE)) + assignment++; + break; /* FORMAT_STRING */ + +#if TRIO_FEATURE_FLOAT + case FORMAT_DOUBLE: + { + if (flags & FLAGS_IGNORE) + { + pointer = NULL; + } + else + { + pointer = (flags & FLAGS_LONGDOUBLE) + ? (trio_pointer_t)parameters[i].data.longdoublePointer + : (trio_pointer_t)parameters[i].data.doublePointer; + } + if (!TrioReadDouble(data, pointer, flags, width)) + { + status = assignment; + goto end; + } + if (!(flags & FLAGS_IGNORE)) + { + assignment++; + } + break; /* FORMAT_DOUBLE */ + } +#endif + + case FORMAT_GROUP: + { + int characterclass[MAX_CHARACTER_CLASS + 1]; + + /* Skip over modifiers */ + while (format[offset] != SPECIFIER_GROUP) + { + offset++; + } + /* Skip over group specifier */ + offset++; + + memset(characterclass, 0, sizeof(characterclass)); + status = TrioGetCharacterClass(format, + &offset, + &flags, + characterclass); + if (status < 0) + goto end; + + if (!TrioReadGroup(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.string, + characterclass, + flags, + parameters[i].width)) + { + status = assignment; + goto end; + } + if (!(flags & FLAGS_IGNORE)) + assignment++; + } + break; /* FORMAT_GROUP */ + + case FORMAT_COUNT: + pointer = parameters[i].data.pointer; + if (NULL != pointer) + { + int count = data->processed; + if (ch != EOF) + count--; /* a character is read, but is not consumed yet */ +#if TRIO_FEATURE_SIZE_T || TRIO_FEATURE_SIZE_T_UPPER + if (flags & FLAGS_SIZE_T) + *(size_t *)pointer = (size_t)count; + else +#endif +#if TRIO_FEATURE_PTRDIFF_T + if (flags & FLAGS_PTRDIFF_T) + *(ptrdiff_t *)pointer = (ptrdiff_t)count; + else +#endif +#if TRIO_FEATURE_INTMAX_T + if (flags & FLAGS_INTMAX_T) + *(trio_intmax_t *)pointer = (trio_intmax_t)count; + else +#endif + if (flags & FLAGS_QUAD) + { + *(trio_ulonglong_t *)pointer = (trio_ulonglong_t)count; + } + else if (flags & FLAGS_LONG) + { + *(long int *)pointer = (long int)count; + } + else if (flags & FLAGS_SHORT) + { + *(short int *)pointer = (short int)count; + } + else + { + *(int *)pointer = (int)count; + } + } + break; /* FORMAT_COUNT */ + + case FORMAT_CHAR: +#if TRIO_FEATURE_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + if (TrioReadWideChar(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.wstring, + flags, + (width == NO_WIDTH) ? 1 : width) == 0) + { + status = assignment; + goto end; + } + } + else +#endif + { + if (TrioReadChar(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.string, + flags, + (width == NO_WIDTH) ? 1 : width) == 0) + { + status = assignment; + goto end; + } + } + if (!(flags & FLAGS_IGNORE)) + assignment++; + break; /* FORMAT_CHAR */ + + case FORMAT_POINTER: + if (!TrioReadPointer(data, + (flags & FLAGS_IGNORE) + ? NULL + : (trio_pointer_t *)parameters[i].data.pointer, + flags)) + { + status = assignment; + goto end; + } + if (!(flags & FLAGS_IGNORE)) + assignment++; + break; /* FORMAT_POINTER */ + + case FORMAT_PARAMETER: + break; /* FORMAT_PARAMETER */ + + default: + status = TRIO_ERROR_RETURN(TRIO_EINVAL, offset); + goto end; + } + + ch = data->current; + offset = parameters[i].endOffset; + i++; + } + + status = assignment; + end: + if (data->UndoStream) + data->UndoStream(data); + return status; +} + +/************************************************************************* + * TrioScan + */ +TRIO_PRIVATE int +TrioScan +TRIO_ARGS8((source, sourceSize, InStream, UndoStream, format, arglist, argfunc, argarray), + trio_pointer_t source, + size_t sourceSize, + void (*InStream) TRIO_PROTO((trio_class_t *, int *)), + void (*UndoStream) TRIO_PROTO((trio_class_t *)), + TRIO_CONST char *format, + va_list arglist, + trio_argfunc_t argfunc, + trio_pointer_t *argarray) +{ + int status; + trio_parameter_t parameters[MAX_PARAMETERS]; + trio_class_t data; + + assert(VALID(InStream)); + assert(VALID(format)); + + memset(&data, 0, sizeof(data)); + data.InStream = InStream; + data.UndoStream = UndoStream; + data.location = (trio_pointer_t)source; + data.max = sourceSize; + data.error = 0; + +#if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +#endif + + status = TrioParse(TYPE_SCAN, format, parameters, arglist, argfunc, argarray); + if (status < 0) + return status; + + status = TrioScanProcess(&data, format, parameters); + if (data.error != 0) + { + status = data.error; + } + return status; +} + +/************************************************************************* + * TrioInStreamFile + */ +#if TRIO_FEATURE_FILE || TRIO_FEATURE_STDIO +TRIO_PRIVATE void +TrioInStreamFile +TRIO_ARGS2((self, intPointer), + trio_class_t *self, + int *intPointer) +{ + FILE *file = (FILE *)self->location; + + assert(VALID(self)); + assert(VALID(file)); + + self->actually.cached = 0; + + /* The initial value of self->current is zero */ + if (self->current == EOF) + { + self->error = (ferror(file)) + ? TRIO_ERROR_RETURN(TRIO_ERRNO, 0) + : TRIO_ERROR_RETURN(TRIO_EOF, 0); + } + else + { + self->processed++; + self->actually.cached++; + } + + self->current = fgetc(file); + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} +#endif /* TRIO_FEATURE_FILE || TRIO_FEATURE_STDIO */ + +/************************************************************************* + * TrioUndoStreamFile + */ +#if TRIO_FEATURE_FILE || TRIO_FEATURE_STDIO +TRIO_PRIVATE void +TrioUndoStreamFile +TRIO_ARGS1((self), + trio_class_t *self) +{ + FILE *file = (FILE *)self->location; + + assert(VALID(self)); + assert(VALID(file)); + + if (self->actually.cached > 0) + { + assert(self->actually.cached == 1); + + self->current = ungetc(self->current, file); + self->actually.cached = 0; + } +} +#endif /* TRIO_FEATURE_FILE || TRIO_FEATURE_STDIO */ + +/************************************************************************* + * TrioInStreamFileDescriptor + */ +#if TRIO_FEATURE_FD +TRIO_PRIVATE void +TrioInStreamFileDescriptor +TRIO_ARGS2((self, intPointer), + trio_class_t *self, + int *intPointer) +{ + int fd = *((int *)self->location); + int size; + unsigned char input; + + assert(VALID(self)); + + self->actually.cached = 0; + + size = read(fd, &input, sizeof(char)); + if (size == -1) + { + self->error = TRIO_ERROR_RETURN(TRIO_ERRNO, 0); + self->current = EOF; + } + else + { + self->current = (size == 0) ? EOF : input; + } + if (self->current != EOF) + { + self->actually.cached++; + self->processed++; + } + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} +#endif /* TRIO_FEATURE_FD */ + +/************************************************************************* + * TrioInStreamCustom + */ +#if TRIO_FEATURE_CLOSURE +TRIO_PRIVATE void +TrioInStreamCustom +TRIO_ARGS2((self, intPointer), + trio_class_t *self, + int *intPointer) +{ + trio_custom_t *data; + + assert(VALID(self)); + assert(VALID(self->location)); + + self->actually.cached = 0; + + data = (trio_custom_t *)self->location; + + self->current = (data->stream.in == NULL) + ? NIL + : (data->stream.in)(data->closure); + + if (self->current == NIL) + { + self->current = EOF; + } + else + { + self->processed++; + self->actually.cached++; + } + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} +#endif /* TRIO_FEATURE_CLOSURE */ + +/************************************************************************* + * TrioInStreamString + */ +TRIO_PRIVATE void +TrioInStreamString +TRIO_ARGS2((self, intPointer), + trio_class_t *self, + int *intPointer) +{ + unsigned char **buffer; + + assert(VALID(self)); + assert(VALID(self->location)); + + self->actually.cached = 0; + + buffer = (unsigned char **)self->location; + self->current = (*buffer)[0]; + if (self->current == NIL) + { + self->current = EOF; + } + else + { + (*buffer)++; + self->processed++; + self->actually.cached++; + } + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} + +/************************************************************************* + * + * Formatted scanning functions + * + ************************************************************************/ + +#if defined(TRIO_DOCUMENTATION) +# include "doc/doc_scanf.h" +#endif +/** @addtogroup Scanf + @{ +*/ + +/************************************************************************* + * scanf + */ + +/** + Scan characters from standard input stream. + + @param format Formatting string. + @param ... Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_STDIO +TRIO_PUBLIC int +trio_scanf +TRIO_VARGS2((format, va_alist), + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioScan((trio_pointer_t)stdin, 0, + TrioInStreamFile, + TrioUndoStreamFile, + format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_STDIO */ + +/** + Scan characters from standard input stream. + + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_STDIO +TRIO_PUBLIC int +trio_vscanf +TRIO_ARGS2((format, args), + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioScan((trio_pointer_t)stdin, 0, + TrioInStreamFile, + TrioUndoStreamFile, + format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_STDIO */ + +/** + Scan characters from standard input stream. + + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_STDIO +TRIO_PUBLIC int +trio_scanfv +TRIO_ARGS2((format, args), + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + + assert(VALID(format)); + + return TrioScan((trio_pointer_t)stdin, 0, + TrioInStreamFile, + TrioUndoStreamFile, + format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_STDIO */ + +/************************************************************************* + * fscanf + */ + +/** + Scan characters from file. + + @param file File pointer. + @param format Formatting string. + @param ... Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_FILE +TRIO_PUBLIC int +trio_fscanf +TRIO_VARGS3((file, format, va_alist), + FILE *file, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(file)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioScan((trio_pointer_t)file, 0, + TrioInStreamFile, + TrioUndoStreamFile, + format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_FILE */ + +/** + Scan characters from file. + + @param file File pointer. + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_FILE +TRIO_PUBLIC int +trio_vfscanf +TRIO_ARGS3((file, format, args), + FILE *file, + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(file)); + assert(VALID(format)); + + return TrioScan((trio_pointer_t)file, 0, + TrioInStreamFile, + TrioUndoStreamFile, + format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_FILE */ + +/** + Scan characters from file. + + @param file File pointer. + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_FILE +TRIO_PUBLIC int +trio_fscanfv +TRIO_ARGS3((file, format, args), + FILE *file, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + + assert(VALID(file)); + assert(VALID(format)); + + return TrioScan((trio_pointer_t)file, 0, + TrioInStreamFile, + TrioUndoStreamFile, + format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_FILE */ + +/************************************************************************* + * dscanf + */ + +/** + Scan characters from file descriptor. + + @param fd File descriptor. + @param format Formatting string. + @param ... Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_FD +TRIO_PUBLIC int +trio_dscanf +TRIO_VARGS3((fd, format, va_alist), + int fd, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioScan((trio_pointer_t)&fd, 0, + TrioInStreamFileDescriptor, + NULL, + format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_FD */ + +/** + Scan characters from file descriptor. + + @param fd File descriptor. + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_FD +TRIO_PUBLIC int +trio_vdscanf +TRIO_ARGS3((fd, format, args), + int fd, + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioScan((trio_pointer_t)&fd, 0, + TrioInStreamFileDescriptor, + NULL, + format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_FD */ + +/** + Scan characters from file descriptor. + + @param fd File descriptor. + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +#if TRIO_FEATURE_FD +TRIO_PUBLIC int +trio_dscanfv +TRIO_ARGS3((fd, format, args), + int fd, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + + assert(VALID(format)); + + return TrioScan((trio_pointer_t)&fd, 0, + TrioInStreamFileDescriptor, + NULL, + format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_FD */ + +/************************************************************************* + * cscanf + */ +#if TRIO_FEATURE_CLOSURE +TRIO_PUBLIC int +trio_cscanf +TRIO_VARGS4((stream, closure, format, va_alist), + trio_instream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + data.stream.in = stream; + data.closure = closure; + status = TrioScan(&data, 0, TrioInStreamCustom, NULL, format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} +#endif /* TRIO_FEATURE_CLOSURE */ + +#if TRIO_FEATURE_CLOSURE +TRIO_PUBLIC int +trio_vcscanf +TRIO_ARGS4((stream, closure, format, args), + trio_instream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + va_list args) +{ + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + + data.stream.in = stream; + data.closure = closure; + return TrioScan(&data, 0, TrioInStreamCustom, NULL, format, args, NULL, NULL); +} +#endif /* TRIO_FEATURE_CLOSURE */ + +#if TRIO_FEATURE_CLOSURE +TRIO_PUBLIC int +trio_cscanfv +TRIO_ARGS4((stream, closure, format, args), + trio_instream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + + data.stream.in = stream; + data.closure = closure; + return TrioScan(&data, 0, TrioInStreamCustom, NULL, format, + unused, TrioArrayGetter, args); +} +#endif /* TRIO_FEATURE_CLOSURE */ + +#if TRIO_FEATURE_CLOSURE && TRIO_FEATURE_ARGFUNC +TRIO_PUBLIC int +trio_cscanff +TRIO_ARGS5((stream, closure, format, argfunc, context), + trio_instream_t stream, + trio_pointer_t closure, + TRIO_CONST char *format, + trio_argfunc_t argfunc, + trio_pointer_t context) +{ + static va_list unused; + trio_custom_t data; + + assert(VALID(stream)); + assert(VALID(format)); + assert(VALID(argfunc)); + + data.stream.in = stream; + data.closure = closure; + return TrioScan(&data, 0, TrioInStreamCustom, NULL, format, + unused, argfunc, (trio_pointer_t *)context); +} +#endif /* TRIO_FEATURE_CLOSURE && TRIO_FEATURE_ARGFUNC */ + +/************************************************************************* + * sscanf + */ + +/** + Scan characters from string. + + @param buffer Input string. + @param format Formatting string. + @param ... Arguments. + @return Number of scanned characters. + */ +TRIO_PUBLIC int +trio_sscanf +TRIO_VARGS3((buffer, format, va_alist), + TRIO_CONST char *buffer, + TRIO_CONST char *format, + TRIO_VA_DECL) +{ + int status; + va_list args; + + assert(VALID(buffer)); + assert(VALID(format)); + + TRIO_VA_START(args, format); + status = TrioScan((trio_pointer_t)&buffer, 0, + TrioInStreamString, + NULL, + format, args, NULL, NULL); + TRIO_VA_END(args); + return status; +} + +/** + Scan characters from string. + + @param buffer Input string. + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +TRIO_PUBLIC int +trio_vsscanf +TRIO_ARGS3((buffer, format, args), + TRIO_CONST char *buffer, + TRIO_CONST char *format, + va_list args) +{ + assert(VALID(buffer)); + assert(VALID(format)); + + return TrioScan((trio_pointer_t)&buffer, 0, + TrioInStreamString, + NULL, + format, args, NULL, NULL); +} + +/** + Scan characters from string. + + @param buffer Input string. + @param format Formatting string. + @param args Arguments. + @return Number of scanned characters. + */ +TRIO_PUBLIC int +trio_sscanfv +TRIO_ARGS3((buffer, format, args), + TRIO_CONST char *buffer, + TRIO_CONST char *format, + trio_pointer_t *args) +{ + static va_list unused; + + assert(VALID(buffer)); + assert(VALID(format)); + + return TrioScan((trio_pointer_t)&buffer, 0, + TrioInStreamString, + NULL, + format, + unused, TrioArrayGetter, args); +} + +#endif /* TRIO_FEATURE_SCANF */ + +/** @} End of Scanf documentation module */ + +/************************************************************************* + * trio_strerror + */ +TRIO_PUBLIC TRIO_CONST char * +trio_strerror +TRIO_ARGS1((errorcode), + int errorcode) +{ +#if TRIO_FEATURE_STRERR + /* Textual versions of the error codes */ + switch (TRIO_ERROR_CODE(errorcode)) + { + case TRIO_EOF: + return "End of file"; + case TRIO_EINVAL: + return "Invalid argument"; + case TRIO_ETOOMANY: + return "Too many arguments"; + case TRIO_EDBLREF: + return "Double reference"; + case TRIO_EGAP: + return "Reference gap"; + case TRIO_ENOMEM: + return "Out of memory"; + case TRIO_ERANGE: + return "Invalid range"; + case TRIO_ECUSTOM: + return "Custom error"; + default: + return "Unknown"; + } +#else + return "Unknown"; +#endif +} diff --git a/psx/mednadisc/trio/trio.h b/psx/mednadisc/trio/trio.h new file mode 100644 index 0000000000..6bb74b1d4c --- /dev/null +++ b/psx/mednadisc/trio/trio.h @@ -0,0 +1,270 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************* + * + * http://ctrio.sourceforge.net/ + * + ************************************************************************/ + +#ifndef TRIO_TRIO_H +#define TRIO_TRIO_H + +#if !defined(WITHOUT_TRIO) + +/* + * Use autoconf defines if present. Packages using trio must define + * HAVE_CONFIG_H as a compiler option themselves. + */ +#if defined(HAVE_CONFIG_H) +# include +#endif + +#include "triop.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Error codes. + * + * Remember to add a textual description to trio_strerror. + */ +enum { + TRIO_EOF = 1, + TRIO_EINVAL = 2, + TRIO_ETOOMANY = 3, + TRIO_EDBLREF = 4, + TRIO_EGAP = 5, + TRIO_ENOMEM = 6, + TRIO_ERANGE = 7, + TRIO_ERRNO = 8, + TRIO_ECUSTOM = 9 +}; + +/* Error macros */ +#define TRIO_ERROR_CODE(x) ((-(x)) & 0x00FF) +#define TRIO_ERROR_POSITION(x) ((-(x)) >> 8) +#define TRIO_ERROR_NAME(x) trio_strerror(x) + +/* Argument function types */ +enum { + TRIO_TYPE_POINTER = 1, + TRIO_TYPE_CHAR = 2, + TRIO_TYPE_SHORT = 3, + TRIO_TYPE_INT = 4, + TRIO_TYPE_LONG = 5, + TRIO_TYPE_ULONGLONG = 6, + TRIO_TYPE_UINTMAX = 7, + TRIO_TYPE_PTRDIFF = 8, + TRIO_TYPE_SIZE = 9, + TRIO_TYPE_PCHAR = 10, + TRIO_TYPE_PWCHAR = 11, + TRIO_TYPE_FLOAT = 12, + TRIO_TYPE_DOUBLE = 13, + TRIO_TYPE_LONGDOUBLE = 14 +}; + +typedef trio_pointer_t (*trio_argfunc_t) TRIO_PROTO((trio_pointer_t, int, int)); +typedef int (*trio_outstream_t) TRIO_PROTO((trio_pointer_t, int)); +typedef int (*trio_instream_t) TRIO_PROTO((trio_pointer_t)); + +TRIO_CONST char *trio_strerror TRIO_PROTO((int)); + +/************************************************************************* + * Print Functions + */ + +#if defined(TRIO_COMPILER_GCC) && !TRIO_EXTENSION +# define TRIO_PROTO_PRINTF(x,a) TRIO_PROTO(x) __attribute__ ((format (gnu_printf, a, a+1))) +# define TRIO_PROTO_SCANF(x,a) TRIO_PROTO(x) __attribute__ ((format (gnu_scanf, a, a+1))) +#else +# define TRIO_PROTO_PRINTF(x,a) TRIO_PROTO(x) +# define TRIO_PROTO_SCANF(x,a) TRIO_PROTO(x) +#endif + +int trio_printf TRIO_PROTO_PRINTF((TRIO_CONST char *format, ...), 1); +int trio_vprintf TRIO_PROTO((TRIO_CONST char *format, va_list args)); +int trio_printfv TRIO_PROTO((TRIO_CONST char *format, trio_pointer_t *args)); + +int trio_fprintf TRIO_PROTO_PRINTF((FILE *file, TRIO_CONST char *format, ...), 2); +int trio_vfprintf TRIO_PROTO((FILE *file, TRIO_CONST char *format, va_list args)); +int trio_fprintfv TRIO_PROTO((FILE *file, TRIO_CONST char *format, trio_pointer_t *args)); + +int trio_dprintf TRIO_PROTO_PRINTF((int fd, TRIO_CONST char *format, ...), 2); +int trio_vdprintf TRIO_PROTO((int fd, TRIO_CONST char *format, va_list args)); +int trio_dprintfv TRIO_PROTO((int fd, TRIO_CONST char *format, trio_pointer_t *args)); + +int trio_cprintf TRIO_PROTO_PRINTF((trio_outstream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, ...), + 3); +int trio_vcprintf TRIO_PROTO((trio_outstream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, va_list args)); +int trio_cprintfv TRIO_PROTO((trio_outstream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, trio_pointer_t *args)); +int trio_cprintff TRIO_PROTO((trio_outstream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, + trio_argfunc_t func, trio_pointer_t context)); + +int trio_sprintf TRIO_PROTO_PRINTF((char *buffer, TRIO_CONST char *format, ...), 2); +int trio_vsprintf TRIO_PROTO((char *buffer, TRIO_CONST char *format, va_list args)); +int trio_sprintfv TRIO_PROTO((char *buffer, TRIO_CONST char *format, trio_pointer_t *args)); + +int trio_snprintf TRIO_PROTO_PRINTF((char *buffer, size_t max, TRIO_CONST char *format, ...), 3); +int trio_vsnprintf TRIO_PROTO((char *buffer, size_t bufferSize, TRIO_CONST char *format, + va_list args)); +int trio_snprintfv TRIO_PROTO((char *buffer, size_t bufferSize, TRIO_CONST char *format, + trio_pointer_t *args)); + +int trio_snprintfcat TRIO_PROTO_PRINTF((char *buffer, size_t max, TRIO_CONST char *format, ...), 3); +int trio_vsnprintfcat TRIO_PROTO((char *buffer, size_t bufferSize, TRIO_CONST char *format, + va_list args)); + +#if defined(TRIO_DEPRECATED) +char *trio_aprintf TRIO_PROTO_PRINTF((TRIO_CONST char *format, ...), 1); +char *trio_vaprintf TRIO_PROTO((TRIO_CONST char *format, va_list args)); +#endif + +int trio_asprintf TRIO_PROTO_PRINTF((char **ret, TRIO_CONST char *format, ...), 2); +int trio_vasprintf TRIO_PROTO((char **ret, TRIO_CONST char *format, va_list args)); +int trio_asprintfv TRIO_PROTO((char **result, TRIO_CONST char *format, trio_pointer_t * args)); + +/************************************************************************* + * Scan Functions + */ +int trio_scanf TRIO_PROTO_SCANF((TRIO_CONST char *format, ...), 1); +int trio_vscanf TRIO_PROTO((TRIO_CONST char *format, va_list args)); +int trio_scanfv TRIO_PROTO((TRIO_CONST char *format, void **args)); + +int trio_fscanf TRIO_PROTO_SCANF((FILE *file, TRIO_CONST char *format, ...), 2); +int trio_vfscanf TRIO_PROTO((FILE *file, TRIO_CONST char *format, va_list args)); +int trio_fscanfv TRIO_PROTO((FILE *file, TRIO_CONST char *format, void **args)); + +int trio_dscanf TRIO_PROTO_SCANF((int fd, TRIO_CONST char *format, ...), 2); +int trio_vdscanf TRIO_PROTO((int fd, TRIO_CONST char *format, va_list args)); +int trio_dscanfv TRIO_PROTO((int fd, TRIO_CONST char *format, void **args)); + +int trio_cscanf TRIO_PROTO_SCANF((trio_instream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, ...), + 3); +int trio_vcscanf TRIO_PROTO((trio_instream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, va_list args)); +int trio_cscanfv TRIO_PROTO((trio_instream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, void **args)); +int trio_cscanff TRIO_PROTO((trio_instream_t stream, trio_pointer_t closure, + TRIO_CONST char *format, + trio_argfunc_t func, trio_pointer_t context)); + +int trio_sscanf TRIO_PROTO_SCANF((TRIO_CONST char *buffer, TRIO_CONST char *format, ...), 2); +int trio_vsscanf TRIO_PROTO((TRIO_CONST char *buffer, TRIO_CONST char *format, va_list args)); +int trio_sscanfv TRIO_PROTO((TRIO_CONST char *buffer, TRIO_CONST char *format, void **args)); + +/************************************************************************* + * Locale Functions + */ +void trio_locale_set_decimal_point TRIO_PROTO((char *decimalPoint)); +void trio_locale_set_thousand_separator TRIO_PROTO((char *thousandSeparator)); +void trio_locale_set_grouping TRIO_PROTO((char *grouping)); + +/************************************************************************* + * Renaming + */ +#ifdef TRIO_REPLACE_STDIO +/* Replace the functions */ +#ifndef HAVE_PRINTF +# undef printf +# define printf trio_printf +#endif +#ifndef HAVE_VPRINTF +# undef vprintf +# define vprintf trio_vprintf +#endif +#ifndef HAVE_FPRINTF +# undef fprintf +# define fprintf trio_fprintf +#endif +#ifndef HAVE_VFPRINTF +# undef vfprintf +# define vfprintf trio_vfprintf +#endif +#ifndef HAVE_SPRINTF +# undef sprintf +# define sprintf trio_sprintf +#endif +#ifndef HAVE_VSPRINTF +# undef vsprintf +# define vsprintf trio_vsprintf +#endif +#ifndef HAVE_SNPRINTF +# undef snprintf +# define snprintf trio_snprintf +#endif +#ifndef HAVE_VSNPRINTF +# undef vsnprintf +# define vsnprintf trio_vsnprintf +#endif +#ifndef HAVE_SCANF +# undef scanf +# define scanf trio_scanf +#endif +#ifndef HAVE_VSCANF +# undef vscanf +# define vscanf trio_vscanf +#endif +#ifndef HAVE_FSCANF +# undef fscanf +# define fscanf trio_fscanf +#endif +#ifndef HAVE_VFSCANF +# undef vfscanf +# define vfscanf trio_vfscanf +#endif +#ifndef HAVE_SSCANF +# undef sscanf +# define sscanf trio_sscanf +#endif +#ifndef HAVE_VSSCANF +# undef vsscanf +# define vsscanf trio_vsscanf +#endif +/* These aren't stdio functions, but we make them look similar */ +#undef dprintf +#define dprintf trio_dprintf +#undef vdprintf +#define vdprintf trio_vdprintf +#undef aprintf +#define aprintf trio_aprintf +#undef vaprintf +#define vaprintf trio_vaprintf +#undef asprintf +#define asprintf trio_asprintf +#undef vasprintf +#define vasprintf trio_vasprintf +#undef dscanf +#define dscanf trio_dscanf +#undef vdscanf +#define vdscanf trio_vdscanf +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WITHOUT_TRIO */ + +#endif /* TRIO_TRIO_H */ diff --git a/psx/mednadisc/trio/triodef.h b/psx/mednadisc/trio/triodef.h new file mode 100644 index 0000000000..7a77b4a8b9 --- /dev/null +++ b/psx/mednadisc/trio/triodef.h @@ -0,0 +1,375 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2001 Bjorn Reese + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +#ifndef TRIO_TRIODEF_H +#define TRIO_TRIODEF_H + +/************************************************************************* + * Compiler support detection + */ + +#if defined(__GNUC__) +# define TRIO_COMPILER_GCC +#endif + +#if defined(__SUNPRO_CC) +# define TRIO_COMPILER_SUNPRO __SUNPRO_CC +#else +# if defined(__SUNPRO_C) +# define TRIO_COMPILER_SUNPRO __SUNPRO_C +# endif +#endif + +#if defined(__xlC__) || defined(__IBMC__) || defined(__IBMCPP__) +# define TRIO_COMPILER_XLC +#else +# if defined(_AIX) && !defined(__GNUC__) +# define TRIO_COMPILER_XLC /* Workaround for old xlc */ +# endif +#endif + +#if defined(__DECC) || defined(__DECCXX) +# define TRIO_COMPILER_DECC +#else +# if defined(__osf__) && defined(__LANGUAGE_C__) && !defined(__GNUC__) +# define TRIO_COMPILER_DECC /* Workaround for old DEC C compilers */ +# endif +#endif + +#if defined(__HP_aCC) || defined(__HP_cc) +# define TRIO_COMPILER_HP +#endif + +#if defined(sgi) || defined(__sgi) +# define TRIO_COMPILER_MIPSPRO +#endif + +#if defined(_MSC_VER) +# define TRIO_COMPILER_MSVC +#endif + +#if defined(__BORLANDC__) +# define TRIO_COMPILER_BCB +#endif + +/************************************************************************* + * Platform support detection + */ + +#if defined(VMS) || defined(__VMS) +# define TRIO_PLATFORM_VMS +#endif + +#if defined(unix) || defined(__unix) || defined(__unix__) +# define TRIO_PLATFORM_UNIX +#endif + +#if defined(TRIO_COMPILER_XLC) || defined(_AIX) +# define TRIO_PLATFORM_UNIX +#endif + +#if defined(TRIO_COMPILER_DECC) || defined(__osf___) +# if !defined(TRIO_PLATFORM_VMS) +# define TRIO_PLATFORM_UNIX +# endif +#endif + +#if defined(__NetBSD__) +# define TRIO_PLATFORM_UNIX +#endif + +#if defined(__Lynx__) +# define TRIO_PLATFORM_UNIX +# define TRIO_PLATFORM_LYNX +#endif + +#if defined(__APPLE__) && defined(__MACH__) +# define TRIO_PLATFORM_UNIX +#endif + +#if defined(__QNX__) +# define TRIO_PLATFORM_UNIX +# define TRIO_PLATFORM_QNX +#endif + +#if defined(__CYGWIN__) +# define TRIO_PLATFORM_UNIX +#endif + +#if defined(AMIGA) && defined(TRIO_COMPILER_GCC) +# define TRIO_PLATFORM_UNIX +#endif + +#if defined(TRIO_COMPILER_MSVC) || defined(WIN32) || defined(_WIN32) +# define TRIO_PLATFORM_WIN32 +#endif + +#if defined(_WIN32_WCE) +# define TRIO_PLATFORM_WINCE +#endif + +#if defined(mpeix) || defined(__mpexl) +# define TRIO_PLATFORM_MPEIX +#endif + +#if defined(_AIX) +# define TRIO_PLATFORM_AIX +#endif + +#if defined(__hpux) +# define TRIO_PLATFORM_HPUX +#endif + +#if defined(sun) || defined(__sun__) +# if defined(__SVR4) || defined(__svr4__) +# define TRIO_PLATFORM_SOLARIS +# else +# define TRIO_PLATFORM_SUNOS +# endif +#endif + +#if defined(__powerpc) || defined(__powerpc__) || defined(_ARCH_PPC) +# define TRIO_CPU_POWERPC +#endif + +#if defined(__sparc) || defined(__sparc__) +# define TRIO_CPU_SPARC +#endif + +#if defined(__s390x__) || defined(__zarch__) || defined(__SYSC_ZARCH__) +# define TRIO_CPU_SYSTEMZ +#endif + +/************************************************************************* + * Standards support detection + */ + +#if defined(__STDC__) \ + || defined(_MSC_EXTENSIONS) \ + || defined(TRIO_COMPILER_BCB) +# define PREDEF_STANDARD_C89 +#endif +#if defined(__STDC_VERSION__) +# define PREDEF_STANDARD_C90 +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199409L) +# define PREDEF_STANDARD_C94 +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define PREDEF_STANDARD_C99 +#endif +#if defined(TRIO_COMPILER_SUNPRO) && (TRIO_COMPILER_SUNPRO >= 0x420) +# if !defined(PREDEF_STANDARD_C94) +# define PREDEF_STANDARD_C94 +# endif +#endif + +#if defined(__cplusplus) +# define PREDEF_STANDARD_CXX +#endif +#if defined(__cplusplus) && (__cplusplus >= 199711L) +# define PREDEF_STANDARD_CXX89 +#endif + +#if defined(TRIO_PLATFORM_UNIX) +# include +#endif + +#if defined(_POSIX_VERSION) +# define PREDEF_STANDARD_POSIX _POSIX_VERSION +# if (_POSIX_VERSION >= 199506L) +# define PREDEF_STANDARD_POSIX_1996 +# endif +#endif + +#if (_XOPEN_VERSION - 0 >= 3) || defined(_XOPEN_XPG3) +# define PREDEF_STANDARD_XPG3 +#endif +#if (_XOPEN_VERSION - 0 >= 4) || defined(_XOPEN_XPG4) +# define PREDEF_STANDARD_XPG4 +#endif +#if (_XOPEN_VERSION - 0 > 4) \ + || (defined(_XOPEN_UNIX) && (_XOPEN_VERSION - 0 == 4)) +# define PREDEF_STANDARD_UNIX95 +#endif +#if (_XOPEN_VERSION - 0 >= 500) +# define PREDEF_STANDARD_UNIX98 +#endif +#if (_XOPEN_VERSION - 0 >= 600) +# define PREDEF_STANDARD_UNIX03 +#endif + +/************************************************************************* + * Generic defines + */ + +#if !defined(TRIO_PUBLIC) +/* Based on http://gcc.gnu.org/wiki/Visibility */ +# if defined(TRIO_PLATFORM_WIN32) || defined (__CYGWIN__) +# if defined(BUILDING_DLL) +# if defined(TRIO_COMPILER_GCC) +# define TRIO_PUBLIC __attribute__ ((dllexport)) +# else +# define TRIO_PUBLIC __declspec(dllexport) +# endif +# else +# if defined(TRIO_COMPILER_GCC) +# define TRIO_PUBLIC __attribute__ ((dllimport)) +# else +# define TRIO_PUBLIC __declspec(dllimport) +# endif +# endif +# else +# if defined(TRIO_COMPILER_GCC) && __GNUC__ >= 4 +# define TRIO_PUBLIC __attribute__ ((visibility ("default"))) +# define TRIO_PRIVATE __attribute__ ((visibility ("hidden"))) +# else +# define TRIO_PUBLIC +# endif +# endif +#endif +#if !defined(TRIO_PRIVATE) +# define TRIO_PRIVATE static +#endif + +#if !(defined(PREDEF_STANDARD_C89) || defined(PREDEF_STANDARD_CXX)) +# define TRIO_COMPILER_ANCIENT +#endif + +#if defined(TRIO_COMPILER_ANCIENT) +# define TRIO_CONST +# define TRIO_VOLATILE +# define TRIO_SIGNED +typedef double trio_long_double_t; +typedef char * trio_pointer_t; +# define TRIO_SUFFIX_LONG(x) x +# define TRIO_PROTO(x) () +# define TRIO_NOARGS +# define TRIO_ARGS1(list,a1) list a1; +# define TRIO_ARGS2(list,a1,a2) list a1; a2; +# define TRIO_ARGS3(list,a1,a2,a3) list a1; a2; a3; +# define TRIO_ARGS4(list,a1,a2,a3,a4) list a1; a2; a3; a4; +# define TRIO_ARGS5(list,a1,a2,a3,a4,a5) list a1; a2; a3; a4; a5; +# define TRIO_ARGS6(list,a1,a2,a3,a4,a5,a6) list a1; a2; a3; a4; a5; a6; +# define TRIO_ARGS7(list,a1,a2,a3,a4,a5,a6,a7) list a1; a2; a3; a4; a5; a6; a7; +# define TRIO_ARGS8(list,a1,a2,a3,a4,a5,a6,a7,a8) list a1; a2; a3; a4; a5; a6; a7; a8; +# define TRIO_VARGS2(list,a1,a2) list a1; a2 +# define TRIO_VARGS3(list,a1,a2,a3) list a1; a2; a3 +# define TRIO_VARGS4(list,a1,a2,a3,a4) list a1; a2; a3; a4 +# define TRIO_VARGS5(list,a1,a2,a3,a4,a5) list a1; a2; a3; a4; a5 +# define TRIO_VA_DECL va_dcl +# define TRIO_VA_START(x,y) va_start(x) +# define TRIO_VA_END(x) va_end(x) +#else /* ANSI C */ +# define TRIO_CONST const +# define TRIO_VOLATILE volatile +# define TRIO_SIGNED signed +typedef long double trio_long_double_t; +typedef void * trio_pointer_t; +# define TRIO_SUFFIX_LONG(x) x ## L +# define TRIO_PROTO(x) x +# define TRIO_NOARGS void +# define TRIO_ARGS1(list,a1) (a1) +# define TRIO_ARGS2(list,a1,a2) (a1,a2) +# define TRIO_ARGS3(list,a1,a2,a3) (a1,a2,a3) +# define TRIO_ARGS4(list,a1,a2,a3,a4) (a1,a2,a3,a4) +# define TRIO_ARGS5(list,a1,a2,a3,a4,a5) (a1,a2,a3,a4,a5) +# define TRIO_ARGS6(list,a1,a2,a3,a4,a5,a6) (a1,a2,a3,a4,a5,a6) +# define TRIO_ARGS7(list,a1,a2,a3,a4,a5,a6,a7) (a1,a2,a3,a4,a5,a6,a7) +# define TRIO_ARGS8(list,a1,a2,a3,a4,a5,a6,a7,a8) (a1,a2,a3,a4,a5,a6,a7,a8) +# define TRIO_VARGS2 TRIO_ARGS2 +# define TRIO_VARGS3 TRIO_ARGS3 +# define TRIO_VARGS4 TRIO_ARGS4 +# define TRIO_VARGS5 TRIO_ARGS5 +# define TRIO_VA_DECL ... +# define TRIO_VA_START(x,y) va_start(x,y) +# define TRIO_VA_END(x) va_end(x) +#endif + +#if defined(PREDEF_STANDARD_C99) || defined(PREDEF_STANDARD_CXX) +# define TRIO_INLINE inline +#else +# if defined(TRIO_COMPILER_GCC) +# define TRIO_INLINE __inline__ +# endif +# if defined(TRIO_COMPILER_MSVC) +# define TRIO_INLINE _inline +# endif +# if defined(TRIO_COMPILER_BCB) +# define TRIO_INLINE __inline +# endif +#endif +#if !defined(TRIO_INLINE) +# define TRIO_INLINE +#endif + +/************************************************************************* + * Workarounds + */ + +#if defined(TRIO_PLATFORM_VMS) +/* + * Computations done with constants at compile time can trigger these + * even when compiling with IEEE enabled. + */ +# pragma message disable (UNDERFLOW, FLOATOVERFL) + +# if (__CRTL_VER < 80210001) +/* + * Although the compiler supports C99 language constructs, the C + * run-time library does not contain all C99 functions. + */ +# if defined(PREDEF_STANDARD_C99) +# undef PREDEF_STANDARD_C99 +# endif +# endif +#endif + +/* + * Not all preprocessors supports the LL token. + */ +#if defined(TRIO_COMPILER_MSVC) || defined(TRIO_COMPILER_BCB) +#else +# define TRIO_COMPILER_SUPPORTS_LL +#endif + +#if defined(__CYGWIN__) +/* + * Cygwin defines the macros for hosted C99, but does not support certain + * long double math functions. + */ +# include +# define TRIO_CYGWIN_VERSION_API CYGWIN_VERSION_API_MAJOR * 1000 + \ + CYGWIN_VERSION_API_MINOR +/* + * Please change the version number below when the Cygwin API supports + * long double math functions (powl, fmodl, etc.) + */ +# if TRIO_CYGWIN_VERSION_API < 99999999 +# define TRIO_NO_FLOORL 1 +# define TRIO_NO_CEILL 1 +# define TRIO_NO_POWL 1 +# define TRIO_NO_FMODL 1 +# define TRIO_NO_LOG10L 1 +# endif +#endif + +# if defined(TRIO_CPU_POWERPC) || defined(TRIO_CPU_SPARC) || defined(TRIO_CPU_SYSTEMZ) +# define TRIO_DOUBLE_DOUBLE +# endif + +#endif /* TRIO_TRIODEF_H */ diff --git a/psx/mednadisc/trio/trionan.c b/psx/mednadisc/trio/trionan.c new file mode 100644 index 0000000000..e3bae34816 --- /dev/null +++ b/psx/mednadisc/trio/trionan.c @@ -0,0 +1,1318 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2001 Bjorn Reese + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************ + * + * Functions to handle special quantities in floating-point numbers + * (that is, NaNs and infinity). They provide the capability to detect + * and fabricate special quantities. + * + * Although written to be as portable as possible, it can never be + * guaranteed to work on all platforms, as not all hardware supports + * special quantities. + * + * The approach used here (approximately) is to: + * + * 1. Use C99 functionality when available. + * 2. Use IEEE 754 bit-patterns if possible. + * 3. Use platform-specific techniques. + * + ************************************************************************/ + +/************************************************************************* + * Include files + */ +#include "triodef.h" +#include "trionan.h" + +#include +#include +#include +#if !defined(TRIO_PLATFORM_SYMBIAN) +# include +#endif +#if defined(TRIO_PLATFORM_UNIX) +# include +#endif +#if defined(TRIO_COMPILER_DECC) +# include +#endif +#include + +#if defined(TRIO_DOCUMENTATION) +# include "doc/doc_nan.h" +#endif +/** @addtogroup SpecialQuantities + @{ +*/ + +/************************************************************************* + * Definitions + */ + +#if !defined(TRIO_PUBLIC_NAN) +# define TRIO_PUBLIC_NAN TRIO_PUBLIC +#endif +#if !defined(TRIO_PRIVATE_NAN) +# define TRIO_PRIVATE_NAN TRIO_PRIVATE +#endif + +#define TRIO_TRUE (1 == 1) +#define TRIO_FALSE (0 == 1) + +/* + * We must enable IEEE floating-point on Alpha + */ +#if defined(__alpha) && !defined(_IEEE_FP) +# if defined(TRIO_COMPILER_DECC) +# if defined(TRIO_PLATFORM_VMS) +# error "Must be compiled with option /IEEE_MODE=UNDERFLOW_TO_ZERO/FLOAT=IEEE" +# else +# if !defined(_CFE) +# error "Must be compiled with option -ieee" +# endif +# endif +# else +# if defined(TRIO_COMPILER_GCC) +# error "Must be compiled with option -mieee" +# endif +# endif +#endif /* __alpha && ! _IEEE_FP */ + +/* + * In ANSI/IEEE 754-1985 64-bits double format numbers have the + * following properties (amoungst others) + * + * o FLT_RADIX == 2: binary encoding + * o DBL_MAX_EXP == 1024: 11 bits exponent, where one bit is used + * to indicate special numbers (e.g. NaN and Infinity), so the + * maximum exponent is 10 bits wide (2^10 == 1024). + * o DBL_MANT_DIG == 53: The mantissa is 52 bits wide, but because + * numbers are normalized the initial binary 1 is represented + * implicitly (the so-called "hidden bit"), which leaves us with + * the ability to represent 53 bits wide mantissa. + */ +#if defined(__STDC_IEC_559__) +# define TRIO_IEEE_754 +#else +# if (FLT_RADIX - 0 == 2) && (DBL_MAX_EXP - 0 == 1024) && (DBL_MANT_DIG - 0 == 53) +# define TRIO_IEEE_754 +# endif +#endif + +/* + * Determine which fpclassify_and_sign() function to use. + */ +#if defined(TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT) +# if defined(PREDEF_STANDARD_C99) && defined(fpclassify) +# define TRIO_FUNC_C99_FPCLASSIFY_AND_SIGNBIT +# else +# if defined(TRIO_COMPILER_DECC) +# define TRIO_FUNC_DECC_FPCLASSIFY_AND_SIGNBIT +# else +# if defined(TRIO_COMPILER_VISUALC) || defined(TRIO_COMPILER_BORLAND) +# define TRIO_FUNC_MS_FPCLASSIFY_AND_SIGNBIT +# else +# if defined(TRIO_COMPILER_HP) && defined(FP_PLUS_NORM) +# define TRIO_FUNC_HP_FPCLASSIFY_AND_SIGNBIT +# else +# if defined(TRIO_COMPILER_XLC) && defined(FP_PLUS_NORM) +# define TRIO_FUNC_XLC_FPCLASSIFY_AND_SIGNBIT +# else +# define TRIO_FUNC_INTERNAL_FPCLASSIFY_AND_SIGNBIT +# endif +# endif +# endif +# endif +# endif +#endif + +/* + * Determine how to generate negative zero. + */ +#if defined(TRIO_FUNC_NZERO) +# if defined(TRIO_IEEE_754) +# define TRIO_NZERO_IEEE_754 +# else +# define TRIO_NZERO_FALLBACK +# endif +#endif + +/* + * Determine how to generate positive infinity. + */ +#if defined(TRIO_FUNC_PINF) +# if defined(INFINITY) && defined(__STDC_IEC_559__) +# define TRIO_PINF_C99_MACRO +# else +# if defined(TRIO_IEEE_754) +# define TRIO_PINF_IEEE_754 +# else +# define TRIO_PINF_FALLBACK +# endif +# endif +#endif + +/* + * Determine how to generate NaN. + */ +#if defined(TRIO_FUNC_NAN) +# if defined(PREDEF_STANDARD_C99) && !defined(TRIO_COMPILER_DECC) +# define TRIO_NAN_C99_FUNCTION +# else +# if defined(NAN) && defined(__STDC_IEC_559__) +# define TRIO_NAN_C99_MACRO +# else +# if defined(TRIO_IEEE_754) +# define TRIO_NAN_IEEE_754 +# else +# define TRIO_NAN_FALLBACK +# endif +# endif +# endif +#endif + +/* + * Resolve internal dependencies. + */ +#if defined(TRIO_FUNC_INTERNAL_FPCLASSIFY_AND_SIGNBIT) +# define TRIO_FUNC_INTERNAL_ISNAN +# define TRIO_FUNC_INTERNAL_ISINF +# if defined(TRIO_IEEE_754) +# define TRIO_FUNC_INTERNAL_IS_SPECIAL_QUANTITY +# define TRIO_FUNC_INTERNAL_IS_NEGATIVE +# endif +#endif + +#if defined(TRIO_NZERO_IEEE_754) \ + || defined(TRIO_PINF_IEEE_754) \ + || defined(TRIO_NAN_IEEE_754) +# define TRIO_FUNC_INTERNAL_MAKE_DOUBLE +#endif + +#if defined(TRIO_FUNC_INTERNAL_ISNAN) +# if defined(PREDEF_STANDARD_XPG3) +# define TRIO_INTERNAL_ISNAN_XPG3 +# else +# if defined(TRIO_IEEE_754) +# define TRIO_INTERNAL_ISNAN_IEEE_754 +# else +# define TRIO_INTERNAL_ISNAN_FALLBACK +# endif +# endif +#endif + +#if defined(TRIO_FUNC_INTERNAL_ISINF) +# if defined(TRIO_IEEE_754) +# define TRIO_INTERNAL_ISINF_IEEE_754 +# else +# define TRIO_INTERNAL_ISINF_FALLBACK +# endif +#endif + +/************************************************************************* + * Constants + */ + +#if !defined(TRIO_EMBED_NAN) +static TRIO_CONST char rcsid[] = "@(#)$Id$"; +#endif + +#if defined(TRIO_FUNC_INTERNAL_MAKE_DOUBLE) \ + || defined(TRIO_FUNC_INTERNAL_IS_SPECIAL_QUANTITY) \ + || defined(TRIO_FUNC_INTERNAL_IS_NEGATIVE) +/* + * Endian-agnostic indexing macro. + * + * The value of internalEndianMagic, when converted into a 64-bit + * integer, becomes 0x0706050403020100 (we could have used a 64-bit + * integer value instead of a double, but not all platforms supports + * that type). The value is automatically encoded with the correct + * endianess by the compiler, which means that we can support any + * kind of endianess. The individual bytes are then used as an index + * for the IEEE 754 bit-patterns and masks. + */ +#define TRIO_DOUBLE_INDEX(x) (((unsigned char *)&internalEndianMagic)[7-(x)]) +static TRIO_CONST double internalEndianMagic = 7.949928895127363e-275; +#endif + +#if defined(TRIO_FUNC_INTERNAL_IS_SPECIAL_QUANTITY) +/* Mask for the exponent */ +static TRIO_CONST unsigned char ieee_754_exponent_mask[] = { + 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* Mask for the mantissa */ +static TRIO_CONST unsigned char ieee_754_mantissa_mask[] = { + 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +#endif + +#if defined(TRIO_FUNC_INTERNAL_IS_NEGATIVE) +/* Mask for the sign bit */ +static TRIO_CONST unsigned char ieee_754_sign_mask[] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +#endif + +#if defined(TRIO_NZERO_IEEE_754) +/* Bit-pattern for negative zero */ +static TRIO_CONST unsigned char ieee_754_negzero_array[] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +#endif + +#if defined(TRIO_PINF_IEEE_754) +/* Bit-pattern for infinity */ +static TRIO_CONST unsigned char ieee_754_infinity_array[] = { + 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +#endif + +#if defined(TRIO_NAN_IEEE_754) +/* Bit-pattern for quiet NaN */ +static TRIO_CONST unsigned char ieee_754_qnan_array[] = { + 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +#endif + + +/************************************************************************* + * Internal functions + */ + +/* + * + */ +#if defined(TRIO_PLATFORM_UNIX) && defined(TRIO_INTERNAL_ISNAN_FALLBACK) + +/* Assume that if SA_SIGINFO is defined, then sigaction() and + * 'struct sigaction' are also properly defined on this platform. + */ +#ifndef TRIO_USE_SIGACTION +# ifdef SA_SIGINFO +# define TRIO_USE_SIGACTION 1 +# else +# define TRIO_USE_SIGACTION 0 +# endif +#endif + +# if TRIO_USE_SIGACTION +typedef struct sigaction signal_handler_t; +# else +typedef void (*signal_handler_t) TRIO_PROTO((int)); +# endif + +/* + * internal_ignore_signal_handler + */ + +TRIO_PRIVATE_NAN signal_handler_t +internal_ignore_signal_handler +TRIO_ARGS1((signum), + int signum) +{ +# if TRIO_USE_SIGACTION + signal_handler_t old_handler, new_handler; + memset(&new_handler, '\0', sizeof(new_handler)); + new_handler.sa_handler = SIG_IGN; + new_handler.sa_flags = SA_RESTART; + sigaction(signum, &new_handler, &old_handler); + return old_handler; +# else + return signal(signum, SIG_IGN); +# endif +} + +/* + * internal_restore_signal_handler + */ +TRIO_PRIVATE_NAN void +internal_restore_signal_handler +TRIO_ARGS2((signum, handler), + int signum, + signal_handler_t handler) +{ +# if TRIO_USE_SIGACTION + sigaction(signum, &handler, NULL); +# else + signal(signum, handler); +# endif +} + +#endif + +/* + * internal_make_double + */ +#if defined(TRIO_FUNC_INTERNAL_MAKE_DOUBLE) + +TRIO_PRIVATE_NAN double +internal_make_double +TRIO_ARGS1((values), + TRIO_CONST unsigned char *values) +{ + TRIO_VOLATILE double result; + int i; + + for (i = 0; i < (int)sizeof(double); i++) { + ((TRIO_VOLATILE unsigned char *)&result)[TRIO_DOUBLE_INDEX(i)] = values[i]; + } + return result; +} + +#endif + +/* + * internal_is_special_quantity + */ +#if defined(TRIO_FUNC_INTERNAL_IS_SPECIAL_QUANTITY) + +TRIO_PRIVATE_NAN int +internal_is_special_quantity +TRIO_ARGS2((number, has_mantissa), + double number, + int *has_mantissa) +{ + unsigned int i; + unsigned char current; + int is_special_quantity = TRIO_TRUE; + + *has_mantissa = 0; + + for (i = 0; i < (unsigned int)sizeof(double); i++) { + current = ((unsigned char *)&number)[TRIO_DOUBLE_INDEX(i)]; + is_special_quantity + &= ((current & ieee_754_exponent_mask[i]) == ieee_754_exponent_mask[i]); + *has_mantissa |= (current & ieee_754_mantissa_mask[i]); + } + return is_special_quantity; +} + +#endif + +/* + * internal_is_negative + */ +#if defined(TRIO_FUNC_INTERNAL_IS_NEGATIVE) + +TRIO_PRIVATE_NAN int +internal_is_negative +TRIO_ARGS1((number), + double number) +{ + unsigned int i; + int is_negative = TRIO_FALSE; + + for (i = 0; i < (unsigned int)sizeof(double); i++) { + is_negative |= (((unsigned char *)&number)[TRIO_DOUBLE_INDEX(i)] + & ieee_754_sign_mask[i]); + } + return is_negative; +} + +#endif + +#if defined(TRIO_FUNC_C99_FPCLASSIFY_AND_SIGNBIT) + +TRIO_PRIVATE_NAN TRIO_INLINE int +c99_fpclassify_and_signbit +TRIO_ARGS2((number, is_negative), + double number, + int *is_negative) +{ + *is_negative = signbit(number); + switch (fpclassify(number)) { + case FP_NAN: + return TRIO_FP_NAN; + case FP_INFINITE: + return TRIO_FP_INFINITE; + case FP_SUBNORMAL: + return TRIO_FP_SUBNORMAL; + case FP_ZERO: + return TRIO_FP_ZERO; + default: + return TRIO_FP_NORMAL; + } +} + +#endif /* TRIO_FUNC_C99_FPCLASSIFY_AND_SIGNBIT */ + +#if defined(TRIO_FUNC_DECC_FPCLASSIFY_AND_SIGNBIT) + +TRIO_PRIVATE_NAN TRIO_INLINE int +decc_fpclassify_and_signbit +TRIO_ARGS2((number, is_negative), + double number, + int *is_negative) +{ + switch (fp_class(number)) { + case FP_QNAN: + case FP_SNAN: + *is_negative = TRIO_FALSE; /* NaN has no sign */ + return TRIO_FP_NAN; + case FP_POS_INF: + *is_negative = TRIO_FALSE; + return TRIO_FP_INFINITE; + case FP_NEG_INF: + *is_negative = TRIO_TRUE; + return TRIO_FP_INFINITE; + case FP_POS_DENORM: + *is_negative = TRIO_FALSE; + return TRIO_FP_SUBNORMAL; + case FP_NEG_DENORM: + *is_negative = TRIO_TRUE; + return TRIO_FP_SUBNORMAL; + case FP_POS_ZERO: + *is_negative = TRIO_FALSE; + return TRIO_FP_ZERO; + case FP_NEG_ZERO: + *is_negative = TRIO_TRUE; + return TRIO_FP_ZERO; + case FP_POS_NORM: + *is_negative = TRIO_FALSE; + return TRIO_FP_NORMAL; + case FP_NEG_NORM: + *is_negative = TRIO_TRUE; + return TRIO_FP_NORMAL; + default: + *is_negative = (number < 0.0); + return TRIO_FP_NORMAL; + } +} + +#endif /* TRIO_FUNC_DECC_FPCLASSIFY_AND_SIGNBIT */ + +#if defined(TRIO_FUNC_MS_FPCLASSIFY_AND_SIGNBIT) + +TRIO_PRIVATE_NAN int +ms_fpclassify_and_signbit +TRIO_ARGS2((number, is_negative), + double number, + int *is_negative) +{ + int result; +# if defined(TRIO_COMPILER_BORLAND) + /* + * The floating-point precision may be changed by the Borland _fpclass() + * function, so we have to save and restore the floating-point control mask. + */ + unsigned int mask; + /* Remember the old mask */ + mask = _control87(0, 0); +# endif + + switch (_fpclass(number)) { + case _FPCLASS_QNAN: + case _FPCLASS_SNAN: + *is_negative = TRIO_FALSE; /* NaN has no sign */ + result = TRIO_FP_NAN; + break; + case _FPCLASS_PINF: + *is_negative = TRIO_FALSE; + result = TRIO_FP_INFINITE; + break; + case _FPCLASS_NINF: + *is_negative = TRIO_TRUE; + result = TRIO_FP_INFINITE; + break; + case _FPCLASS_PD: + *is_negative = TRIO_FALSE; + result = TRIO_FP_SUBNORMAL; + break; + case _FPCLASS_ND: + *is_negative = TRIO_TRUE; + result = TRIO_FP_SUBNORMAL; + break; + case _FPCLASS_PZ: + *is_negative = TRIO_FALSE; + result = TRIO_FP_ZERO; + break; + case _FPCLASS_NZ: + *is_negative = TRIO_TRUE; + result = TRIO_FP_ZERO; + break; + case _FPCLASS_PN: + *is_negative = TRIO_FALSE; + result = TRIO_FP_NORMAL; + break; + case _FPCLASS_NN: + *is_negative = TRIO_TRUE; + result = TRIO_FP_NORMAL; + break; + default: + *is_negative = (number < 0.0); + result = TRIO_FP_NORMAL; + break; + } + +# if defined(TRIO_COMPILER_BORLAND) + /* Restore the old precision */ + (void)_control87(mask, MCW_PC); +# endif + + return result; +} + +#endif /* TRIO_FUNC_MS_FPCLASSIFY_AND_SIGNBIT */ + +#if defined(TRIO_FUNC_HP_FPCLASSIFY_AND_SIGNBIT) + +TRIO_PRIVATE_NAN TRIO_INLINE int +hp_fpclassify_and_signbit +TRIO_ARGS2((number, is_negative), + double number, + int *is_negative) +{ + /* + * HP-UX 9.x and 10.x have an fpclassify() function, that is different + * from the C99 fpclassify() macro supported on HP-UX 11.x. + */ + switch (fpclassify(number)) { + case FP_QNAN: + case FP_SNAN: + *is_negative = TRIO_FALSE; /* NaN has no sign */ + return TRIO_FP_NAN; + case FP_PLUS_INF: + *is_negative = TRIO_FALSE; + return TRIO_FP_INFINITE; + case FP_MINUS_INF: + *is_negative = TRIO_TRUE; + return TRIO_FP_INFINITE; + case FP_PLUS_DENORM: + *is_negative = TRIO_FALSE; + return TRIO_FP_SUBNORMAL; + case FP_MINUS_DENORM: + *is_negative = TRIO_TRUE; + return TRIO_FP_SUBNORMAL; + case FP_PLUS_ZERO: + *is_negative = TRIO_FALSE; + return TRIO_FP_ZERO; + case FP_MINUS_ZERO: + *is_negative = TRIO_TRUE; + return TRIO_FP_ZERO; + case FP_PLUS_NORM: + *is_negative = TRIO_FALSE; + return TRIO_FP_NORMAL; + case FP_MINUS_NORM: + *is_negative = TRIO_TRUE; + return TRIO_FP_NORMAL; + default: + *is_negative = (number < 0.0); + return TRIO_FP_NORMAL; + } +} + +#endif /* TRIO_FUNC_HP_FPCLASSIFY_AND_SIGNBIT */ + +#if defined(TRIO_FUNC_XLC_FPCLASSIFY_AND_SIGNBIT) + +TRIO_PRIVATE_NAN TRIO_INLINE int +xlc_fpclassify_and_signbit +TRIO_ARGS2((number, is_negative), + double number, + int *is_negative) +{ + /* + * AIX has class() for C, and _class() for C++ + */ +# if defined(__cplusplus) +# define AIX_CLASS(n) _class(n) +# else +# define AIX_CLASS(n) class(n) +# endif + + switch (AIX_CLASS(number)) { + case FP_QNAN: + case FP_SNAN: + *is_negative = TRIO_FALSE; /* NaN has no sign */ + return TRIO_FP_NAN; + case FP_PLUS_INF: + *is_negative = TRIO_FALSE; + return TRIO_FP_INFINITE; + case FP_MINUS_INF: + *is_negative = TRIO_TRUE; + return TRIO_FP_INFINITE; + case FP_PLUS_DENORM: + *is_negative = TRIO_FALSE; + return TRIO_FP_SUBNORMAL; + case FP_MINUS_DENORM: + *is_negative = TRIO_TRUE; + return TRIO_FP_SUBNORMAL; + case FP_PLUS_ZERO: + *is_negative = TRIO_FALSE; + return TRIO_FP_ZERO; + case FP_MINUS_ZERO: + *is_negative = TRIO_TRUE; + return TRIO_FP_ZERO; + case FP_PLUS_NORM: + *is_negative = TRIO_FALSE; + return TRIO_FP_NORMAL; + case FP_MINUS_NORM: + *is_negative = TRIO_TRUE; + return TRIO_FP_NORMAL; + default: + *is_negative = (number < 0.0); + return TRIO_FP_NORMAL; + } +} + +#endif /* TRIO_FUNC_XLC_FPCLASSIFY_AND_SIGNBIT */ + +#if defined(TRIO_FUNC_INTERNAL_ISNAN) + +TRIO_PRIVATE_NAN TRIO_INLINE int +internal_isnan +TRIO_ARGS1((number), + double number) +{ +# if defined(TRIO_INTERNAL_ISNAN_XPG3) || defined(TRIO_PLATFORM_SYMBIAN) + /* + * XPG3 defines isnan() as a function. + */ + return isnan(number); + +# endif + +# if defined(TRIO_INTERNAL_ISNAN_IEEE_754) + + /* + * Examine IEEE 754 bit-pattern. A NaN must have a special exponent + * pattern, and a non-empty mantissa. + */ + int has_mantissa; + int is_special_quantity; + + is_special_quantity = internal_is_special_quantity(number, &has_mantissa); + + return (is_special_quantity && has_mantissa); + +# endif + +# if defined(TRIO_INTERNAL_ISNAN_FALLBACK) + + /* + * Fallback solution + */ + int status; + double integral, fraction; + +# if defined(TRIO_PLATFORM_UNIX) + signal_handler_t sigfpe_handler = internal_ignore_signal_handler(SIGFPE); +# endif + + status = (/* + * NaN is the only number which does not compare to itself + */ + ((TRIO_VOLATILE double)number != (TRIO_VOLATILE double)number) || + /* + * Fallback solution if NaN compares to NaN + */ + ((number != 0.0) && + (fraction = modf(number, &integral), + integral == fraction))); + +# if defined(TRIO_PLATFORM_UNIX) + internal_restore_signal_handler(SIGFPR, sigfpe_handler); +# endif + + return status; + +# endif +} + +#endif /* TRIO_FUNC_INTERNAL_ISNAN */ + +#if defined(TRIO_FUNC_INTERNAL_ISINF) + +TRIO_PRIVATE_NAN TRIO_INLINE int +internal_isinf +TRIO_ARGS1((number), + double number) +{ +# if defined(TRIO_PLATFORM_SYMBIAN) + + return isinf(number); + +# endif + +# if defined(TRIO_INTERNAL_ISINF_IEEE_754) + /* + * Examine IEEE 754 bit-pattern. Infinity must have a special exponent + * pattern, and an empty mantissa. + */ + int has_mantissa; + int is_special_quantity; + + is_special_quantity = internal_is_special_quantity(number, &has_mantissa); + + return (is_special_quantity && !has_mantissa) + ? ((number < 0.0) ? -1 : 1) + : 0; + +# endif + +# if defined(TRIO_INTERNAL_ISINF_FALLBACK) + + /* + * Fallback solution. + */ + int status; + +# if defined(TRIO_PLATFORM_UNIX) + signal_handler_t sigfpe_handler = internal_ignore_signal_handler(SIGFPE); +# endif + + double infinity = trio_pinf(); + + status = ((number == infinity) + ? 1 + : ((number == -infinity) ? -1 : 0)); + +# if defined(TRIO_PLATFORM_UNIX) + internal_restore_signal_handler(SIGFPE, sigfpe_handler); +# endif + + return status; + +# endif +} + +#endif /* TRIO_FUNC_INTERNAL_ISINF */ + +/************************************************************************* + * Public functions + */ + +#if defined(TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT) + +TRIO_PUBLIC_NAN int +trio_fpclassify_and_signbit +TRIO_ARGS2((number, is_negative), + double number, + int *is_negative) +{ + /* The TRIO_FUNC_xxx_FPCLASSIFY_AND_SIGNBIT macros are mutually exclusive */ + +#if defined(TRIO_FUNC_C99_FPCLASSIFY_AND_SIGNBIT) + + return c99_fpclassify_and_signbit(number, is_negative); + +#endif + +#if defined(TRIO_FUNC_DECC_FPCLASSIFY_AND_SIGNBIT) + + return decc_fpclassify_and_signbit(number, is_negative); + +#endif + +#if defined(TRIO_FUNC_MS_FPCLASSIFY_AND_SIGNBIT) + + return ms_fpclassify_and_signbit(number, is_negative); + +#endif + +#if defined(TRIO_FUNC_HP_FPCLASSIFY_AND_SIGNBIT) + + return hp_fpclassify_and_signbit(number, is_negative); + +#endif + +#if defined(TRIO_FUNC_XLC_FPCLASSIFY_AND_SIGNBIT) + + return xlc_fpclassify_and_signbit(number, is_negative); + +#endif + +#if defined(TRIO_FUNC_INTERNAL_FPCLASSIFY_AND_SIGNBIT) + + /* + * Fallback solution. + */ + int rc; + + if (number == 0.0) { + /* + * In IEEE 754 the sign of zero is ignored in comparisons, so we + * have to handle this as a special case by examining the sign bit + * directly. + */ +# if defined(TRIO_IEEE_754) + *is_negative = internal_is_negative(number); +# else + *is_negative = TRIO_FALSE; /* FIXME */ +# endif + return TRIO_FP_ZERO; + } + if (internal_isnan(number)) { + *is_negative = TRIO_FALSE; + return TRIO_FP_NAN; + } + rc = internal_isinf(number); + if (rc != 0) { + *is_negative = (rc == -1); + return TRIO_FP_INFINITE; + } + if ((number > 0.0) && (number < DBL_MIN)) { + *is_negative = TRIO_FALSE; + return TRIO_FP_SUBNORMAL; + } + if ((number < 0.0) && (number > -DBL_MIN)) { + *is_negative = TRIO_TRUE; + return TRIO_FP_SUBNORMAL; + } + *is_negative = (number < 0.0); + return TRIO_FP_NORMAL; + +#endif +} + +#endif + +/** + Check for NaN. + + @param number An arbitrary floating-point number. + @return Boolean value indicating whether or not the number is a NaN. +*/ +#if defined(TRIO_FUNC_ISNAN) + +TRIO_PUBLIC_NAN int +trio_isnan +TRIO_ARGS1((number), + double number) +{ + int dummy; + + return (trio_fpclassify_and_signbit(number, &dummy) == TRIO_FP_NAN); +} + +#endif + +/** + Check for infinity. + + @param number An arbitrary floating-point number. + @return 1 if positive infinity, -1 if negative infinity, 0 otherwise. +*/ +#if defined(TRIO_FUNC_ISINF) + +TRIO_PUBLIC_NAN int +trio_isinf +TRIO_ARGS1((number), + double number) +{ + int is_negative; + + if (trio_fpclassify_and_signbit(number, &is_negative) == TRIO_FP_INFINITE) + { + return (is_negative) ? -1 : 1; + } + else + { + return 0; + } +} + +#endif + +/** + Check for finity. + + @param number An arbitrary floating-point number. + @return Boolean value indicating whether or not the number is a finite. +*/ +#if defined(TRIO_FUNC_ISFINITE) + +TRIO_PUBLIC_NAN int +trio_isfinite +TRIO_ARGS1((number), + double number) +{ + int dummy; + + switch (trio_fpclassify_and_signbit(number, &dummy)) + { + case TRIO_FP_INFINITE: + case TRIO_FP_NAN: + return 0; + default: + return 1; + } +} + +#endif + +/** + Examine the sign of a number. + + @param number An arbitrary floating-point number. + @return Boolean value indicating whether or not the number has the + sign bit set (i.e. is negative). +*/ +#if defined(TRIO_FUNC_SIGNBIT) + +TRIO_PUBLIC_NAN int +trio_signbit +TRIO_ARGS1((number), + double number) +{ + int is_negative; + + (void)trio_fpclassify_and_signbit(number, &is_negative); + return is_negative; +} + +#endif + +/** + Examine the class of a number. + + @param number An arbitrary floating-point number. + @return Enumerable value indicating the class of @p number +*/ +#if defined(TRIO_FUNC_FPCLASSIFY) + +TRIO_PUBLIC_NAN int +trio_fpclassify +TRIO_ARGS1((number), + double number) +{ + int dummy; + + return trio_fpclassify_and_signbit(number, &dummy); +} + +#endif + +/** + Generate negative zero. + + @return Floating-point representation of negative zero. +*/ +#if defined(TRIO_FUNC_NZERO) + +TRIO_PUBLIC_NAN double +trio_nzero(TRIO_NOARGS) +{ +# if defined(TRIO_NZERO_IEEE_754) + + return internal_make_double(ieee_754_negzero_array); + +# endif + +# if defined(TRIO_NZERO_FALLBACK) + + TRIO_VOLATILE double zero = 0.0; + + return -zero; + +# endif +} + +#endif + +/** + Generate positive infinity. + + @return Floating-point representation of positive infinity. +*/ +#if defined(TRIO_FUNC_PINF) + +TRIO_PUBLIC_NAN double +trio_pinf(TRIO_NOARGS) +{ + /* Cache the result */ + static double pinf_value = 0.0; + + if (pinf_value == 0.0) { + +# if defined(TRIO_PINF_C99_MACRO) + + pinf_value = (double)INFINITY; + +# endif + +# if defined(TRIO_PINF_IEEE_754) + + pinf_value = internal_make_double(ieee_754_infinity_array); + +# endif + +# if defined(TRIO_PINF_FALLBACK) + /* + * If HUGE_VAL is different from DBL_MAX, then HUGE_VAL is used + * as infinity. Otherwise we have to resort to an overflow + * operation to generate infinity. + */ +# if defined(TRIO_PLATFORM_UNIX) + signal_handler_t sigfpe_handler = internal_ignore_signal_handler(SIGFPE); +# endif + + pinf_value = HUGE_VAL; + if (HUGE_VAL == DBL_MAX) { + /* Force overflow */ + pinf_value += HUGE_VAL; + } + +# if defined(TRIO_PLATFORM_UNIX) + internal_restore_signal_handler(SIGFPE, sigfpe_handler); +# endif + +# endif + } + return pinf_value; +} + +#endif + +/** + Generate negative infinity. + + @return Floating-point value of negative infinity. +*/ +#if defined(TRIO_FUNC_NINF) + +TRIO_PUBLIC_NAN double +trio_ninf(TRIO_NOARGS) +{ + static double ninf_value = 0.0; + + if (ninf_value == 0.0) { + /* + * Negative infinity is calculated by negating positive infinity, + * which can be done because it is legal to do calculations on + * infinity (for example, 1 / infinity == 0). + */ + ninf_value = -trio_pinf(); + } + return ninf_value; +} + +#endif + +/** + Generate NaN. + + @return Floating-point representation of NaN. +*/ +#if defined(TRIO_FUNC_NAN) + +TRIO_PUBLIC_NAN double +trio_nan(TRIO_NOARGS) +{ + /* Cache the result */ + static double nan_value = 0.0; + + if (nan_value == 0.0) { + +# if defined(TRIO_NAN_C99_FUNCTION) || defined(TRIO_PLATFORM_SYMBIAN) + + nan_value = nan(""); + +# endif + +# if defined(TRIO_NAN_C99_MACRO) + + nan_value = (double)NAN; + +# endif + +# if defined(TRIO_NAN_IEEE_754) + + nan_value = internal_make_double(ieee_754_qnan_array); + +# endif + +# if defined(TRIO_NAN_FALLBACK) + /* + * There are several ways to generate NaN. The one used here is + * to divide infinity by infinity. I would have preferred to add + * negative infinity to positive infinity, but that yields wrong + * result (infinity) on FreeBSD. + * + * This may fail if the hardware does not support NaN, or if + * the Invalid Operation floating-point exception is unmasked. + */ +# if defined(TRIO_PLATFORM_UNIX) + signal_handle_t sigfpe_handler = internal_ignore_signal_handler(SIGFPE); +# endif + + nan_value = trio_pinf() / trio_pinf(); + +# if defined(TRIO_PLATFORM_UNIX) + internal_restore_signal_handler(SIGFPE, sigfpe_handler); +# endif + +# endif + } + return nan_value; +} + +#endif + +/** @} SpecialQuantities */ + +/************************************************************************* + * For test purposes. + * + * Add the following compiler option to include this test code. + * + * Unix : -DSTANDALONE + * VMS : /DEFINE=(STANDALONE) + */ +#if defined(STANDALONE) +# include + +static TRIO_CONST char * +getClassification +TRIO_ARGS1((type), + int type) +{ + switch (type) { + case TRIO_FP_INFINITE: + return "FP_INFINITE"; + case TRIO_FP_NAN: + return "FP_NAN"; + case TRIO_FP_NORMAL: + return "FP_NORMAL"; + case TRIO_FP_SUBNORMAL: + return "FP_SUBNORMAL"; + case TRIO_FP_ZERO: + return "FP_ZERO"; + default: + return "FP_UNKNOWN"; + } +} + +static void +print_class +TRIO_ARGS2((prefix, number), + TRIO_CONST char *prefix, + double number) +{ + printf("%-6s: %s %-15s %g\n", + prefix, + trio_signbit(number) ? "-" : "+", + getClassification(trio_fpclassify(number)), + number); +} + +int main(TRIO_NOARGS) +{ + double my_nan; + double my_pinf; + double my_ninf; +# if defined(TRIO_PLATFORM_UNIX) + signal_handler_t signal_handler; +# endif + + my_nan = trio_nan(); + my_pinf = trio_pinf(); + my_ninf = trio_ninf(); + + print_class("Nan", my_nan); + print_class("PInf", my_pinf); + print_class("NInf", my_ninf); + print_class("PZero", 0.0); + print_class("NZero", -0.0); + print_class("PNorm", 1.0); + print_class("NNorm", -1.0); + print_class("PSub", 1.01e-307 - 1.00e-307); + print_class("NSub", 1.00e-307 - 1.01e-307); + + printf("NaN : %4g 0x%02x%02x%02x%02x%02x%02x%02x%02x (%2d, %2d, %2d)\n", + my_nan, + ((unsigned char *)&my_nan)[0], + ((unsigned char *)&my_nan)[1], + ((unsigned char *)&my_nan)[2], + ((unsigned char *)&my_nan)[3], + ((unsigned char *)&my_nan)[4], + ((unsigned char *)&my_nan)[5], + ((unsigned char *)&my_nan)[6], + ((unsigned char *)&my_nan)[7], + trio_isnan(my_nan), trio_isinf(my_nan), trio_isfinite(my_nan)); + printf("PInf: %4g 0x%02x%02x%02x%02x%02x%02x%02x%02x (%2d, %2d, %2d)\n", + my_pinf, + ((unsigned char *)&my_pinf)[0], + ((unsigned char *)&my_pinf)[1], + ((unsigned char *)&my_pinf)[2], + ((unsigned char *)&my_pinf)[3], + ((unsigned char *)&my_pinf)[4], + ((unsigned char *)&my_pinf)[5], + ((unsigned char *)&my_pinf)[6], + ((unsigned char *)&my_pinf)[7], + trio_isnan(my_pinf), trio_isinf(my_pinf), trio_isfinite(my_pinf)); + printf("NInf: %4g 0x%02x%02x%02x%02x%02x%02x%02x%02x (%2d, %2d, %2d)\n", + my_ninf, + ((unsigned char *)&my_ninf)[0], + ((unsigned char *)&my_ninf)[1], + ((unsigned char *)&my_ninf)[2], + ((unsigned char *)&my_ninf)[3], + ((unsigned char *)&my_ninf)[4], + ((unsigned char *)&my_ninf)[5], + ((unsigned char *)&my_ninf)[6], + ((unsigned char *)&my_ninf)[7], + trio_isnan(my_ninf), trio_isinf(my_ninf), trio_isfinite(my_ninf)); + +# if defined(TRIO_PLATFORM_UNIX) + signal_handler = internal_ignore_signal_handler(SIGFPE); +# endif + + my_pinf = DBL_MAX + DBL_MAX; + my_ninf = -my_pinf; + my_nan = my_pinf / my_pinf; + +# if defined(TRIO_PLATFORM_UNIX) + internal_restore_signal_handler(SIGFPE, signal_handler); +# endif + + printf("NaN : %4g 0x%02x%02x%02x%02x%02x%02x%02x%02x (%2d, %2d, %2d)\n", + my_nan, + ((unsigned char *)&my_nan)[0], + ((unsigned char *)&my_nan)[1], + ((unsigned char *)&my_nan)[2], + ((unsigned char *)&my_nan)[3], + ((unsigned char *)&my_nan)[4], + ((unsigned char *)&my_nan)[5], + ((unsigned char *)&my_nan)[6], + ((unsigned char *)&my_nan)[7], + trio_isnan(my_nan), trio_isinf(my_nan), trio_isfinite(my_nan)); + printf("PInf: %4g 0x%02x%02x%02x%02x%02x%02x%02x%02x (%2d, %2d, %2d)\n", + my_pinf, + ((unsigned char *)&my_pinf)[0], + ((unsigned char *)&my_pinf)[1], + ((unsigned char *)&my_pinf)[2], + ((unsigned char *)&my_pinf)[3], + ((unsigned char *)&my_pinf)[4], + ((unsigned char *)&my_pinf)[5], + ((unsigned char *)&my_pinf)[6], + ((unsigned char *)&my_pinf)[7], + trio_isnan(my_pinf), trio_isinf(my_pinf), trio_isfinite(my_pinf)); + printf("NInf: %4g 0x%02x%02x%02x%02x%02x%02x%02x%02x (%2d, %2d, %2d)\n", + my_ninf, + ((unsigned char *)&my_ninf)[0], + ((unsigned char *)&my_ninf)[1], + ((unsigned char *)&my_ninf)[2], + ((unsigned char *)&my_ninf)[3], + ((unsigned char *)&my_ninf)[4], + ((unsigned char *)&my_ninf)[5], + ((unsigned char *)&my_ninf)[6], + ((unsigned char *)&my_ninf)[7], + trio_isnan(my_ninf), trio_isinf(my_ninf), trio_isfinite(my_ninf)); + + return 0; +} +#endif diff --git a/psx/mednadisc/trio/trionan.h b/psx/mednadisc/trio/trionan.h new file mode 100644 index 0000000000..b441b4ae21 --- /dev/null +++ b/psx/mednadisc/trio/trionan.h @@ -0,0 +1,183 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2001 Bjorn Reese + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +#ifndef TRIO_TRIONAN_H +#define TRIO_TRIONAN_H + +#include "triodef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(TRIO_PUBLIC_NAN) +# if !defined(TRIO_PUBLIC) +# define TRIO_PUBLIC +# endif +# define TRIO_PUBLIC_NAN TRIO_PUBLIC +#endif + +enum { + TRIO_FP_INFINITE, + TRIO_FP_NAN, + TRIO_FP_NORMAL, + TRIO_FP_SUBNORMAL, + TRIO_FP_ZERO +}; + +/************************************************************************* + * Dependencies + */ + +#if defined(TRIO_EMBED_NAN) + +/* + * The application that trionan is embedded in must define which functions + * it uses. + * + * The following resolves internal dependencies. + */ + +# if defined(TRIO_FUNC_ISNAN) \ + || defined(TRIO_FUNC_ISINF) +# if !defined(TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT) +# define TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT +# endif +# endif + +# if defined(TRIO_FUNC_NAN) +# if !defined(TRIO_FUNC_PINF) +# define TRIO_FUNC_PINF +# endif +# endif + +# if defined(TRIO_FUNC_NINF) +# if !defined(TRIO_FUNC_PINF) +# define TRIO_FUNC_PINF +# endif +# endif + +#else + +/* + * When trionan is not embedded all all functions are defined. + */ + +# define TRIO_FUNC_NAN +# define TRIO_FUNC_PINF +# define TRIO_FUNC_NINF +# define TRIO_FUNC_NZERO +# define TRIO_FUNC_ISNAN +# define TRIO_FUNC_ISINF +# define TRIO_FUNC_ISFINITE +# define TRIO_FUNC_SIGNBIT +# define TRIO_FUNC_FPCLASSIFY +# define TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT + +#endif + +/************************************************************************* + * Functions + */ + +/* + * Return NaN (Not-a-Number). + */ +#if defined(TRIO_FUNC_NAN) +TRIO_PUBLIC_NAN double +trio_nan +TRIO_PROTO((void)); +#endif + +/* + * Return positive infinity. + */ +#if defined(TRIO_FUNC_PINF) +TRIO_PUBLIC_NAN double +trio_pinf +TRIO_PROTO((void)); +#endif + +/* + * Return negative infinity. + */ +#if defined(TRIO_FUNC_NINF) +TRIO_PUBLIC_NAN double +trio_ninf +TRIO_PROTO((void)); +#endif + +/* + * Return negative zero. + */ +#if defined(TRIO_FUNC_NZERO) +TRIO_PUBLIC_NAN double +trio_nzero +TRIO_PROTO((TRIO_NOARGS)); +#endif + +/* + * If number is a NaN return non-zero, otherwise return zero. + */ +#if defined(TRIO_FUNC_ISNAN) +TRIO_PUBLIC_NAN int +trio_isnan +TRIO_PROTO((double number)); +#endif + +/* + * If number is positive infinity return 1, if number is negative + * infinity return -1, otherwise return 0. + */ +#if defined(TRIO_FUNC_ISINF) +TRIO_PUBLIC_NAN int +trio_isinf +TRIO_PROTO((double number)); +#endif + +/* + * If number is finite return non-zero, otherwise return zero. + */ +#if defined(TRIO_FUNC_ISFINITE) +TRIO_PUBLIC_NAN int +trio_isfinite +TRIO_PROTO((double number)); +#endif + +#if defined(TRIO_FUNC_SIGNBIT) +TRIO_PUBLIC_NAN int +trio_signbit +TRIO_PROTO((double number)); +#endif + +#if defined(TRIO_FUNC_FPCLASSIFY) +TRIO_PUBLIC_NAN int +trio_fpclassify +TRIO_PROTO((double number)); +#endif + +#if defined(TRIO_FUNC_FPCLASSIFY_AND_SIGNBIT) +TRIO_PUBLIC_NAN int +trio_fpclassify_and_signbit +TRIO_PROTO((double number, int *is_negative)); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TRIO_TRIONAN_H */ diff --git a/psx/mednadisc/trio/triop.h b/psx/mednadisc/trio/triop.h new file mode 100644 index 0000000000..352754ea2c --- /dev/null +++ b/psx/mednadisc/trio/triop.h @@ -0,0 +1,496 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2000 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************ + * + * Private functions, types, etc. used for callback functions. + * + * The ref pointer is an opaque type and should remain as such. + * Private data must only be accessible through the getter and + * setter functions. + * + ************************************************************************/ + +#ifndef TRIO_TRIOP_H +#define TRIO_TRIOP_H + +#include "triodef.h" + +#include +#if defined(TRIO_COMPILER_ANCIENT) +# include +#else +# include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/************************************************************************* + * Supported standards + */ + +/* + * TRIO_C99 (=0 or =1) + * + * Define this to 0 to disable C99 format specifier extensions, or + * define to 1 to enable them. The format specifiers that are + * disabled by this switch are labelled with [C99] in the format + * specifier documentation. + */ +#if !defined(TRIO_C99) +# define TRIO_C99 1 +#endif + +/* + * TRIO_BSD (=0 or =1) + * + * Define this to 0 to disable BSD format specifier extensions, or + * define to 1 to enable them. The format specifiers that are + * disabled by this switch are labelled with [BSD] in the format + * specifier documentation. + */ +#if !defined(TRIO_BSD) +# define TRIO_BSD 1 +#endif + +/* + * TRIO_GNU (=0 or =1) + * + * Define this to 0 to disable GNU format specifier extensions, or + * define to 1 to enable them. The format specifiers that are + * disabled by this switch are labelled with [GNU] in the format + * specifier documentation. + */ +#if !defined(TRIO_GNU) +# define TRIO_GNU 1 +#endif + +/* + * TRIO_MISC (=0 or =1) + * + * Define this to 0 to disable miscellaneous format specifier + * extensions, or define to 1 to enable them. The format specifiers + * that are disabled by this switch are labelled with [MISC] in the + * format specifier documentation. + */ +#if !defined(TRIO_MISC) +# define TRIO_MISC 1 +#endif + +/* + * TRIO_UNIX98 (=0 or =1) + * + * Define this to 0 to disable UNIX98 format specifier extensions, + * or define to 1 to enable them. The format specifiers that are + * disabled by this switch are labelled with [UNIX98] in the format + * specifier documentation. + */ +#if !defined(TRIO_UNIX98) +# define TRIO_UNIX98 1 +#endif + +/* + * TRIO_MICROSOFT (=0 or =1) + * + * Define this to 0 to disable Microsoft Visual C format specifier + * extensions, or define to 1 to enable them. The format specifiers + * that are disabled by this switch are labelled with [MSVC] in the + * format specifier documentation. + */ +#if !defined(TRIO_MICROSOFT) +# define TRIO_MICROSOFT 1 +#endif + +/* + * TRIO_EXTENSION (=0 or =1) + * + * Define this to 0 to disable Trio-specific extensions, or define + * to 1 to enable them. This has two effects: it controls whether + * or not the Trio user-defined formating mechanism + * (trio_register() etc) is supported, and it enables or disables + * Trio's own format specifier extensions. The format specifiers + * that are disabled by this switch are labelled with [TRIO] in + * the format specifier documentation. + */ +#if !defined(TRIO_EXTENSION) +# define TRIO_EXTENSION 1 +#endif + +/* + * TRIO_DEPRECATED (=0 or =1) + * + * Define this to 0 to disable deprecated functionality, or define + * to 1 to enable them. + */ +#if !defined(TRIO_DEPRECATED) +# define TRIO_DEPRECATED 1 +#endif + +/************************************************************************* + * Features + */ + +#if defined(TRIO_SNPRINTF_ONLY) +# define TRIO_FEATURE_SCANF 0 +# define TRIO_FEATURE_FILE 0 +# define TRIO_FEATURE_STDIO 0 +# define TRIO_FEATURE_FD 0 +# define TRIO_FEATURE_DYNAMICSTRING 0 +# define TRIO_FEATURE_CLOSURE 0 +# define TRIO_FEATURE_ARGFUNC 0 +# define TRIO_FEATURE_STRERR 0 +# define TRIO_FEATURE_LOCALE 0 +# define TRIO_EMBED_NAN 1 +# define TRIO_EMBED_STRING 1 +#endif + +/* + * TRIO_FEATURE_SCANF (=0 or =1) + * + * Define this to 0 to disable all the scanf() variants, or define to 1 + * to enable them. + */ +#if !defined(TRIO_FEATURE_SCANF) +# define TRIO_FEATURE_SCANF 1 +#endif + +/* + * TRIO_FEATURE_FILE (=0 or =1) + * + * Define this to 0 to disable compilation of the trio_fprintf() and + * trio_fscanf() family of functions, or define to 1 to enable them. + * + * This may be useful on an embedded platform with no filesystem. + * Note that trio_printf() uses fwrite to write to stdout, so if you + * do not have an implementation of fwrite() at all then you must also + * define TRIO_FEATURE_STDIO to 0. + */ +#if !defined(TRIO_FEATURE_FILE) +# define TRIO_FEATURE_FILE 1 +#endif + +/* + * TRIO_FEATURE_STDIO (=0 or =1) + * + * Define this to 0 to disable compilation of the trio_printf() and + * trio_scanf() family of functions, or define to 1 to enable them. + * + * This may be useful on an embedded platform with no standard I/O. + */ +#if !defined(TRIO_FEATURE_STDIO) +# define TRIO_FEATURE_STDIO 1 +#endif + +/* + * TRIO_FEATURE_FD (=0 or =1) + * + * Define this to 0 to disable compilation of the trio_dprintf() and + * trio_dscanf() family of functions, or define to 1 to enable them. + * + * This may be useful on an embedded platform with no filesystem, or on + * a platform that supports file I/O using FILE* but not using raw file + * descriptors. + */ +#if !defined(TRIO_FEATURE_FD) +# define TRIO_FEATURE_FD 1 +#endif + +/* + * TRIO_FEATURE_DYNAMICSTRING (=0 or =1) + * + * Define this to 0 to disable compilation of the trio_aprintf() + * family of functions, or define to 1 to enable them. + * + * If you define both this and TRIO_MINIMAL to 0, then Trio will never + * call malloc or free. + */ +#if !defined(TRIO_FEATURE_DYNAMICSTRING) +# define TRIO_FEATURE_DYNAMICSTRING 1 +#endif + +/* + * TRIO_FEATURE_CLOSURE (=0 or =1) + * + * Define this to 0 to disable compilation of the trio_cprintf() and + * trio_cscanf() family of functions, or define to 1 to enable them. + * + * These functions are rarely needed. This saves a (small) amount of code. + */ +#if !defined(TRIO_FEATURE_CLOSURE) +# define TRIO_FEATURE_CLOSURE 1 +#endif + +/* + * TRIO_FEATURE_ARGFUNC (=0 or =1) + * + * Define this to 0 to disable compilation of trio_cprintff() and + * trio_cscanff() functions and related code (might have a tiny + * performance gain), or define to 1 to enable them. + * + * This functionality is needed only if you have to fetch the arguments using + * a pull model instead of passing them all at once (for example if you plan + * to plug the library into a script interpreter or validate the types). + * + * Only the closure family of functions are available with this interface, + * because if you need this, you usually provide custom input/output + * handling too (and so this forces TRIO_FEATURE_CLOSURE to enabled). + */ +#if !defined(TRIO_FEATURE_ARGFUNC) +# define TRIO_FEATURE_ARGFUNC 1 +#endif +#if TRIO_FEATURE_ARGFUNC +# undef TRIO_FEATURE_CLOSURE +# define TRIO_FEATURE_CLOSURE 1 +#endif + +/* + * TRIO_FEATURE_ERRORCODE (=0 or =1) + * + * Define this to 0 to return -1 from the print and scan function on + * error, or define to 1 to return a negative number with debugging + * information as part of the return code. + * + * If enabled, the return code will be a negative number, which encodes + * an error code and an error location. These can be decoded with the + * TRIO_ERROR_CODE and TRIO_ERROR_POSITION macros. + */ +#if defined(TRIO_ERRORS) +# define TRIO_FEATURE_ERRORCODE TRIO_ERRORS +#endif +#if !defined(TRIO_FEATURE_ERRORCODE) +# define TRIO_FEATURE_ERRORCODE 1 +#endif + +/* + * TRIO_FEATURE_STRERR (=0 or =1) + * + * Define this to 0 if you do not use trio_strerror(), or define to 1 if + * you do use it. + * + * This saves a (small) amount of code. + */ +#if !defined(TRIO_FEATURE_STRERR) +# define TRIO_FEATURE_STRERR 1 +#endif + +/* + * TRIO_FEATURE_FLOAT (=0 or =1) + * + * Define this to 0 to disable all floating-point support, or define + * to 1 to enable it. + * + * This is useful in restricted embedded platforms that do not support + * floating-point. Obviously you cannot use floating-point format + * specifiers if you define this. + * + * Do not compile trionan.c if you disable this. + */ +#if !defined(TRIO_FEATURE_FLOAT) +# define TRIO_FEATURE_FLOAT 1 +#endif + +/* + * TRIO_FEATURE_LOCALE (=0 or =1) + * + * Define this to 0 to disable customized locale support, or define + * to 1 to enable it. + * + * This saves a (small) amount of code. + */ +#if !defined(TRIO_FEATURE_LOCALE) +# define TRIO_FEATURE_LOCALE 1 +#endif + +/* + * TRIO_MINIMAL + * + * Define this to disable building the public trionan.h and triostr.h. + * If you define this, then you must not compile trionan.c and triostr.c + * separately. + */ +#if defined(TRIO_MINIMAL) +# if !defined(TRIO_EMBED_NAN) +# define TRIO_EMBED_NAN +# endif +# if !defined(TRIO_EMBED_STRING) +# define TRIO_EMBED_STRING +# endif +#endif + +/* Does not work yet. Do not enable */ +#ifndef TRIO_FEATURE_WIDECHAR +# define TRIO_FEATURE_WIDECHAR 0 +#endif + +/************************************************************************* + * Mapping standards to internal features + */ + +#if !defined(TRIO_FEATURE_HEXFLOAT) +# define TRIO_FEATURE_HEXFLOAT (TRIO_C99 && TRIO_FEATURE_FLOAT) +#endif + +#if !defined(TRIO_FEATURE_LONGDOUBLE) +# define TRIO_FEATURE_LONGDOUBLE TRIO_FEATURE_FLOAT +#endif + +#if !defined(TRIO_FEATURE_ERRNO) +# define TRIO_FEATURE_ERRNO TRIO_GNU +#endif + +#if !defined(TRIO_FEATURE_QUAD) +# define TRIO_FEATURE_QUAD (TRIO_BSD || TRIO_GNU) +#endif + +#if !defined(TRIO_FEATURE_SIZE_T) +# define TRIO_FEATURE_SIZE_T TRIO_C99 +#endif + +#if !defined(TRIO_FEATURE_SIZE_T_UPPER) +# define TRIO_FEATURE_SIZE_T_UPPER TRIO_GNU +#endif + +#if !defined(TRIO_FEATURE_PTRDIFF_T) +# define TRIO_FEATURE_PTRDIFF_T TRIO_C99 +#endif + +#if !defined(TRIO_FEATURE_INTMAX_T) +# define TRIO_FEATURE_INTMAX_T TRIO_C99 +#endif + +#if !defined(TRIO_FEATURE_FIXED_SIZE) +# define TRIO_FEATURE_FIXED_SIZE TRIO_MICROSOFT +#endif + +#if !defined(TRIO_FEATURE_POSITIONAL) +# define TRIO_FEATURE_POSITIONAL TRIO_UNIX98 +#endif + +#if !defined(TRIO_FEATURE_USER_DEFINED) +# define TRIO_FEATURE_USER_DEFINED TRIO_EXTENSION +#endif + +#if !defined(TRIO_FEATURE_BINARY) +# define TRIO_FEATURE_BINARY TRIO_EXTENSION +#endif + +#if !defined(TRIO_FEATURE_QUOTE) +# define TRIO_FEATURE_QUOTE TRIO_EXTENSION +#endif + +#if !defined(TRIO_FEATURE_STICKY) +# define TRIO_FEATURE_STICKY TRIO_EXTENSION +#endif + +#if !defined(TRIO_FEATURE_VARSIZE) +# define TRIO_FEATURE_VARSIZE TRIO_EXTENSION +#endif + +#if !defined(TRIO_FEATURE_ROUNDING) +# define TRIO_FEATURE_ROUNDING TRIO_EXTENSION +#endif + +/************************************************************************* + * Memory handling + */ +#ifndef TRIO_MALLOC +# define TRIO_MALLOC(n) malloc(n) +#endif +#ifndef TRIO_REALLOC +# define TRIO_REALLOC(x,n) realloc((x),(n)) +#endif +#ifndef TRIO_FREE +# define TRIO_FREE(x) free(x) +#endif + + +/************************************************************************* + * User-defined specifiers + */ + +typedef int (*trio_callback_t) TRIO_PROTO((trio_pointer_t)); + +trio_pointer_t trio_register TRIO_PROTO((trio_callback_t callback, const char *name)); +void trio_unregister TRIO_PROTO((trio_pointer_t handle)); + +TRIO_CONST char *trio_get_format TRIO_PROTO((trio_pointer_t ref)); +TRIO_CONST trio_pointer_t trio_get_argument TRIO_PROTO((trio_pointer_t ref)); + +/* Modifiers */ +int trio_get_width TRIO_PROTO((trio_pointer_t ref)); +void trio_set_width TRIO_PROTO((trio_pointer_t ref, int width)); +int trio_get_precision TRIO_PROTO((trio_pointer_t ref)); +void trio_set_precision TRIO_PROTO((trio_pointer_t ref, int precision)); +int trio_get_base TRIO_PROTO((trio_pointer_t ref)); +void trio_set_base TRIO_PROTO((trio_pointer_t ref, int base)); +int trio_get_padding TRIO_PROTO((trio_pointer_t ref)); +void trio_set_padding TRIO_PROTO((trio_pointer_t ref, int is_padding)); +int trio_get_short TRIO_PROTO((trio_pointer_t ref)); /* h */ +void trio_set_shortshort TRIO_PROTO((trio_pointer_t ref, int is_shortshort)); +int trio_get_shortshort TRIO_PROTO((trio_pointer_t ref)); /* hh */ +void trio_set_short TRIO_PROTO((trio_pointer_t ref, int is_short)); +int trio_get_long TRIO_PROTO((trio_pointer_t ref)); /* l */ +void trio_set_long TRIO_PROTO((trio_pointer_t ref, int is_long)); +int trio_get_longlong TRIO_PROTO((trio_pointer_t ref)); /* ll */ +void trio_set_longlong TRIO_PROTO((trio_pointer_t ref, int is_longlong)); +int trio_get_longdouble TRIO_PROTO((trio_pointer_t ref)); /* L */ +void trio_set_longdouble TRIO_PROTO((trio_pointer_t ref, int is_longdouble)); +int trio_get_alternative TRIO_PROTO((trio_pointer_t ref)); /* # */ +void trio_set_alternative TRIO_PROTO((trio_pointer_t ref, int is_alternative)); +int trio_get_alignment TRIO_PROTO((trio_pointer_t ref)); /* - */ +void trio_set_alignment TRIO_PROTO((trio_pointer_t ref, int is_leftaligned)); +int trio_get_spacing TRIO_PROTO((trio_pointer_t ref)); /* (space) */ +void trio_set_spacing TRIO_PROTO((trio_pointer_t ref, int is_space)); +int trio_get_sign TRIO_PROTO((trio_pointer_t ref)); /* + */ +void trio_set_sign TRIO_PROTO((trio_pointer_t ref, int is_showsign)); +#if TRIO_FEATURE_QUOTE +int trio_get_quote TRIO_PROTO((trio_pointer_t ref)); /* ' */ +void trio_set_quote TRIO_PROTO((trio_pointer_t ref, int is_quote)); +#endif +int trio_get_upper TRIO_PROTO((trio_pointer_t ref)); +void trio_set_upper TRIO_PROTO((trio_pointer_t ref, int is_upper)); +#if TRIO_FEATURE_INTMAX_T +int trio_get_largest TRIO_PROTO((trio_pointer_t ref)); /* j */ +void trio_set_largest TRIO_PROTO((trio_pointer_t ref, int is_largest)); +#endif +#if TRIO_FEATURE_PTRDIFF_T +int trio_get_ptrdiff TRIO_PROTO((trio_pointer_t ref)); /* t */ +void trio_set_ptrdiff TRIO_PROTO((trio_pointer_t ref, int is_ptrdiff)); +#endif +#if TRIO_FEATURE_SIZE_T +int trio_get_size TRIO_PROTO((trio_pointer_t ref)); /* z / Z */ +void trio_set_size TRIO_PROTO((trio_pointer_t ref, int is_size)); +#endif + +/* Printing */ +int trio_print_ref TRIO_PROTO((trio_pointer_t ref, const char *format, ...)); +int trio_vprint_ref TRIO_PROTO((trio_pointer_t ref, const char *format, va_list args)); +int trio_printv_ref TRIO_PROTO((trio_pointer_t ref, const char *format, trio_pointer_t *args)); + +void trio_print_int TRIO_PROTO((trio_pointer_t ref, int number)); +void trio_print_uint TRIO_PROTO((trio_pointer_t ref, unsigned int number)); +/* void trio_print_long TRIO_PROTO((trio_pointer_t ref, long number)); */ +/* void trio_print_ulong TRIO_PROTO((trio_pointer_t ref, unsigned long number)); */ +void trio_print_double TRIO_PROTO((trio_pointer_t ref, double number)); +void trio_print_string TRIO_PROTO((trio_pointer_t ref, TRIO_CONST char *string)); +void trio_print_pointer TRIO_PROTO((trio_pointer_t ref, trio_pointer_t pointer)); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TRIO_TRIOP_H */ diff --git a/psx/mednadisc/trio/triostr.c b/psx/mednadisc/trio/triostr.c new file mode 100644 index 0000000000..d43e622c72 --- /dev/null +++ b/psx/mednadisc/trio/triostr.c @@ -0,0 +1,2385 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2001 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +/************************************************************************* + * Include files + */ + +#if defined(HAVE_CONFIG_H) +# include +#endif +#include +#include +#include +#include +#include "triodef.h" +#include "triostr.h" +#if defined(TRIO_FUNC_TO_LONG_DOUBLE) +# define USE_MATH +#endif +#if defined(USE_MATH) +# include +#endif + +/************************************************************************* + * Definitions + */ + +#if !defined(TRIO_PUBLIC_STRING) +# define TRIO_PUBLIC_STRING TRIO_PUBLIC +#endif +#if !defined(TRIO_PRIVATE_STRING) +# define TRIO_PRIVATE_STRING TRIO_PRIVATE +#endif + +#if !defined(NULL) +# define NULL 0 +#endif +#if !defined(NIL) +# define NIL ((char)0) +#endif +#if !defined(FALSE) +# define FALSE (1 == 0) +# define TRUE (! FALSE) +#endif +#if !defined(BOOLEAN_T) +# define BOOLEAN_T int +#endif + +#if defined(USE_MATH) +# if defined(PREDEF_STANDARD_C99) +# if defined(TRIO_COMPILER_DECC) +# if (TRIO_COMPILER_DECC - 0 > 80000000) +/* + * The OSF/1 runtime that comes with the DECC compiler does not support + * hexfloats conversion. + */ +# define USE_STRTOD +# define USE_STRTOF +# endif +# else +# define USE_STRTOD +# define USE_STRTOF +# endif +# else +# if defined(TRIO_COMPILER_VISUALC) +# define USE_STRTOD +# endif +#endif +#endif + +#if defined(TRIO_PLATFORM_UNIX) +# if defined(PREDEF_STANDARD_UNIX95) +# define USE_STRCASECMP +# define USE_STRNCASECMP +# endif +# if defined(TRIO_PLATFORM_SUNOS) +# define USE_SYS_ERRLIST +# else +# define USE_STRERROR +# endif +# if defined(TRIO_PLATFORM_QNX) +# define strcasecmp(x,y) stricmp(x,y) +# define strncasecmp(x,y,n) strnicmp(x,y,n) +# endif +#endif + +#if defined(TRIO_PLATFORM_WIN32) +# define USE_STRCASECMP +# if defined(TRIO_PLATFORM_WINCE) +# define strcasecmp(x,y) _stricmp(x,y) +# else +# define strcasecmp(x,y) strcmpi(x,y) +# endif +#endif + +#if !defined(HAVE_CONFIG_H) +# if !(defined(TRIO_PLATFORM_SUNOS)) +# define HAVE_TOLOWER +# define HAVE_TOUPPER +# endif +#endif + +#if defined(USE_MATH) && !defined(TRIO_NO_POWL) +# if !defined(HAVE_POWL) +# if defined(PREDEF_STANDARD_C99) \ + || defined(PREDEF_STANDARD_UNIX03) +# define HAVE_POWL +# else +# if defined(TRIO_COMPILER_VISUALC) +# if defined(powl) +# define HAVE_POWL +# endif +# endif +# endif +# endif +#endif + +#if defined(HAVE_POWL) +# define trio_powl(x,y) powl((x),(y)) +#else +# define trio_powl(x,y) pow((double)(x),(double)(y)) +#endif + +#if defined(TRIO_FUNC_TO_UPPER) \ + || (defined(TRIO_FUNC_EQUAL) && !defined(USE_STRCASECMP)) \ + || (defined(TRIO_FUNC_EQUAL_MAX) && !defined(USE_STRNCASECMP)) \ + || defined(TRIO_FUNC_MATCH) \ + || defined(TRIO_FUNC_TO_LONG_DOUBLE) \ + || defined(TRIO_FUNC_UPPER) +# define TRIO_FUNC_INTERNAL_TO_UPPER +#endif + +/************************************************************************* + * Structures + */ + +struct _trio_string_t +{ + char *content; + size_t length; + size_t allocated; +}; + +/************************************************************************* + * Constants + */ + +#if !defined(TRIO_EMBED_STRING) +static TRIO_CONST char rcsid[] = "@(#)$Id$"; +#endif + +/************************************************************************* + * Static String Functions + */ + +#if defined(TRIO_DOCUMENTATION) +# include "doc/doc_static.h" +#endif +/** @addtogroup StaticStrings + @{ +*/ + +/* + * internal_duplicate_max + */ +#if defined(TRIO_FUNC_DUPLICATE) \ + || defined(TRIO_FUNC_DUPLICATE_MAX) \ + || defined(TRIO_FUNC_STRING_DUPLICATE) \ + || defined(TRIO_FUNC_XSTRING_DUPLICATE) + +TRIO_PRIVATE_STRING char * +internal_duplicate_max +TRIO_ARGS2((source, size), + TRIO_CONST char *source, + size_t size) +{ + char *target; + + assert(source); + + /* Make room for string plus a terminating zero */ + size++; + target = trio_create(size); + if (target) + { + trio_copy_max(target, size, source); + } + return target; +} + +#endif + +/* + * internal_string_alloc + */ +#if defined(TRIO_FUNC_STRING_CREATE) \ + || defined(TRIO_FUNC_STRING_DUPLICATE) \ + || defined(TRIO_FUNC_XSTRING_DUPLICATE) + +TRIO_PRIVATE_STRING trio_string_t * +internal_string_alloc(TRIO_NOARGS) +{ + trio_string_t *self; + + self = (trio_string_t *)TRIO_MALLOC(sizeof(trio_string_t)); + if (self) + { + self->content = NULL; + self->length = 0; + self->allocated = 0; + } + return self; +} + +#endif + +/* + * internal_string_grow + * + * The size of the string will be increased by 'delta' characters. If + * 'delta' is zero, the size will be doubled. + */ +#if defined(TRIO_FUNC_STRING_CREATE) \ + || defined(TRIO_FUNC_STRING_APPEND) \ + || defined(TRIO_FUNC_XSTRING_APPEND) \ + || defined(TRIO_FUNC_XSTRING_APPEND_CHAR) + +TRIO_PRIVATE_STRING BOOLEAN_T +internal_string_grow +TRIO_ARGS2((self, delta), + trio_string_t *self, + size_t delta) +{ + BOOLEAN_T status = FALSE; + char *new_content; + size_t new_size; + + new_size = (delta == 0) + ? ( (self->allocated == 0) ? 1 : self->allocated * 2 ) + : self->allocated + delta; + + new_content = (char *)TRIO_REALLOC(self->content, new_size); + if (new_content) + { + self->content = new_content; + self->allocated = new_size; + status = TRUE; + } + return status; +} + +#endif + +/* + * internal_string_grow_to + * + * The size of the string will be increased to 'length' plus one characters. + * If 'length' is less than the original size, the original size will be + * used (that is, the size of the string is never decreased). + */ +#if defined(TRIO_FUNC_STRING_APPEND) \ + || defined(TRIO_FUNC_XSTRING_APPEND) \ + || defined(TRIO_FUNC_XSTRING_APPEND_MAX) + +TRIO_PRIVATE_STRING BOOLEAN_T +internal_string_grow_to +TRIO_ARGS2((self, length), + trio_string_t *self, + size_t length) +{ + length++; /* Room for terminating zero */ + return (self->allocated < length) + ? internal_string_grow(self, length - self->allocated) + : TRUE; +} + +#endif + +#if defined(TRIO_FUNC_INTERNAL_TO_UPPER) + +TRIO_PRIVATE_STRING int +internal_to_upper +TRIO_ARGS1((source), + int source) +{ +# if defined(HAVE_TOUPPER) + + return toupper(source); + +# else + + /* Does not handle locales or non-contiguous alphabetic characters */ + return ((source >= (int)'a') && (source <= (int)'z')) + ? source - 'a' + 'A' + : source; + +# endif +} + +#endif + + +/** + Create new string. + + @param size Size of new string. + @return Pointer to string, or NULL if allocation failed. +*/ +#if defined(TRIO_FUNC_CREATE) + +TRIO_PUBLIC_STRING char * +trio_create +TRIO_ARGS1((size), + size_t size) +{ + return (char *)TRIO_MALLOC(size); +} + +#endif + +/** + Destroy string. + + @param string String to be freed. +*/ +#if defined(TRIO_FUNC_DESTROY) + +TRIO_PUBLIC_STRING void +trio_destroy +TRIO_ARGS1((string), + char *string) +{ + if (string) + { + TRIO_FREE(string); + } +} + +#endif + +/** + Count the number of characters in a string. + + @param string String to measure. + @return Number of characters in @p string. +*/ +#if defined(TRIO_FUNC_LENGTH) + +TRIO_PUBLIC_STRING size_t +trio_length +TRIO_ARGS1((string), + TRIO_CONST char *string) +{ + return strlen(string); +} + +#endif + +/** + Count at most @p max characters in a string. + + @param string String to measure. + @param max Maximum number of characters to count. + @return The maximum value of @p max and number of characters in @p string. +*/ +#if defined(TRIO_FUNC_LENGTH_MAX) + +TRIO_PUBLIC_STRING size_t +trio_length_max +TRIO_ARGS2((string, max), + TRIO_CONST char *string, + size_t max) +{ + size_t i; + + for (i = 0; i < max; ++i) + { + if (string[i] == 0) + break; + } + return i; +} + +#endif + +/** + Append @p source at the end of @p target. + + @param target Target string. + @param source Source string. + @return Boolean value indicating success or failure. + + @pre @p target must point to a memory chunk with sufficient room to + contain the @p target string and @p source string. + @pre No boundary checking is performed, so insufficient memory will + result in a buffer overrun. + @post @p target will be zero terminated. +*/ +#if defined(TRIO_FUNC_APPEND) + +TRIO_PUBLIC_STRING int +trio_append +TRIO_ARGS2((target, source), + char *target, + TRIO_CONST char *source) +{ + assert(target); + assert(source); + + return (strcat(target, source) != NULL); +} + +#endif + +/** + Append at most @p max characters from @p source to @p target. + + @param target Target string. + @param max Maximum number of characters to append. + @param source Source string. + @return Boolean value indicating success or failure. + + @pre @p target must point to a memory chuck with sufficient room to + contain the @p target string and the @p source string (at most @p max + characters). + @pre No boundary checking is performed, so insufficient memory will + result in a buffer overrun. + @post @p target will be zero terminated. +*/ +#if defined(TRIO_FUNC_APPEND_MAX) + +TRIO_PUBLIC_STRING int +trio_append_max +TRIO_ARGS3((target, max, source), + char *target, + size_t max, + TRIO_CONST char *source) +{ + size_t length; + + assert(target); + assert(source); + + length = trio_length(target); + + if (max > length) + { + strncat(target, source, max - length - 1); + } + return TRUE; +} + +#endif + +/** + Determine if a string contains a substring. + + @param string String to be searched. + @param substring String to be found. + @return Boolean value indicating success or failure. +*/ +#if defined(TRIO_FUNC_CONTAINS) + +TRIO_PUBLIC_STRING int +trio_contains +TRIO_ARGS2((string, substring), + TRIO_CONST char *string, + TRIO_CONST char *substring) +{ + assert(string); + assert(substring); + + return (0 != strstr(string, substring)); +} + +#endif + +/** + Copy @p source to @p target. + + @param target Target string. + @param source Source string. + @return Boolean value indicating success or failure. + + @pre @p target must point to a memory chunk with sufficient room to + contain the @p source string. + @pre No boundary checking is performed, so insufficient memory will + result in a buffer overrun. + @post @p target will be zero terminated. +*/ +#if defined(TRIO_FUNC_COPY) + +TRIO_PUBLIC_STRING int +trio_copy +TRIO_ARGS2((target, source), + char *target, + TRIO_CONST char *source) +{ + assert(target); + assert(source); + + (void)strcpy(target, source); + return TRUE; +} + +#endif + +/** + Copy at most @p max - 1 characters from @p source to @p target. + + @param target Target string. + @param max Maximum number of characters to append (one of which is + a NUL terminator). In other words @p source must point to at least + @p max - 1 bytes, but @p target must point to at least @p max + bytes. + @param source Source string. + @return Boolean value indicating success or failure. + + @pre @p target must point to a memory chunk with sufficient room to + contain the @p source string and a NUL terminator (at most @p max + bytes total). + @pre No boundary checking is performed, so insufficient memory will + result in a buffer overrun. + @post @p target will be zero terminated. +*/ +#if defined(TRIO_FUNC_COPY_MAX) + +TRIO_PUBLIC_STRING int +trio_copy_max +TRIO_ARGS3((target, max, source), + char *target, + size_t max, + TRIO_CONST char *source) +{ + assert(target); + assert(source); + assert(max > 0); /* Includes != 0 */ + + (void)strncpy(target, source, max - 1); + target[max - 1] = (char)0; + return TRUE; +} + +#endif + +/** + Duplicate @p source. + + @param source Source string. + @return A copy of the @p source string. + + @post @p target will be zero terminated. +*/ +#if defined(TRIO_FUNC_DUPLICATE) + +TRIO_PUBLIC_STRING char * +trio_duplicate +TRIO_ARGS1((source), + TRIO_CONST char *source) +{ + return internal_duplicate_max(source, trio_length(source)); +} + +#endif + +/** + Duplicate at most @p max characters of @p source. + + @param source Source string. + @param max Maximum number of characters to duplicate. + @return A copy of the @p source string. + + @post @p target will be zero terminated. +*/ +#if defined(TRIO_FUNC_DUPLICATE_MAX) + +TRIO_PUBLIC_STRING char * +trio_duplicate_max +TRIO_ARGS2((source, max), + TRIO_CONST char *source, + size_t max) +{ + size_t length; + + assert(source); + assert(max > 0); + + length = trio_length(source); + if (length > max) + { + length = max; + } + return internal_duplicate_max(source, length); +} + +#endif + +/** + Compare if two strings are equal. + + @param first First string. + @param second Second string. + @return Boolean indicating whether the two strings are equal or not. + + Case-insensitive comparison. +*/ +#if defined(TRIO_FUNC_EQUAL) + +TRIO_PUBLIC_STRING int +trio_equal +TRIO_ARGS2((first, second), + TRIO_CONST char *first, + TRIO_CONST char *second) +{ + assert(first); + assert(second); + + if ((first != NULL) && (second != NULL)) + { +# if defined(USE_STRCASECMP) + return (0 == strcasecmp(first, second)); +# else + while ((*first != NIL) && (*second != NIL)) + { + if (internal_to_upper(*first) != internal_to_upper(*second)) + { + break; + } + first++; + second++; + } + return ((*first == NIL) && (*second == NIL)); +# endif + } + return FALSE; +} + +#endif + +/** + Compare if two strings are equal. + + @param first First string. + @param second Second string. + @return Boolean indicating whether the two strings are equal or not. + + Case-sensitive comparison. +*/ +#if defined(TRIO_FUNC_EQUAL_CASE) + +TRIO_PUBLIC_STRING int +trio_equal_case +TRIO_ARGS2((first, second), + TRIO_CONST char *first, + TRIO_CONST char *second) +{ + assert(first); + assert(second); + + if ((first != NULL) && (second != NULL)) + { + return (0 == strcmp(first, second)); + } + return FALSE; +} + +#endif + +/** + Compare if two strings up until the first @p max characters are equal. + + @param first First string. + @param max Maximum number of characters to compare. + @param second Second string. + @return Boolean indicating whether the two strings are equal or not. + + Case-sensitive comparison. +*/ +#if defined(TRIO_FUNC_EQUAL_CASE_MAX) + +TRIO_PUBLIC_STRING int +trio_equal_case_max +TRIO_ARGS3((first, max, second), + TRIO_CONST char *first, + size_t max, + TRIO_CONST char *second) +{ + assert(first); + assert(second); + + if ((first != NULL) && (second != NULL)) + { + return (0 == strncmp(first, second, max)); + } + return FALSE; +} + +#endif + +/** + Compare if two strings are equal. + + @param first First string. + @param second Second string. + @return Boolean indicating whether the two strings are equal or not. + + Collating characters are considered equal. +*/ +#if defined(TRIO_FUNC_EQUAL_LOCALE) + +TRIO_PUBLIC_STRING int +trio_equal_locale +TRIO_ARGS2((first, second), + TRIO_CONST char *first, + TRIO_CONST char *second) +{ + assert(first); + assert(second); + +# if defined(LC_COLLATE) + return (strcoll(first, second) == 0); +# else + return trio_equal(first, second); +# endif +} + +#endif + +/** + Compare if two strings up until the first @p max characters are equal. + + @param first First string. + @param max Maximum number of characters to compare. + @param second Second string. + @return Boolean indicating whether the two strings are equal or not. + + Case-insensitive comparison. +*/ +#if defined(TRIO_FUNC_EQUAL_MAX) + +TRIO_PUBLIC_STRING int +trio_equal_max +TRIO_ARGS3((first, max, second), + TRIO_CONST char *first, + size_t max, + TRIO_CONST char *second) +{ + assert(first); + assert(second); + + if ((first != NULL) && (second != NULL)) + { +# if defined(USE_STRNCASECMP) + return (0 == strncasecmp(first, second, max)); +# else + /* Not adequately tested yet */ + size_t cnt = 0; + while ((*first != NIL) && (*second != NIL) && (cnt <= max)) + { + if (internal_to_upper(*first) != internal_to_upper(*second)) + { + break; + } + first++; + second++; + cnt++; + } + return ((cnt == max) || ((*first == NIL) && (*second == NIL))); +# endif + } + return FALSE; +} + +#endif + +/** + Provide a textual description of an error code (errno). + + @param error_number Error number. + @return Textual description of @p error_number. +*/ +#if defined(TRIO_FUNC_ERROR) + +TRIO_PUBLIC_STRING TRIO_CONST char * +trio_error +TRIO_ARGS1((error_number), + int error_number) +{ +# if defined(USE_STRERROR) + + return strerror(error_number); + +# else +# if defined(USE_SYS_ERRLIST) + + extern char *sys_errlist[]; + extern int sys_nerr; + + return ((error_number < 0) || (error_number >= sys_nerr)) + ? "unknown" + : sys_errlist[error_number]; + +# else + + return "unknown"; + +# endif +# endif +} + +#endif + +/** + Format the date/time according to @p format. + + @param target Target string. + @param max Maximum number of characters to format. + @param format Formatting string. + @param datetime Date/time structure. + @return Number of formatted characters. + + The formatting string accepts the same specifiers as the standard C + function strftime. +*/ +#if defined(TRIO_FUNC_FORMAT_DATE_MAX) + +TRIO_PUBLIC_STRING size_t +trio_format_date_max +TRIO_ARGS4((target, max, format, datetime), + char *target, + size_t max, + TRIO_CONST char *format, + TRIO_CONST struct tm *datetime) +{ + assert(target); + assert(format); + assert(datetime); + assert(max > 0); + + return strftime(target, max, format, datetime); +} + +#endif + +/** + Calculate a hash value for a string. + + @param string String to be calculated on. + @param type Hash function. + @return Calculated hash value. + + @p type can be one of the following + @li @c TRIO_HASH_PLAIN Plain hash function. +*/ +#if defined(TRIO_FUNC_HASH) + +TRIO_PUBLIC_STRING unsigned long +trio_hash +TRIO_ARGS2((string, type), + TRIO_CONST char *string, + int type) +{ + unsigned long value = 0L; + char ch; + + assert(string); + + switch (type) + { + case TRIO_HASH_PLAIN: + while ( (ch = *string++) != NIL ) + { + value *= 31; + value += (unsigned long)ch; + } + break; + default: + assert(FALSE); + break; + } + return value; +} + +#endif + +/** + Find first occurrence of a character in a string. + + @param string String to be searched. + @param character Character to be found. + @return A pointer to the found character, or NULL if character was not found. + */ +#if defined(TRIO_FUNC_INDEX) + +TRIO_PUBLIC_STRING char * +trio_index +TRIO_ARGS2((string, character), + TRIO_CONST char *string, + int character) +{ + assert(string); + + return strchr(string, character); +} + +#endif + +/** + Find last occurrence of a character in a string. + + @param string String to be searched. + @param character Character to be found. + @return A pointer to the found character, or NULL if character was not found. + */ +#if defined(TRIO_FUNC_INDEX_LAST) + +TRIO_PUBLIC_STRING char * +trio_index_last +TRIO_ARGS2((string, character), + TRIO_CONST char *string, + int character) +{ + assert(string); + + return strchr(string, character); +} + +#endif + +/** + Convert the alphabetic letters in the string to lower-case. + + @param target String to be converted. + @return Number of processed characters (converted or not). +*/ +#if defined(TRIO_FUNC_LOWER) + +TRIO_PUBLIC_STRING int +trio_lower +TRIO_ARGS1((target), + char *target) +{ + assert(target); + + return trio_span_function(target, target, trio_to_lower); +} + +#endif + +/** + Compare two strings using wildcards. + + @param string String to be searched. + @param pattern Pattern, including wildcards, to search for. + @return Boolean value indicating success or failure. + + Case-insensitive comparison. + + The following wildcards can be used + @li @c * Match any number of characters. + @li @c ? Match a single character. +*/ +#if defined(TRIO_FUNC_MATCH) + +TRIO_PUBLIC_STRING int +trio_match +TRIO_ARGS2((string, pattern), + TRIO_CONST char *string, + TRIO_CONST char *pattern) +{ + assert(string); + assert(pattern); + + for (; ('*' != *pattern); ++pattern, ++string) + { + if (NIL == *string) + { + return (NIL == *pattern); + } + if ((internal_to_upper((int)*string) != internal_to_upper((int)*pattern)) + && ('?' != *pattern)) + { + return FALSE; + } + } + /* two-line patch to prevent *too* much recursiveness: */ + while ('*' == pattern[1]) + pattern++; + + do + { + if ( trio_match(string, &pattern[1]) ) + { + return TRUE; + } + } + while (*string++); + + return FALSE; +} + +#endif + +/** + Compare two strings using wildcards. + + @param string String to be searched. + @param pattern Pattern, including wildcards, to search for. + @return Boolean value indicating success or failure. + + Case-sensitive comparison. + + The following wildcards can be used + @li @c * Match any number of characters. + @li @c ? Match a single character. +*/ +#if defined(TRIO_FUNC_MATCH_CASE) + +TRIO_PUBLIC_STRING int +trio_match_case +TRIO_ARGS2((string, pattern), + TRIO_CONST char *string, + TRIO_CONST char *pattern) +{ + assert(string); + assert(pattern); + + for (; ('*' != *pattern); ++pattern, ++string) + { + if (NIL == *string) + { + return (NIL == *pattern); + } + if ((*string != *pattern) + && ('?' != *pattern)) + { + return FALSE; + } + } + /* two-line patch to prevent *too* much recursiveness: */ + while ('*' == pattern[1]) + pattern++; + + do + { + if ( trio_match_case(string, &pattern[1]) ) + { + return TRUE; + } + } + while (*string++); + + return FALSE; +} + +#endif + +/** + Execute a function on each character in string. + + @param target Target string. + @param source Source string. + @param Function Function to be executed. + @return Number of processed characters. +*/ +#if defined(TRIO_FUNC_SPAN_FUNCTION) + +TRIO_PUBLIC_STRING size_t +trio_span_function +TRIO_ARGS3((target, source, Function), + char *target, + TRIO_CONST char *source, + int (*Function) TRIO_PROTO((int))) +{ + size_t count = 0; + + assert(target); + assert(source); + assert(Function); + + while (*source != NIL) + { + *target++ = Function(*source++); + count++; + } + return count; +} + +#endif + +/** + Search for a substring in a string. + + @param string String to be searched. + @param substring String to be found. + @return Pointer to first occurrence of @p substring in @p string, or NULL + if no match was found. +*/ +#if defined(TRIO_FUNC_SUBSTRING) + +TRIO_PUBLIC_STRING char * +trio_substring +TRIO_ARGS2((string, substring), + TRIO_CONST char *string, + TRIO_CONST char *substring) +{ + assert(string); + assert(substring); + + return strstr(string, substring); +} + +#endif + +/** + Search for a substring in the first @p max characters of a string. + + @param string String to be searched. + @param max Maximum characters to be searched. + @param substring String to be found. + @return Pointer to first occurrence of @p substring in @p string, or NULL + if no match was found. +*/ +#if defined(TRIO_FUNC_SUBSTRING_MAX) + +TRIO_PUBLIC_STRING char * +trio_substring_max +TRIO_ARGS3((string, max, substring), + TRIO_CONST char *string, + size_t max, + TRIO_CONST char *substring) +{ + size_t count; + size_t size; + char *result = NULL; + + assert(string); + assert(substring); + + size = trio_length(substring); + if (size <= max) + { + for (count = 0; count <= max - size; count++) + { + if (trio_equal_max(substring, size, &string[count])) + { + result = (char *)&string[count]; + break; + } + } + } + return result; +} + +#endif + +/** + Tokenize string. + + @param string String to be tokenized. + @param delimiters String containing list of delimiting characters. + @return Start of new token. + + @warning @p string will be destroyed. +*/ +#if defined(TRIO_FUNC_TOKENIZE) + +TRIO_PUBLIC_STRING char * +trio_tokenize +TRIO_ARGS2((string, delimiters), + char *string, + TRIO_CONST char *delimiters) +{ + assert(delimiters); + + return strtok(string, delimiters); +} + +#endif + +/** + Convert string to floating-point number. + + @param source String to be converted. + @param endp Pointer to end of the converted string. + @return A floating-point number. + + The following Extended Backus-Naur form is used + @verbatim + double ::= [ ] + ( | + | + ) + [ [ ] ] + number ::= 1*( ) + digit ::= ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) + exponential ::= ( 'e' | 'E' ) + sign ::= ( '-' | '+' ) + decimal_point ::= '.' + @endverbatim +*/ +#if defined(TRIO_FUNC_TO_LONG_DOUBLE) + +/* FIXME: Add EBNF for hex-floats */ +TRIO_PUBLIC_STRING trio_long_double_t +trio_to_long_double +TRIO_ARGS2((source, endp), + TRIO_CONST char *source, + char **endp) +{ +# if defined(USE_STRTOLD) + return strtold(source, endp); +# else + int isNegative = FALSE; + int isExponentNegative = FALSE; + trio_long_double_t integer = 0.0; + trio_long_double_t fraction = 0.0; + unsigned long exponent = 0; + trio_long_double_t base; + trio_long_double_t fracdiv = 1.0; + trio_long_double_t value = 0.0; + + /* First try hex-floats */ + if ((source[0] == '0') && ((source[1] == 'x') || (source[1] == 'X'))) + { + base = 16.0; + source += 2; + while (isxdigit((int)*source)) + { + integer *= base; + integer += (isdigit((int)*source) + ? (*source - '0') + : 10 + (internal_to_upper((int)*source) - 'A')); + source++; + } + if (*source == '.') + { + source++; + while (isxdigit((int)*source)) + { + fracdiv /= base; + fraction += fracdiv * (isdigit((int)*source) + ? (*source - '0') + : 10 + (internal_to_upper((int)*source) - 'A')); + source++; + } + if ((*source == 'p') || (*source == 'P')) + { + source++; + if ((*source == '+') || (*source == '-')) + { + isExponentNegative = (*source == '-'); + source++; + } + while (isdigit((int)*source)) + { + exponent *= 10; + exponent += (*source - '0'); + source++; + } + } + } + /* For later use with exponent */ + base = 2.0; + } + else /* Then try normal decimal floats */ + { + base = 10.0; + isNegative = (*source == '-'); + /* Skip sign */ + if ((*source == '+') || (*source == '-')) + source++; + + /* Integer part */ + while (isdigit((int)*source)) + { + integer *= base; + integer += (*source - '0'); + source++; + } + + if (*source == '.') + { + source++; /* skip decimal point */ + while (isdigit((int)*source)) + { + fracdiv /= base; + fraction += (*source - '0') * fracdiv; + source++; + } + } + if ((*source == 'e') + || (*source == 'E') +# if TRIO_MICROSOFT + || (*source == 'd') + || (*source == 'D') +# endif + ) + { + source++; /* Skip exponential indicator */ + isExponentNegative = (*source == '-'); + if ((*source == '+') || (*source == '-')) + source++; + while (isdigit((int)*source)) + { + exponent *= (int)base; + exponent += (*source - '0'); + source++; + } + } + } + + value = integer + fraction; + if (exponent != 0) + { + if (isExponentNegative) + value /= trio_powl(base, (trio_long_double_t)exponent); + else + value *= trio_powl(base, (trio_long_double_t)exponent); + } + if (isNegative) + value = -value; + + if (endp) + *endp = (char *)source; + return value; +# endif +} + +#endif + +/** + Convert string to floating-point number. + + @param source String to be converted. + @param endp Pointer to end of the converted string. + @return A floating-point number. + + See @ref trio_to_long_double. +*/ +#if defined(TRIO_FUNC_TO_DOUBLE) + +TRIO_PUBLIC_STRING double +trio_to_double +TRIO_ARGS2((source, endp), + TRIO_CONST char *source, + char **endp) +{ +#if defined(USE_STRTOD) + return strtod(source, endp); +#else + return (double)trio_to_long_double(source, endp); +#endif +} + +#endif + +/** + Convert string to floating-point number. + + @param source String to be converted. + @param endp Pointer to end of the converted string. + @return A floating-point number. + + See @ref trio_to_long_double. +*/ +#if defined(TRIO_FUNC_TO_FLOAT) + +TRIO_PUBLIC_STRING float +trio_to_float +TRIO_ARGS2((source, endp), + TRIO_CONST char *source, + char **endp) +{ +# if defined(USE_STRTOF) + return strtof(source, endp); +# else + return (float)trio_to_long_double(source, endp); +# endif +} + +#endif + +/** + Convert string to signed integer. + + @param string String to be converted. + @param endp Pointer to end of converted string. + @param base Radix number of number. +*/ +#if defined(TRIO_FUNC_TO_LONG) + +TRIO_PUBLIC_STRING long +trio_to_long +TRIO_ARGS3((string, endp, base), + TRIO_CONST char *string, + char **endp, + int base) +{ + assert(string); + assert((base >= 2) && (base <= 36)); + + return strtol(string, endp, base); +} + +#endif + +/** + Convert one alphabetic letter to lower-case. + + @param source The letter to be converted. + @return The converted letter. +*/ +#if defined(TRIO_FUNC_TO_LOWER) + +TRIO_PUBLIC_STRING int +trio_to_lower +TRIO_ARGS1((source), + int source) +{ +# if defined(HAVE_TOLOWER) + + return tolower(source); + +# else + + /* Does not handle locales or non-contiguous alphabetic characters */ + return ((source >= (int)'A') && (source <= (int)'Z')) + ? source - 'A' + 'a' + : source; + +# endif +} + +#endif + +/** + Convert string to unsigned integer. + + @param string String to be converted. + @param endp Pointer to end of converted string. + @param base Radix number of number. +*/ +#if defined(TRIO_FUNC_TO_UNSIGNED_LONG) + +TRIO_PUBLIC_STRING unsigned long +trio_to_unsigned_long +TRIO_ARGS3((string, endp, base), + TRIO_CONST char *string, + char **endp, + int base) +{ + assert(string); + assert((base >= 2) && (base <= 36)); + + return strtoul(string, endp, base); +} + +#endif + +/** + Convert one alphabetic letter to upper-case. + + @param source The letter to be converted. + @return The converted letter. +*/ +#if defined(TRIO_FUNC_TO_UPPER) + +TRIO_PUBLIC_STRING int +trio_to_upper +TRIO_ARGS1((source), + int source) +{ + return internal_to_upper(source); +} + +#endif + +/** + Convert the alphabetic letters in the string to upper-case. + + @param target The string to be converted. + @return The number of processed characters (converted or not). +*/ +#if defined(TRIO_FUNC_UPPER) + +TRIO_PUBLIC_STRING int +trio_upper +TRIO_ARGS1((target), + char *target) +{ + assert(target); + + return trio_span_function(target, target, internal_to_upper); +} + +#endif + +/** @} End of StaticStrings */ + + +/************************************************************************* + * Dynamic String Functions + */ + +#if defined(TRIO_DOCUMENTATION) +# include "doc/doc_dynamic.h" +#endif +/** @addtogroup DynamicStrings + @{ +*/ + +/** + Create a new dynamic string. + + @param initial_size Initial size of the buffer. + @return Newly allocated dynamic string, or NULL if memory allocation failed. +*/ +#if defined(TRIO_FUNC_STRING_CREATE) + +TRIO_PUBLIC_STRING trio_string_t * +trio_string_create +TRIO_ARGS1((initial_size), + int initial_size) +{ + trio_string_t *self; + + self = internal_string_alloc(); + if (self) + { + if (internal_string_grow(self, + (size_t)((initial_size > 0) ? initial_size : 1))) + { + self->content[0] = (char)0; + self->allocated = initial_size; + } + else + { + trio_string_destroy(self); + self = NULL; + } + } + return self; +} + +#endif + +/** + Deallocate the dynamic string and its contents. + + @param self Dynamic string +*/ +#if defined(TRIO_FUNC_STRING_DESTROY) + +TRIO_PUBLIC_STRING void +trio_string_destroy +TRIO_ARGS1((self), + trio_string_t *self) +{ + assert(self); + + if (self) + { + trio_destroy(self->content); + TRIO_FREE(self); + } +} + +#endif + +/** + Get a pointer to the content. + + @param self Dynamic string. + @param offset Offset into content. + @return Pointer to the content. + + @p Offset can be zero, positive, or negative. If @p offset is zero, + then the start of the content will be returned. If @p offset is positive, + then a pointer to @p offset number of characters from the beginning of the + content is returned. If @p offset is negative, then a pointer to @p offset + number of characters from the ending of the string, starting at the + terminating zero, is returned. +*/ +#if defined(TRIO_FUNC_STRING_GET) + +TRIO_PUBLIC_STRING char * +trio_string_get +TRIO_ARGS2((self, offset), + trio_string_t *self, + int offset) +{ + char *result = NULL; + + assert(self); + + if (self->content != NULL) + { + if (self->length == 0) + { + (void)trio_string_length(self); + } + if (offset >= 0) + { + if (offset > (int)self->length) + { + offset = self->length; + } + } + else + { + offset += self->length + 1; + if (offset < 0) + { + offset = 0; + } + } + result = &(self->content[offset]); + } + return result; +} + +#endif + +/** + Extract the content. + + @param self Dynamic String + @return Content of dynamic string. + + The content is removed from the dynamic string. This enables destruction + of the dynamic string without deallocation of the content. +*/ +#if defined(TRIO_FUNC_STRING_EXTRACT) + +TRIO_PUBLIC_STRING char * +trio_string_extract +TRIO_ARGS1((self), + trio_string_t *self) +{ + char *result; + + assert(self); + + result = self->content; + /* FIXME: Allocate new empty buffer? */ + self->content = NULL; + self->length = self->allocated = 0; + return result; +} + +#endif + +/** + Set the content of the dynamic string. + + @param self Dynamic String + @param buffer The new content. + + Sets the content of the dynamic string to a copy @p buffer. + An existing content will be deallocated first, if necessary. + + @remark + This function will make a copy of @p buffer. + You are responsible for deallocating @p buffer yourself. +*/ +#if defined(TRIO_FUNC_XSTRING_SET) + +TRIO_PUBLIC_STRING void +trio_xstring_set +TRIO_ARGS2((self, buffer), + trio_string_t *self, + char *buffer) +{ + assert(self); + + trio_destroy(self->content); + self->content = trio_duplicate(buffer); +} + +#endif + +/* + * trio_string_size + */ +#if defined(TRIO_FUNC_STRING_SIZE) + +TRIO_PUBLIC_STRING int +trio_string_size +TRIO_ARGS1((self), + trio_string_t *self) +{ + assert(self); + + return self->allocated; +} + +#endif + +/* + * trio_string_terminate + */ +#if defined(TRIO_FUNC_STRING_TERMINATE) + +TRIO_PUBLIC_STRING void +trio_string_terminate +TRIO_ARGS1((self), + trio_string_t *self) +{ + trio_xstring_append_char(self, 0); +} + +#endif + +/** + Append the second string to the first. + + @param self Dynamic string to be modified. + @param other Dynamic string to copy from. + @return Boolean value indicating success or failure. +*/ +#if defined(TRIO_FUNC_STRING_APPEND) + +TRIO_PUBLIC_STRING int +trio_string_append +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + size_t length; + + assert(self); + assert(other); + + length = self->length + other->length; + if (!internal_string_grow_to(self, length)) + goto error; + trio_copy(&self->content[self->length], other->content); + self->length = length; + return TRUE; + + error: + return FALSE; +} + +#endif + + +/* + * trio_xstring_append + */ +#if defined(TRIO_FUNC_XSTRING_APPEND) + +TRIO_PUBLIC_STRING int +trio_xstring_append +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + size_t length; + + assert(self); + assert(other); + + length = self->length + trio_length(other); + if (!internal_string_grow_to(self, length)) + goto error; + trio_copy(&self->content[self->length], other); + self->length = length; + return TRUE; + + error: + return FALSE; +} + +#endif + +/* + * trio_xstring_append_char + */ +#if defined(TRIO_FUNC_XSTRING_APPEND_CHAR) + +TRIO_PUBLIC_STRING int +trio_xstring_append_char +TRIO_ARGS2((self, character), + trio_string_t *self, + char character) +{ + assert(self); + + if ((int)self->length >= trio_string_size(self)) + { + if (!internal_string_grow(self, 0)) + goto error; + } + self->content[self->length] = character; + self->length++; + return TRUE; + + error: + return FALSE; +} + +#endif + +/* + * trio_xstring_append_max + */ +#if defined(TRIO_FUNC_XSTRING_APPEND_MAX) + +TRIO_PUBLIC_STRING int +trio_xstring_append_max +TRIO_ARGS3((self, other, max), + trio_string_t *self, + TRIO_CONST char *other, + size_t max) +{ + size_t length; + + assert(self); + assert(other); + + length = self->length + trio_length_max(other, max); + if (!internal_string_grow_to(self, length)) + goto error; + + /* + * Pass max + 1 since trio_copy_max copies one character less than + * this from the source to make room for a terminating zero. + */ + trio_copy_max(&self->content[self->length], max + 1, other); + self->length = length; + return TRUE; + + error: + return FALSE; +} + +#endif + +/** + Search for the first occurrence of second parameter in the first. + + @param self Dynamic string to be modified. + @param other Dynamic string to copy from. + @return Boolean value indicating success or failure. +*/ +#if defined(TRIO_FUNC_STRING_CONTAINS) + +TRIO_PUBLIC_STRING int +trio_string_contains +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_contains(self->content, other->content); +} + +#endif + +/* + * trio_xstring_contains + */ +#if defined(TRIO_FUNC_XSTRING_CONTAINS) + +TRIO_PUBLIC_STRING int +trio_xstring_contains +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_contains(self->content, other); +} + +#endif + +/* + * trio_string_copy + */ +#if defined(TRIO_FUNC_STRING_COPY) + +TRIO_PUBLIC_STRING int +trio_string_copy +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + self->length = 0; + return trio_string_append(self, other); +} + +#endif + + +/* + * trio_xstring_copy + */ +#if defined(TRIO_FUNC_XSTRING_COPY) + +TRIO_PUBLIC_STRING int +trio_xstring_copy +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + self->length = 0; + return trio_xstring_append(self, other); +} + +#endif + +/* + * trio_string_duplicate + */ +#if defined(TRIO_FUNC_STRING_DUPLICATE) + +TRIO_PUBLIC_STRING trio_string_t * +trio_string_duplicate +TRIO_ARGS1((other), + trio_string_t *other) +{ + trio_string_t *self; + + assert(other); + + self = internal_string_alloc(); + if (self) + { + self->content = internal_duplicate_max(other->content, other->length); + if (self->content) + { + self->length = other->length; + self->allocated = self->length + 1; + } + else + { + self->length = self->allocated = 0; + } + } + return self; +} + +#endif + +/* + * trio_xstring_duplicate + */ +#if defined(TRIO_FUNC_XSTRING_DUPLICATE) + +TRIO_PUBLIC_STRING trio_string_t * +trio_xstring_duplicate +TRIO_ARGS1((other), + TRIO_CONST char *other) +{ + trio_string_t *self; + + assert(other); + + self = internal_string_alloc(); + if (self) + { + self->content = internal_duplicate_max(other, trio_length(other)); + if (self->content) + { + self->length = trio_length(self->content); + self->allocated = self->length + 1; + } + else + { + self->length = self->allocated = 0; + } + } + return self; +} + +#endif + +/* + * trio_string_equal + */ +#if defined(TRIO_FUNC_STRING_EQUAL) + +TRIO_PUBLIC_STRING int +trio_string_equal +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_equal(self->content, other->content); +} + +#endif + + +/* + * trio_xstring_equal + */ +#if defined(TRIO_FUNC_XSTRING_EQUAL) + +TRIO_PUBLIC_STRING int +trio_xstring_equal +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_equal(self->content, other); +} + +#endif + +/* + * trio_string_equal_max + */ +#if defined(TRIO_FUNC_STRING_EQUAL_MAX) + +TRIO_PUBLIC_STRING int +trio_string_equal_max +TRIO_ARGS3((self, max, other), + trio_string_t *self, + size_t max, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_equal_max(self->content, max, other->content); +} +#endif + +/* + * trio_xstring_equal_max + */ +#if defined(TRIO_FUNC_XSTRING_EQUAL_MAX) + +TRIO_PUBLIC_STRING int +trio_xstring_equal_max +TRIO_ARGS3((self, max, other), + trio_string_t *self, + size_t max, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_equal_max(self->content, max, other); +} + +#endif + +/* + * trio_string_equal_case + */ +#if defined(TRIO_FUNC_STRING_EQUAL_CASE) + +TRIO_PUBLIC_STRING int +trio_string_equal_case +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_equal_case(self->content, other->content); +} + +#endif + +/* + * trio_xstring_equal_case + */ +#if defined(TRIO_FUNC_XSTRING_EQUAL_CASE) + +TRIO_PUBLIC_STRING int +trio_xstring_equal_case +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_equal_case(self->content, other); +} + +#endif + +/* + * trio_string_equal_case_max + */ +#if defined(TRIO_FUNC_STRING_EQUAL_CASE_MAX) + +TRIO_PUBLIC_STRING int +trio_string_equal_case_max +TRIO_ARGS3((self, max, other), + trio_string_t *self, + size_t max, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_equal_case_max(self->content, max, other->content); +} + +#endif + +/* + * trio_xstring_equal_case_max + */ +#if defined(TRIO_FUNC_XSTRING_EQUAL_CASE_MAX) + +TRIO_PUBLIC_STRING int +trio_xstring_equal_case_max +TRIO_ARGS3((self, max, other), + trio_string_t *self, + size_t max, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_equal_case_max(self->content, max, other); +} + +#endif + +/* + * trio_string_format_data_max + */ +#if defined(TRIO_FUNC_STRING_FORMAT_DATE_MAX) + +TRIO_PUBLIC_STRING size_t +trio_string_format_date_max +TRIO_ARGS4((self, max, format, datetime), + trio_string_t *self, + size_t max, + TRIO_CONST char *format, + TRIO_CONST struct tm *datetime) +{ + assert(self); + + return trio_format_date_max(self->content, max, format, datetime); +} + +#endif + +/* + * trio_string_index + */ +#if defined(TRIO_FUNC_STRING_INDEX) + +TRIO_PUBLIC_STRING char * +trio_string_index +TRIO_ARGS2((self, character), + trio_string_t *self, + int character) +{ + assert(self); + + return trio_index(self->content, character); +} + +#endif + +/* + * trio_string_index_last + */ +#if defined(TRIO_FUNC_STRING_INDEX_LAST) + +TRIO_PUBLIC_STRING char * +trio_string_index_last +TRIO_ARGS2((self, character), + trio_string_t *self, + int character) +{ + assert(self); + + return trio_index_last(self->content, character); +} + +#endif + +/* + * trio_string_length + */ +#if defined(TRIO_FUNC_STRING_LENGTH) + +TRIO_PUBLIC_STRING int +trio_string_length +TRIO_ARGS1((self), + trio_string_t *self) +{ + assert(self); + + if (self->length == 0) + { + self->length = trio_length(self->content); + } + return self->length; +} + +#endif + +/* + * trio_string_lower + */ +#if defined(TRIO_FUNC_STRING_LOWER) + +TRIO_PUBLIC_STRING int +trio_string_lower +TRIO_ARGS1((self), + trio_string_t *self) +{ + assert(self); + + return trio_lower(self->content); +} + +#endif + +/* + * trio_string_match + */ +#if defined(TRIO_FUNC_STRING_MATCH) + +TRIO_PUBLIC_STRING int +trio_string_match +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_match(self->content, other->content); +} + +#endif + +/* + * trio_xstring_match + */ +#if defined(TRIO_FUNC_XSTRING_MATCH) + +TRIO_PUBLIC_STRING int +trio_xstring_match +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_match(self->content, other); +} + +#endif + +/* + * trio_string_match_case + */ +#if defined(TRIO_FUNC_STRING_MATCH_CASE) + +TRIO_PUBLIC_STRING int +trio_string_match_case +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_match_case(self->content, other->content); +} + +#endif + +/* + * trio_xstring_match_case + */ +#if defined(TRIO_FUNC_XSTRING_MATCH_CASE) + +TRIO_PUBLIC_STRING int +trio_xstring_match_case +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_match_case(self->content, other); +} + +#endif + +/* + * trio_string_substring + */ +#if defined(TRIO_FUNC_STRING_SUBSTRING) + +TRIO_PUBLIC_STRING char * +trio_string_substring +TRIO_ARGS2((self, other), + trio_string_t *self, + trio_string_t *other) +{ + assert(self); + assert(other); + + return trio_substring(self->content, other->content); +} + +#endif + +/* + * trio_xstring_substring + */ +#if defined(TRIO_FUNC_XSTRING_SUBSTRING) + +TRIO_PUBLIC_STRING char * +trio_xstring_substring +TRIO_ARGS2((self, other), + trio_string_t *self, + TRIO_CONST char *other) +{ + assert(self); + assert(other); + + return trio_substring(self->content, other); +} + +#endif + +/* + * trio_string_upper + */ +#if defined(TRIO_FUNC_STRING_UPPER) + +TRIO_PUBLIC_STRING int +trio_string_upper +TRIO_ARGS1((self), + trio_string_t *self) +{ + assert(self); + + return trio_upper(self->content); +} + +#endif + +/** @} End of DynamicStrings */ diff --git a/psx/mednadisc/trio/triostr.h b/psx/mednadisc/trio/triostr.h new file mode 100644 index 0000000000..1e6939bc1f --- /dev/null +++ b/psx/mednadisc/trio/triostr.h @@ -0,0 +1,681 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2001 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +#ifndef TRIO_TRIOSTR_H +#define TRIO_TRIOSTR_H + +/* + * Documentation is located in triostr.c + */ + +#include +#include +#include +#include +#include "triodef.h" +#include "triop.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + TRIO_HASH_NONE = 0, + TRIO_HASH_PLAIN, + TRIO_HASH_TWOSIGNED +}; + +#if !defined(TRIO_PUBLIC_STRING) +# if !defined(TRIO_PUBLIC) +# define TRIO_PUBLIC +# endif +# define TRIO_PUBLIC_STRING TRIO_PUBLIC +#endif + +/************************************************************************* + * Dependencies + */ + +#if defined(TRIO_EMBED_STRING) + +/* + * The application that triostr is embedded in must define which functions + * it uses. + * + * The following resolves internal dependencies. + */ + +# if defined(TRIO_FUNC_XSTRING_SET) +# if !defined(TRIO_FUNC_DUPLICATE) +# define TRIO_FUNC_DUPLICATE +# endif +# endif + +# if defined(TRIO_FUNC_DUPLICATE) \ + || defined(TRIO_FUNC_DUPLICATE_MAX) \ + || defined(TRIO_FUNC_STRING_DUPLICATE) \ + || defined(TRIO_FUNC_XSTRING_DUPLICATE) +# if !defined(TRIO_FUNC_CREATE) +# define TRIO_FUNC_CREATE +# endif +# if !defined(TRIO_FUNC_COPY_MAX) +# define TRIO_FUNC_COPY_MAX +# endif +# endif + +# if defined(TRIO_FUNC_STRING_CREATE) +# if !defined(TRIO_FUNC_STRING_DESTROY) +# define TRIO_FUNC_STRING_DESTROY +# endif +# endif + +# if defined(TRIO_FUNC_STRING_DESTROY) \ + || defined(TRIO_FUNC_XSTRING_SET) +# if !defined(TRIO_FUNC_DESTROY) +# define TRIO_FUNC_DESTROY +# endif +# endif + +# if defined(TRIO_FUNC_EQUAL_LOCALE) \ + || defined(TRIO_FUNC_STRING_EQUAL) \ + || defined(TRIO_FUNC_XSTRING_EQUAL) +# if !defined(TRIO_FUNC_EQUAL) +# define TRIO_FUNC_EQUAL +# endif +# endif + +# if defined(TRIO_FUNC_EQUAL_CASE) \ + || defined(TRIO_FUNC_STRING_EQUAL_CASE) \ + || defined(TRIO_FUNC_XSTRING_EQUAL_CASE) +# if !defined(TRIO_FUNC_EQUAL_CASE) +# define TRIO_FUNC_EQUAL_CASE +# endif +# endif + +# if defined(TRIO_FUNC_SUBSTRING_MAX) \ + || defined(TRIO_FUNC_STRING_EQUAL_MAX) \ + || defined(TRIO_FUNC_XSTRING_EQUAL_MAX) +# if !defined(TRIO_FUNC_EQUAL_MAX) +# define TRIO_FUNC_EQUAL_MAX +# endif +# endif + +# if defined(TRIO_FUNC_TO_DOUBLE) \ + || defined(TRIO_FUNC_TO_FLOAT) +# if !defined(TRIO_FUNC_TO_LONG_DOUBLE) +# define TRIO_FUNC_TO_LONG_DOUBLE +# endif +# endif + +# if defined(TRIO_FUNC_STRING_TERMINATE) +# if !defined(TRIO_FUNC_XSTRING_APPEND_CHAR) +# define TRIO_FUNC_XSTRING_APPEND_CHAR +# endif +# endif + +# if defined(TRIO_FUNC_XSTRING_APPEND_CHAR) +# if !defined(TRIO_FUNC_STRING_SIZE) +# define TRIO_FUNC_STRING_SIZE +# endif +# endif + +#else + +/* + * When triostr is not embedded all functions are defined. + */ + +# define TRIO_FUNC_APPEND +# define TRIO_FUNC_APPEND_MAX +# define TRIO_FUNC_CONTAINS +# define TRIO_FUNC_COPY +# define TRIO_FUNC_COPY_MAX +# define TRIO_FUNC_CREATE +# define TRIO_FUNC_DESTROY +# define TRIO_FUNC_DUPLICATE +# define TRIO_FUNC_DUPLICATE_MAX +# define TRIO_FUNC_EQUAL +# define TRIO_FUNC_EQUAL_CASE +# define TRIO_FUNC_EQUAL_CASE_MAX +# define TRIO_FUNC_EQUAL_LOCALE +# define TRIO_FUNC_EQUAL_MAX +# define TRIO_FUNC_ERROR +# if !defined(TRIO_PLATFORM_WINCE) +# define TRIO_FUNC_FORMAT_DATE_MAX +# endif +# define TRIO_FUNC_HASH +# define TRIO_FUNC_INDEX +# define TRIO_FUNC_INDEX_LAST +# define TRIO_FUNC_LENGTH +# define TRIO_FUNC_LENGTH_MAX +# define TRIO_FUNC_LOWER +# define TRIO_FUNC_MATCH +# define TRIO_FUNC_MATCH_CASE +# define TRIO_FUNC_SPAN_FUNCTION +# define TRIO_FUNC_SUBSTRING +# define TRIO_FUNC_SUBSTRING_MAX +# define TRIO_FUNC_TO_DOUBLE +# define TRIO_FUNC_TO_FLOAT +# define TRIO_FUNC_TO_LONG +# define TRIO_FUNC_TO_LONG_DOUBLE +# define TRIO_FUNC_TO_LOWER +# define TRIO_FUNC_TO_UNSIGNED_LONG +# define TRIO_FUNC_TO_UPPER +# define TRIO_FUNC_TOKENIZE +# define TRIO_FUNC_UPPER + +# define TRIO_FUNC_STRING_APPEND +# define TRIO_FUNC_STRING_CONTAINS +# define TRIO_FUNC_STRING_COPY +# define TRIO_FUNC_STRING_CREATE +# define TRIO_FUNC_STRING_DESTROY +# define TRIO_FUNC_STRING_DUPLICATE +# define TRIO_FUNC_STRING_EQUAL +# define TRIO_FUNC_STRING_EQUAL_CASE +# define TRIO_FUNC_STRING_EQUAL_CASE_MAX +# define TRIO_FUNC_STRING_EQUAL_MAX +# define TRIO_FUNC_STRING_EXTRACT +# if !defined(TRIO_PLATFORM_WINCE) +# define TRIO_FUNC_STRING_FORMAT_DATE_MAX +# endif +# define TRIO_FUNC_STRING_GET +# define TRIO_FUNC_STRING_INDEX +# define TRIO_FUNC_STRING_INDEX_LAST +# define TRIO_FUNC_STRING_LENGTH +# define TRIO_FUNC_STRING_LOWER +# define TRIO_FUNC_STRING_MATCH +# define TRIO_FUNC_STRING_MATCH_CASE +# define TRIO_FUNC_STRING_SIZE +# define TRIO_FUNC_STRING_SUBSTRING +# define TRIO_FUNC_STRING_TERMINATE +# define TRIO_FUNC_STRING_UPPER + +# define TRIO_FUNC_XSTRING_APPEND +# define TRIO_FUNC_XSTRING_APPEND_CHAR +# define TRIO_FUNC_XSTRING_APPEND_MAX +# define TRIO_FUNC_XSTRING_CONTAINS +# define TRIO_FUNC_XSTRING_COPY +# define TRIO_FUNC_XSTRING_DUPLICATE +# define TRIO_FUNC_XSTRING_EQUAL +# define TRIO_FUNC_XSTRING_EQUAL_CASE +# define TRIO_FUNC_XSTRING_EQUAL_CASE_MAX +# define TRIO_FUNC_XSTRING_EQUAL_MAX +# define TRIO_FUNC_XSTRING_MATCH +# define TRIO_FUNC_XSTRING_MATCH_CASE +# define TRIO_FUNC_XSTRING_SET +# define TRIO_FUNC_XSTRING_SUBSTRING + +#endif + + +/************************************************************************* + * String functions + */ + +#if defined(TRIO_FUNC_APPEND) +TRIO_PUBLIC_STRING int +trio_append +TRIO_PROTO((char *target, TRIO_CONST char *source)); +#endif + +#if defined(TRIO_FUNC_APPEND_MAX) +TRIO_PUBLIC_STRING int +trio_append_max +TRIO_PROTO((char *target, size_t max, TRIO_CONST char *source)); +#endif + +#if defined(TRIO_FUNC_CONTAINS) +TRIO_PUBLIC_STRING int +trio_contains +TRIO_PROTO((TRIO_CONST char *string, TRIO_CONST char *substring)); +#endif + +#if defined(TRIO_FUNC_COPY) +TRIO_PUBLIC_STRING int +trio_copy +TRIO_PROTO((char *target, TRIO_CONST char *source)); +#endif + +#if defined(TRIO_FUNC_COPY_MAX) +TRIO_PUBLIC_STRING int +trio_copy_max +TRIO_PROTO((char *target, size_t max, TRIO_CONST char *source)); +#endif + +#if defined(TRIO_FUNC_CREATE) +TRIO_PUBLIC_STRING char * +trio_create +TRIO_PROTO((size_t size)); +#endif + +#if defined(TRIO_FUNC_DESTROY) +TRIO_PUBLIC_STRING void +trio_destroy +TRIO_PROTO((char *string)); +#endif + +#if defined(TRIO_FUNC_DUPLICATE) +TRIO_PUBLIC_STRING char * +trio_duplicate +TRIO_PROTO((TRIO_CONST char *source)); +#endif + +#if defined(TRIO_FUNC_DUPLICATE_MAX) +TRIO_PUBLIC_STRING char * +trio_duplicate_max +TRIO_PROTO((TRIO_CONST char *source, size_t max)); +#endif + +#if defined(TRIO_FUNC_EQUAL) +TRIO_PUBLIC_STRING int +trio_equal +TRIO_PROTO((TRIO_CONST char *first, TRIO_CONST char *second)); +#endif + +#if defined(TRIO_FUNC_EQUAL_CASE) +TRIO_PUBLIC_STRING int +trio_equal_case +TRIO_PROTO((TRIO_CONST char *first, TRIO_CONST char *second)); +#endif + +#if defined(TRIO_FUNC_EQUAL_CASE_MAX) +TRIO_PUBLIC_STRING int +trio_equal_case_max +TRIO_PROTO((TRIO_CONST char *first, size_t max, TRIO_CONST char *second)); +#endif + +#if defined(TRIO_FUNC_EQUAL_LOCALE) +TRIO_PUBLIC_STRING int +trio_equal_locale +TRIO_PROTO((TRIO_CONST char *first, TRIO_CONST char *second)); +#endif + +#if defined(TRIO_FUNC_EQUAL_MAX) +TRIO_PUBLIC_STRING int +trio_equal_max +TRIO_PROTO((TRIO_CONST char *first, size_t max, TRIO_CONST char *second)); +#endif + +#if defined(TRIO_FUNC_ERROR) +TRIO_PUBLIC_STRING TRIO_CONST char * +trio_error +TRIO_PROTO((int)); +#endif + +#if defined(TRIO_FUNC_FORMAT_DATE_MAX) +TRIO_PUBLIC_STRING size_t +trio_format_date_max +TRIO_PROTO((char *target, size_t max, TRIO_CONST char *format, TRIO_CONST struct tm *datetime)); +#endif + +#if defined(TRIO_FUNC_HASH) +TRIO_PUBLIC_STRING unsigned long +trio_hash +TRIO_PROTO((TRIO_CONST char *string, int type)); +#endif + +#if defined(TRIO_FUNC_INDEX) +TRIO_PUBLIC_STRING char * +trio_index +TRIO_PROTO((TRIO_CONST char *string, int character)); +#endif + +#if defined(TRIO_FUNC_INDEX_LAST) +TRIO_PUBLIC_STRING char * +trio_index_last +TRIO_PROTO((TRIO_CONST char *string, int character)); +#endif + +#if defined(TRIO_FUNC_LENGTH) +TRIO_PUBLIC_STRING size_t +trio_length +TRIO_PROTO((TRIO_CONST char *string)); +#endif + +#if defined(TRIO_FUNC_LENGTH_MAX) +TRIO_PUBLIC_STRING size_t +trio_length_max +TRIO_PROTO((TRIO_CONST char *string, size_t max)); +#endif + +#if defined(TRIO_FUNC_LOWER) +TRIO_PUBLIC_STRING int +trio_lower +TRIO_PROTO((char *target)); +#endif + +#if defined(TRIO_FUNC_MATCH) +TRIO_PUBLIC_STRING int +trio_match +TRIO_PROTO((TRIO_CONST char *string, TRIO_CONST char *pattern)); +#endif + +#if defined(TRIO_FUNC_MATCH_CASE) +TRIO_PUBLIC_STRING int +trio_match_case +TRIO_PROTO((TRIO_CONST char *string, TRIO_CONST char *pattern)); +#endif + +#if defined(TRIO_FUNC_SPAN_FUNCTION) +TRIO_PUBLIC_STRING size_t +trio_span_function +TRIO_PROTO((char *target, TRIO_CONST char *source, int (*Function) TRIO_PROTO((int)))); +#endif + +#if defined(TRIO_FUNC_SUBSTRING) +TRIO_PUBLIC_STRING char * +trio_substring +TRIO_PROTO((TRIO_CONST char *string, TRIO_CONST char *substring)); +#endif + +#if defined(TRIO_FUNC_SUBSTRING_MAX) +TRIO_PUBLIC_STRING char * +trio_substring_max +TRIO_PROTO((TRIO_CONST char *string, size_t max, TRIO_CONST char *substring)); +#endif + +#if defined(TRIO_FUNC_TO_DOUBLE) +TRIO_PUBLIC_STRING double +trio_to_double +TRIO_PROTO((TRIO_CONST char *source, char **endp)); +#endif + +#if defined(TRIO_FUNC_TO_FLOAT) +TRIO_PUBLIC_STRING float +trio_to_float +TRIO_PROTO((TRIO_CONST char *source, char **endp)); +#endif + +#if defined(TRIO_FUNC_TO_LONG) +TRIO_PUBLIC_STRING long +trio_to_long +TRIO_PROTO((TRIO_CONST char *source, char **endp, int base)); +#endif + +#if defined(TRIO_FUNC_TO_LOWER) +TRIO_PUBLIC_STRING int +trio_to_lower +TRIO_PROTO((int source)); +#endif + +#if defined(TRIO_FUNC_TO_LONG_DOUBLE) +TRIO_PUBLIC_STRING trio_long_double_t +trio_to_long_double +TRIO_PROTO((TRIO_CONST char *source, char **endp)); +#endif + +#if defined(TRIO_FUNC_TO_UNSIGNED_LONG) +TRIO_PUBLIC_STRING unsigned long +trio_to_unsigned_long +TRIO_PROTO((TRIO_CONST char *source, char **endp, int base)); +#endif + +#if defined(TRIO_FUNC_TO_UPPER) +TRIO_PUBLIC_STRING int +trio_to_upper +TRIO_PROTO((int source)); +#endif + +#if defined(TRIO_FUNC_TOKENIZE) +TRIO_PUBLIC_STRING char * +trio_tokenize +TRIO_PROTO((char *string, TRIO_CONST char *delimiters)); +#endif + +#if defined(TRIO_FUNC_UPPER) +TRIO_PUBLIC_STRING int +trio_upper +TRIO_PROTO((char *target)); +#endif + +/************************************************************************* + * Dynamic string functions + */ + +/* + * Opaque type for dynamic strings + */ + +typedef struct _trio_string_t trio_string_t; + +#if defined(TRIO_FUNC_STRING_APPEND) +TRIO_PUBLIC_STRING int +trio_string_append +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_CONTAINS) +TRIO_PUBLIC_STRING int +trio_string_contains +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_COPY) +TRIO_PUBLIC_STRING int +trio_string_copy +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_CREATE) +TRIO_PUBLIC_STRING trio_string_t * +trio_string_create +TRIO_PROTO((int initial_size)); +#endif + +#if defined(TRIO_FUNC_STRING_DESTROY) +TRIO_PUBLIC_STRING void +trio_string_destroy +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_STRING_DUPLICATE) +TRIO_PUBLIC_STRING trio_string_t * +trio_string_duplicate +TRIO_PROTO((trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_EQUAL) +TRIO_PUBLIC_STRING int +trio_string_equal +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_EQUAL_MAX) +TRIO_PUBLIC_STRING int +trio_string_equal_max +TRIO_PROTO((trio_string_t *self, size_t max, trio_string_t *second)); +#endif + +#if defined(TRIO_FUNC_STRING_EQUAL_CASE) +TRIO_PUBLIC_STRING int +trio_string_equal_case +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_EQUAL_CASE_MAX) +TRIO_PUBLIC_STRING int +trio_string_equal_case_max +TRIO_PROTO((trio_string_t *self, size_t max, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_EXTRACT) +TRIO_PUBLIC_STRING char * +trio_string_extract +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_STRING_FORMAT_DATE_MAX) +TRIO_PUBLIC_STRING size_t +trio_string_format_date_max +TRIO_PROTO((trio_string_t *self, size_t max, TRIO_CONST char *format, TRIO_CONST struct tm *datetime)); +#endif + +#if defined(TRIO_FUNC_STRING_GET) +TRIO_PUBLIC_STRING char * +trio_string_get +TRIO_PROTO((trio_string_t *self, int offset)); +#endif + +#if defined(TRIO_FUNC_STRING_INDEX) +TRIO_PUBLIC_STRING char * +trio_string_index +TRIO_PROTO((trio_string_t *self, int character)); +#endif + +#if defined(TRIO_FUNC_STRING_INDEX_LAST) +TRIO_PUBLIC_STRING char * +trio_string_index_last +TRIO_PROTO((trio_string_t *self, int character)); +#endif + +#if defined(TRIO_FUNC_STRING_LENGTH) +TRIO_PUBLIC_STRING int +trio_string_length +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_STRING_LOWER) +TRIO_PUBLIC_STRING int +trio_string_lower +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_STRING_MATCH) +TRIO_PUBLIC_STRING int +trio_string_match +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_MATCH_CASE) +TRIO_PUBLIC_STRING int +trio_string_match_case +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_SIZE) +TRIO_PUBLIC_STRING int +trio_string_size +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_STRING_SUBSTRING) +TRIO_PUBLIC_STRING char * +trio_string_substring +TRIO_PROTO((trio_string_t *self, trio_string_t *other)); +#endif + +#if defined(TRIO_FUNC_STRING_TERMINATE) +TRIO_PUBLIC_STRING void +trio_string_terminate +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_STRING_UPPER) +TRIO_PUBLIC_STRING int +trio_string_upper +TRIO_PROTO((trio_string_t *self)); +#endif + +#if defined(TRIO_FUNC_XSTRING_APPEND) +TRIO_PUBLIC_STRING int +trio_xstring_append +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_APPEND_CHAR) +TRIO_PUBLIC_STRING int +trio_xstring_append_char +TRIO_PROTO((trio_string_t *self, char character)); +#endif + +#if defined(TRIO_FUNC_XSTRING_APPEND_MAX) +TRIO_PUBLIC_STRING int +trio_xstring_append_max +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other, size_t max)); +#endif + +#if defined(TRIO_FUNC_XSTRING_CONTAINS) +TRIO_PUBLIC_STRING int +trio_xstring_contains +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_COPY) +TRIO_PUBLIC_STRING int +trio_xstring_copy +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_DUPLICATE) +TRIO_PUBLIC_STRING trio_string_t * +trio_xstring_duplicate +TRIO_PROTO((TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_EQUAL) +TRIO_PUBLIC_STRING int +trio_xstring_equal +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_EQUAL_MAX) +TRIO_PUBLIC_STRING int +trio_xstring_equal_max +TRIO_PROTO((trio_string_t *self, size_t max, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_EQUAL_CASE) +TRIO_PUBLIC_STRING int +trio_xstring_equal_case +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_EQUAL_CASE_MAX) +TRIO_PUBLIC_STRING int +trio_xstring_equal_case_max +TRIO_PROTO((trio_string_t *self, size_t max, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_MATCH) +TRIO_PUBLIC_STRING int +trio_xstring_match +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_MATCH_CASE) +TRIO_PUBLIC_STRING int +trio_xstring_match_case +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#if defined(TRIO_FUNC_XSTRING_SET) +TRIO_PUBLIC_STRING void +trio_xstring_set +TRIO_PROTO((trio_string_t *self, char *buffer)); +#endif + +#if defined(TRIO_FUNC_XSTRING_SUBSTRING) +TRIO_PUBLIC_STRING char * +trio_xstring_substring +TRIO_PROTO((trio_string_t *self, TRIO_CONST char *other)); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TRIO_TRIOSTR_H */