diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 73280c104..3bfa03ff9 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -18,17 +18,17 @@ AppDir: - amd64 allow_unauthenticated: true sources: - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy main restricted - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy universe - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-updates universe - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted - universe multiverse - - sourceline: deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted - - sourceline: deb http://security.ubuntu.com/ubuntu/ jammy-security universe - - sourceline: deb http://security.ubuntu.com/ubuntu/ jammy-security multiverse + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic main restricted + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-updates main restricted + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic universe + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-updates universe + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic multiverse + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-updates multiverse + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-backports main + restricted universe multiverse + - sourceline: deb http://security.ubuntu.com/ubuntu kinetic-security main restricted + - sourceline: deb http://security.ubuntu.com/ubuntu kinetic-security universe + - sourceline: deb http://security.ubuntu.com/ubuntu kinetic-security multiverse include: - libbz2-1.0:amd64 - libcom-err2:amd64 @@ -37,15 +37,13 @@ AppDir: - libgpg-error0:amd64 - libkeyutils1:amd64 - liblzma5:amd64 - - libpcre3:amd64 - libselinux1:amd64 - openssl - zlib1g:amd64 files: include: - - /lib/x86_64-linux-gnu/libGLX.so.0 - - /lib/x86_64-linux-gnu/libGLdispatch.so.0 - /lib/x86_64-linux-gnu/libOpenCL.so.1 + - /lib/x86_64-linux-gnu/libSvtAv1Enc.so.1 - /lib/x86_64-linux-gnu/libX11.so.6 - /lib/x86_64-linux-gnu/libXau.so.6 - /lib/x86_64-linux-gnu/libXdmcp.so.6 @@ -53,9 +51,9 @@ AppDir: - /lib/x86_64-linux-gnu/libXfixes.so.3 - /lib/x86_64-linux-gnu/libXrender.so.1 - /lib/x86_64-linux-gnu/libaom.so.3 - - /lib/x86_64-linux-gnu/libavcodec.so.58 - - /lib/x86_64-linux-gnu/libavformat.so.58 - - /lib/x86_64-linux-gnu/libavutil.so.56 + - /lib/x86_64-linux-gnu/libavcodec.so.59 + - /lib/x86_64-linux-gnu/libavformat.so.59 + - /lib/x86_64-linux-gnu/libavutil.so.57 - /lib/x86_64-linux-gnu/libblkid.so.1 - /lib/x86_64-linux-gnu/libbluray.so.2 - /lib/x86_64-linux-gnu/libbrotlicommon.so.1 @@ -65,11 +63,12 @@ AppDir: - /lib/x86_64-linux-gnu/libcairo.so.2 - /lib/x86_64-linux-gnu/libcapstone.so.4 - /lib/x86_64-linux-gnu/libchromaprint.so.1 + - /lib/x86_64-linux-gnu/libcjson.so.1 - /lib/x86_64-linux-gnu/libcodec2.so.1.0 - /lib/x86_64-linux-gnu/libcrypto.so.3 - /lib/x86_64-linux-gnu/libcurl.so.4 - /lib/x86_64-linux-gnu/libdatrie.so.1 - - /lib/x86_64-linux-gnu/libdav1d.so.5 + - /lib/x86_64-linux-gnu/libdav1d.so.6 - /lib/x86_64-linux-gnu/libffi.so.8 - /lib/x86_64-linux-gnu/libfontconfig.so.1 - /lib/x86_64-linux-gnu/libfreetype.so.6 @@ -90,8 +89,8 @@ AppDir: - /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 - /lib/x86_64-linux-gnu/libharfbuzz.so.0 - /lib/x86_64-linux-gnu/libhogweed.so.6 - - /lib/x86_64-linux-gnu/libicudata.so.70 - - /lib/x86_64-linux-gnu/libicuuc.so.70 + - /lib/x86_64-linux-gnu/libicudata.so.71 + - /lib/x86_64-linux-gnu/libicuuc.so.71 - /lib/x86_64-linux-gnu/libidn2.so.0 - /lib/x86_64-linux-gnu/libjpeg.so.8 - /lib/x86_64-linux-gnu/libk5crypto.so.3 @@ -99,6 +98,7 @@ AppDir: - /lib/x86_64-linux-gnu/libkrb5support.so.0 - /lib/x86_64-linux-gnu/liblber-2.5.so.0 - /lib/x86_64-linux-gnu/libldap-2.5.so.0 + - /lib/x86_64-linux-gnu/libmbedcrypto.so.7 - /lib/x86_64-linux-gnu/libmd.so.0 - /lib/x86_64-linux-gnu/libmfx.so.1 - /lib/x86_64-linux-gnu/libmount.so.1 @@ -122,6 +122,7 @@ AppDir: - /lib/x86_64-linux-gnu/libpng16.so.16 - /lib/x86_64-linux-gnu/libpsl.so.5 - /lib/x86_64-linux-gnu/librabbitmq.so.4 + - /lib/x86_64-linux-gnu/librist.so.4 - /lib/x86_64-linux-gnu/librsvg-2.so.2 - /lib/x86_64-linux-gnu/librtmp.so.1 - /lib/x86_64-linux-gnu/libsasl2.so.2 @@ -130,12 +131,12 @@ AppDir: - /lib/x86_64-linux-gnu/libsodium.so.23 - /lib/x86_64-linux-gnu/libsoxr.so.0 - /lib/x86_64-linux-gnu/libspeex.so.1 - - /lib/x86_64-linux-gnu/libsrt-gnutls.so.1.4 + - /lib/x86_64-linux-gnu/libsrt-gnutls.so.1.5 - /lib/x86_64-linux-gnu/libssh-gcrypt.so.4 - /lib/x86_64-linux-gnu/libssh.so.4 - /lib/x86_64-linux-gnu/libssl.so.3 - /lib/x86_64-linux-gnu/libstdc++.so.6 - - /lib/x86_64-linux-gnu/libswresample.so.3 + - /lib/x86_64-linux-gnu/libswresample.so.4 - /lib/x86_64-linux-gnu/libtasn1.so.6 - /lib/x86_64-linux-gnu/libthai.so.0 - /lib/x86_64-linux-gnu/libtheoradec.so.1 @@ -155,7 +156,7 @@ AppDir: - /lib/x86_64-linux-gnu/libvpx.so.7 - /lib/x86_64-linux-gnu/libwebp.so.7 - /lib/x86_64-linux-gnu/libwebpmux.so.3 - - /lib/x86_64-linux-gnu/libx264.so.163 + - /lib/x86_64-linux-gnu/libx264.so.164 - /lib/x86_64-linux-gnu/libx265.so.199 - /lib/x86_64-linux-gnu/libxml2.so.2 - /lib/x86_64-linux-gnu/libxvidcore.so.4 diff --git a/Makefile b/Makefile index 744fbe371..681240807 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,7 @@ SRCS += $(wildcard third_party/libelfin/*.cc) SRCS += third_party/cq/reclaimer.cc SRCS += third_party/clip/clip.cpp SRCS += third_party/clip/image.cpp +SRCS += $(wildcard third_party/cueparser/*.c) SRCS += third_party/gl3w/GL/gl3w.c SRCS += third_party/http-parser/http_parser.c SRCS += third_party/ImFileDialog/ImFileDialog.cpp diff --git a/src/cdrom/cdriso-ccd.cc b/src/cdrom/cdriso-ccd.cc index 7fc4beb19..e9538720b 100644 --- a/src/cdrom/cdriso-ccd.cc +++ b/src/cdrom/cdriso-ccd.cc @@ -40,7 +40,9 @@ bool PCSX::CDRIso::parseccd(const char *isofileString) { } if (fi->failed()) return false; - memset(&m_ti, 0, sizeof(m_ti)); + for (auto &i : m_ti) { + i = {}; + } while (fi->gets(linebuf, sizeof(linebuf))) { if (!strncmp(linebuf, "[TRACK", 6)) { diff --git a/src/cdrom/cdriso-cue.cc b/src/cdrom/cdriso-cue.cc index 9d562c0ec..d91c62ea7 100644 --- a/src/cdrom/cdriso-cue.cc +++ b/src/cdrom/cdriso-cue.cc @@ -19,28 +19,18 @@ #include "cdrom/cdriso.h" #include "core/cdrom.h" +#include "cueparser/cueparser.h" +#include "cueparser/disc.h" +#include "cueparser/fileabstract.h" +#include "cueparser/scheduler.h" +#include "support/ffmpeg-audio-file.h" // this function tries to get the .cue file of the given .bin // the necessary data is put into the ti (trackinformation)-array bool PCSX::CDRIso::parsecue(const char *isofileString) { - auto get_cdda_type = [](const char *str) { - const size_t lenstr = strlen(str); - if (strncmp((str + lenstr - 3), "bin", 3) == 0) { - return trackinfo::BIN; - } else { - return trackinfo::CCDDA; - } - return trackinfo::BIN; // no valid extension or no support; assume bin - }; - std::filesystem::path isofile = MAKEU8(isofileString); std::filesystem::path cuename, filepath; IO fi; - char *token; - char time[20]; - char linebuf[256], tmpb[256], dummy[256]; - unsigned int t, file_len, mode, sector_offs; - unsigned int sector_size = 2352; m_numtracks = 0; @@ -57,134 +47,130 @@ bool PCSX::CDRIso::parsecue(const char *isofileString) { // Some stupid tutorials wrongly tell users to use cdrdao to rip a // "bin/cue" image, which is in fact a "bin/toc" image. So let's check // that... - if (fi->gets(linebuf, sizeof(linebuf))) { - if (!strncmp(linebuf, "CD_ROM_XA", 9)) { - // Don't proceed further, as this is actually a .toc file rather - // than a .cue file. - return parsetoc(isofileString); - } - fi->rSeek(0, SEEK_SET); + if (fi->gets() == "CD_ROM_XA") { + // Don't proceed further, as this is actually a .toc file rather + // than a .cue file. + return parsetoc(isofileString); } + fi->rSeek(0, SEEK_SET); // build a path for files referenced in .cue filepath = cuename.parent_path(); - memset(&m_ti, 0, sizeof(m_ti)); - - file_len = 0; - sector_offs = 2 * 75; - - while (fi->gets(linebuf, sizeof(linebuf))) { - strncpy(dummy, linebuf, sizeof(linebuf)); - token = strtok(dummy, " "); - - if (token == NULL) continue; - - if (!strcmp(token, "TRACK")) { - m_numtracks++; - - sector_size = 0; - if (strstr(linebuf, "AUDIO") != NULL) { - m_ti[m_numtracks].type = TrackType::CDDA; - sector_size = PCSX::IEC60908b::FRAMESIZE_RAW; - // Check if extension is mp3, etc, for compressed audio formats - if (m_multifile && - (m_ti[m_numtracks].cddatype = get_cdda_type(m_ti[m_numtracks].filepath)) > trackinfo::BIN) { - int seconds = get_compressed_cdda_track_length(m_ti[m_numtracks].filepath) + 0; - const bool lazy_decode = true; // TODO: config param - - // TODO: get frame length for compressed audio as well - m_ti[m_numtracks].len_decoded_buffer = 44100 * (16 / 8) * 2 * seconds; - file_len = m_ti[m_numtracks].len_decoded_buffer / PCSX::IEC60908b::FRAMESIZE_RAW; - - // Send to decoder if not lazy decoding - if (!lazy_decode) { - PCSX::g_system->printf("\n"); - file_len = do_decode_cdda(&(m_ti[m_numtracks]), m_numtracks) / PCSX::IEC60908b::FRAMESIZE_RAW; - } - } - } else if (sscanf(linebuf, " TRACK %u MODE%u/%u", &t, &mode, §or_size) == 3) { - int32_t accurate_len; - // TODO: if 2048 frame length -> recalculate file_len? - m_ti[m_numtracks].type = TrackType::DATA; - // detect if ECM or compressed & get accurate length - if (handleecm(m_ti[m_numtracks].filepath, m_cdHandle, &accurate_len)) { - file_len = accurate_len; - } + CueScheduler scheduler; + Scheduler_construct(&scheduler); + struct Context { + std::filesystem::path filepath; + bool failed = false; + } context; + context.filepath = filepath; + scheduler.opaque = &context; + + auto createFile = [](CueFile *file, CueScheduler *scheduler, const char *filename) -> CueFile * { + Context *context = reinterpret_cast(scheduler->opaque); + UvFile *fi = new UvFile(filename); + if (fi->failed()) { + delete fi; + fi = new UvFile(context->filepath / filename); + } + file->opaque = fi; + file->destroy = [](CueFile *file) { + UvFile *fi = reinterpret_cast(file->opaque); + delete fi; + file->opaque = nullptr; + }; + file->close = [](CueFile *file, CueScheduler *scheduler, void (*cb)(CueFile *, CueScheduler *)) { + UvFile *fi = reinterpret_cast(file->opaque); + fi->close(); + File_schedule_close(file, scheduler, cb); + }; + file->size = [](CueFile *file, CueScheduler *scheduler, int compressed, + void (*cb)(CueFile *, CueScheduler *, uint64_t)) { + UvFile *fi = reinterpret_cast(file->opaque); + if (compressed) { + FFmpegAudioFile *cfi = new FFmpegAudioFile(fi, FFmpegAudioFile::CHANNELS_STEREO, + FFmpegAudioFile::ENDIANNESS_LITTLE, 44100); + file->opaque = cfi; + File_schedule_size(file, scheduler, cfi->size(), cb); } else { - PCSX::g_system->printf(".cue: failed to parse TRACK\n"); - m_ti[m_numtracks].type = m_numtracks == 1 ? TrackType::DATA : TrackType::CDDA; - } - if (sector_size == 0) // TODO m_isMode1ISO? - sector_size = PCSX::IEC60908b::FRAMESIZE_RAW; - } else if (!strcmp(token, "INDEX")) { - if (sscanf(linebuf, " INDEX %02d %8s", &t, time) != 2) - PCSX::g_system->printf(".cue: failed to parse INDEX\n"); - m_ti[m_numtracks].start = IEC60908b::MSF(time); - - t = m_ti[m_numtracks].start.toLBA(); - m_ti[m_numtracks].start_offset = t * sector_size; - t += sector_offs; - m_ti[m_numtracks].start = IEC60908b::MSF(t); - - // default track length to file length - t = file_len - m_ti[m_numtracks].start_offset / sector_size; - m_ti[m_numtracks].length = IEC60908b::MSF(t); - - if (m_numtracks > 1 && !m_ti[m_numtracks].handle) { - // this track uses the same file as the last, - // start of this track is last track's end - t = m_ti[m_numtracks].start.toLBA() - m_ti[m_numtracks - 1].start.toLBA(); - m_ti[m_numtracks - 1].length = IEC60908b::MSF(t); + File_schedule_size(file, scheduler, fi->size(), cb); } - if (m_numtracks > 1 && m_pregapOffset == -1) m_pregapOffset = m_ti[m_numtracks].start_offset / sector_size; - } else if (!strcmp(token, "PREGAP")) { - if (sscanf(linebuf, " PREGAP %8s", time) == 1) { - sector_offs += IEC60908b::MSF(time).toLBA(); - } - m_pregapOffset = -1; // mark to fill track start_offset - } else if (!strcmp(token, "FILE")) { - t = sscanf(linebuf, " FILE \"%255[^\"]\"", tmpb); - if (t != 1) sscanf(linebuf, " FILE %255s", tmpb); - - // absolute path? - m_ti[m_numtracks + 1].handle.setFile(new UvFile(tmpb)); - if (g_emulator->settings.get()) { - m_ti[m_numtracks + 1].handle.asA()->startCaching(); - } - if (m_ti[m_numtracks + 1].handle->failed()) { - m_ti[m_numtracks + 1].handle.setFile(new UvFile(filepath / tmpb)); - if (g_emulator->settings.get()) { - m_ti[m_numtracks + 1].handle.asA()->startCaching(); - } - } - - strcpy(m_ti[m_numtracks + 1].filepath, - reinterpret_cast(m_ti[m_numtracks + 1].handle->filename().u8string().c_str())); - - // update global offset if this is not first file in this .cue - if (m_numtracks + 1 > 1) { - m_multifile = true; - sector_offs += file_len; - } - - file_len = 0; - if (m_ti[m_numtracks + 1].handle->failed()) { - PCSX::g_system->message(_("\ncould not open: %s\n"), m_ti[m_numtracks + 1].handle->filename().string()); - m_ti[m_numtracks + 1].handle.reset(); - continue; + }; + file->read = [](CueFile *file, CueScheduler *scheduler, uint32_t amount, uint64_t cursor, uint8_t *buffer, + void (*cb)(CueFile *, CueScheduler *, int error, uint32_t amount, uint8_t *buffer)) { + UvFile *fi = reinterpret_cast(file->opaque); + if (cursor >= fi->size()) { + File_schedule_read(file, scheduler, 0, 0, nullptr, cb); + } else { + auto r = fi->readAt(buffer, amount, cursor); + File_schedule_read(file, scheduler, r < 0 ? 1 : 0, r, buffer, cb); } + }; + file->write = [](CueFile *file, CueScheduler *scheduler, uint32_t amount, uint64_t cursor, + const uint8_t *buffer, void (*cb)(CueFile *, CueScheduler *, int error, uint32_t amount)) { + throw std::runtime_error("Writes not implemented"); + }; + file->cfilename = nullptr; + file->filename = nullptr; + file->references = 1; + return !fi->failed() ? file : nullptr; + }; - // File length, compressed audio length will be calculated in AUDIO tag - m_ti[m_numtracks + 1].handle->rSeek(0, SEEK_END); - file_len = m_ti[m_numtracks + 1].handle->rTell() / PCSX::IEC60908b::FRAMESIZE_RAW; - - if (m_numtracks == 0 && (isofile.extension() == ".cue")) { - // user selected .cue as image file, use its data track instead - m_cdHandle.setFile(new SubFile(m_ti[m_numtracks + 1].handle, 0, m_ti[m_numtracks + 1].handle->size())); + CueFile cue; + CueParser parser; + CueDisc disc; + bool success = createFile(&cue, &scheduler, cuename.string().c_str()); + if (!success) { + throw std::runtime_error("Couldn't open cue file twice..."); + } + cue.cfilename = cuename.string().c_str(); + CueParser_construct(&parser, &disc); + CueParser_parse(&parser, &cue, &scheduler, createFile, + [](CueParser *parser, CueScheduler *scheduler, const char *error) { + Context *context = reinterpret_cast(scheduler->opaque); + if (error) { + context->failed = true; + g_system->log(LogClass::CDROM_IO, "Error parsing Cue File: %s", error); + } + }); + + Scheduler_run(&scheduler); + CueParser_destroy(&parser); + + File_schedule_close(&cue, &scheduler, [](CueFile *file, CueScheduler *scheduler) { file->destroy(file); }); + if (context.failed) { + for (unsigned i = 1; i <= disc.trackCount; i++) { + CueTrack *track = &disc.tracks[i]; + if (track->file) { + if (track->file->references == 1) { + File_schedule_close(track->file, &scheduler, + [](CueFile *file, CueScheduler *scheduler) { file->destroy(file); }); + } else { + track->file->references--; + } + track->file = nullptr; } } + Scheduler_run(&scheduler); + return false; + } + Scheduler_run(&scheduler); + + m_cdHandle.setFile(reinterpret_cast(disc.tracks[1].file->opaque)); + + for (unsigned i = 1; i <= disc.trackCount; i++) { + CueTrack *track = &disc.tracks[i]; + File *fi = reinterpret_cast(track->file->opaque); + m_ti[i].handle.setFile(new SubFile(fi, (track->indices[1] - track->fileOffset) * 2352, track->size * 2352)); + m_ti[i].type = track->trackType == TRACK_TYPE_AUDIO ? TrackType::CDDA : TrackType::DATA; + m_ti[i].cddatype = track->compressed ? trackinfo::CCDDA : trackinfo::BIN; + m_ti[i].start = IEC60908b::MSF(track->indices[1]); + m_ti[i].pregap = IEC60908b::MSF(track->indices[1] - track->indices[0]); + m_ti[i].length = IEC60908b::MSF(track->size); } + m_numtracks = disc.trackCount; + m_multifile = true; + return true; } diff --git a/src/cdrom/cdriso-ffmpeg.cc b/src/cdrom/cdriso-ffmpeg.cc deleted file mode 100644 index 5878a446a..000000000 --- a/src/cdrom/cdriso-ffmpeg.cc +++ /dev/null @@ -1,264 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2022 PCSX-Redux authors * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - ***************************************************************************/ - -#include "cdrom/cdriso.h" - -extern "C" { -#include -#include -#include -#include -#include -#include -} - -int PCSX::CDRIso::get_compressed_cdda_track_length(const char *filepath) { - int seconds = -1; - av_log_set_level(AV_LOG_QUIET); - - AVFormatContext *inAudioFormat = NULL; - inAudioFormat = avformat_alloc_context(); - int errorCode = avformat_open_input(&inAudioFormat, filepath, NULL, NULL); - avformat_find_stream_info(inAudioFormat, NULL); - seconds = (int)ceil((double)inAudioFormat->duration / (double)AV_TIME_BASE); - avformat_close_input(&inAudioFormat); - return seconds; -} - -static int decode_packet(int *got_frame, AVPacket pkt, int audio_stream_idx, AVFrame *frame, - AVCodecContext *audio_dec_ctx, void *buf, int *size, SwrContext *swr) { - int ret = 0; - int decoded = pkt.size; - *got_frame = 0; - - if (pkt.stream_index == audio_stream_idx) { - // ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt); - ret = avcodec_receive_frame(audio_dec_ctx, frame); - if (ret == 0) *got_frame = 1; - if (ret == AVERROR(EAGAIN)) ret = 0; - if (ret == 0) ret = avcodec_send_packet(audio_dec_ctx, &pkt); - if (ret == AVERROR(EAGAIN)) { - ret = 0; - } else if (ret < 0) { - PCSX::g_system->printf(_("Error decoding audio frame\n")); - return ret; - } else { - ret = pkt.size; - } - - /* Some audio decoders decode only part of the packet, and have to be - * called again with the remainder of the packet data. - * Sample: fate-suite/lossless-audio/luckynight-partial.shn - * Also, some decoders might over-read the packet. */ - - decoded = FFMIN(ret, pkt.size); - - if (*got_frame) { - size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(AVSampleFormat(frame->format)); - swr_convert(swr, (uint8_t **)&buf, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples); - (*size) += (unpadded_linesize * 2); - } - } - return decoded; -} - -static int open_codec_context(int *stream_idx, AVFormatContext *fmt_ctx, enum AVMediaType type) { - int ret, stream_index; - AVStream *st; - AVDictionary *opts = NULL; - - ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0); - - if (ret < 0) { - PCSX::g_system->printf(_("Could not find %s stream in input file\n"), av_get_media_type_string(type)); - return ret; - } else { - stream_index = ret; - st = fmt_ctx->streams[stream_index]; - - const AVCodec *dec = avcodec_find_decoder(st->codecpar->codec_id); - if (!dec) { - PCSX::g_system->printf(_("Failed to find %s codec\n"), av_get_media_type_string(type)); - return AVERROR(EINVAL); - } - - AVCodecContext *dec_ctx = avcodec_alloc_context3(dec); - if (!dec_ctx) { - PCSX::g_system->printf(_("Failed to find %s codec\n"), av_get_media_type_string(type)); - return AVERROR(EINVAL); - } - avcodec_parameters_to_context(dec_ctx, st->codecpar); - - /* Init the decoders, with or without reference counting */ - if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) { - PCSX::g_system->printf(_("Failed to open %s codec\n"), av_get_media_type_string(type)); - avcodec_free_context(&dec_ctx); - return ret; - } - avcodec_free_context(&dec_ctx); - *stream_idx = stream_index; - } - return 0; -} - -static int decode_compressed_cdda_track(char *buf, char *src_filename, int *size) { - AVFormatContext *fmt_ctx = NULL; - AVCodecContext *audio_dec_ctx = NULL; - const AVCodec *audio_codec = NULL; - AVStream *audio_stream = NULL; - int audio_stream_idx = -1; - AVFrame *frame = NULL; - AVPacket pkt; - SwrContext *resample_context; - int ret = 0, got_frame; - - if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) { - PCSX::g_system->printf(_("Could not open source file %s\n"), src_filename); - return -1; - } - - if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { - PCSX::g_system->printf(_("Could not find stream information\n")); - ret = -1; - goto end; - } - - if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) { - audio_stream = fmt_ctx->streams[audio_stream_idx]; - } - - if (!audio_stream) { - PCSX::g_system->printf(_("Could not find audio stream in the input, aborting\n")); - ret = -1; - goto end; - } - - audio_codec = avcodec_find_decoder(audio_stream->codecpar->codec_id); - - if (!audio_codec) { - PCSX::g_system->printf(_("Could not find audio codec for the input, aborting\n")); - ret = -1; - goto end; - } - - audio_dec_ctx = avcodec_alloc_context3(audio_codec); - - if (!audio_dec_ctx) { - PCSX::g_system->printf(_("Could not allocate audio codec for the input, aborting\n")); - ret = -1; - goto end; - } - - // init and configure resampler - resample_context = swr_alloc(); - if (!resample_context) { - PCSX::g_system->printf(_("Could not allocate resample context")); - ret = -1; - goto end; - } - av_opt_set_int(resample_context, "in_channel_layout", audio_dec_ctx->channel_layout, 0); - av_opt_set_int(resample_context, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); - av_opt_set_int(resample_context, "in_sample_rate", audio_dec_ctx->sample_rate, 0); - av_opt_set_int(resample_context, "out_sample_rate", 44100, 0); - av_opt_set_sample_fmt(resample_context, "in_sample_fmt", audio_dec_ctx->sample_fmt, 0); - av_opt_set_sample_fmt(resample_context, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); - if (swr_init(resample_context) < 0) { - PCSX::g_system->printf(_("Could not open resample context")); - ret = -1; - goto end; - } - - frame = av_frame_alloc(); - if (!frame) { - PCSX::g_system->printf(_("Could not allocate frame\n")); - ret = AVERROR(ENOMEM); - goto end; - } - - /* initialize packet, set data to NULL, let the demuxer fill it */ - av_init_packet(&pkt); - pkt.data = NULL; - pkt.size = 0; - - /* read frames from the file */ - while (av_read_frame(fmt_ctx, &pkt) >= 0) { - AVPacket orig_pkt = pkt; - do { - ret = decode_packet(&got_frame, pkt, audio_stream_idx, frame, audio_dec_ctx, buf + (*size), size, - resample_context); - if (ret < 0) break; - pkt.data += ret; - pkt.size -= ret; - } while (pkt.size > 0); - av_packet_unref(&orig_pkt); - } - - /* flush cached frames */ - pkt.data = NULL; - pkt.size = 0; - do { - decode_packet(&got_frame, pkt, audio_stream_idx, frame, audio_dec_ctx, buf + (*size), size, resample_context); - } while (got_frame); - -end: - swr_free(&resample_context); - if (audio_dec_ctx) { - avcodec_close(audio_dec_ctx); - avcodec_free_context(&audio_dec_ctx); - } - avformat_close_input(&fmt_ctx); - av_frame_free(&frame); - return ret < 0; -} - -/* end of ffmpeg-only code */ - -int PCSX::CDRIso::do_decode_cdda(struct trackinfo *tri, uint32_t tracknumber) { - tri->decoded_buffer = (char *)malloc(tri->len_decoded_buffer); - memset(tri->decoded_buffer, 0, tri->len_decoded_buffer - 1); - - if (tri->decoded_buffer == NULL) { - PCSX::g_system->message(_("Could not allocate memory to decode CDDA TRACK: %s\n"), tri->filepath); - tri->handle->close(); // encoded file handle not needed anymore - tri->handle.setFile(new BufferFile()); // change handle to decoded one - tri->cddatype = trackinfo::BIN; - return 0; - } - - tri->handle->close(); // encoded file handle not needed anymore - tri->handle.reset(); - - int ret; - PCSX::g_system->printf(_("Decoding audio tr#%u (%s)..."), tracknumber, tri->filepath); - - int len = 0; - - if ((ret = decode_compressed_cdda_track(tri->decoded_buffer, tri->filepath, &len)) == 0) { - if (len > tri->len_decoded_buffer) { - PCSX::g_system->printf(_("Buffer overflow...")); - PCSX::g_system->printf(_("Actual %i vs. %i estimated\n"), len, tri->len_decoded_buffer); - len = tri->len_decoded_buffer; // we probably segfaulted already, oh well... - } - - tri->handle.setFile(new BufferFile(tri->decoded_buffer, len)); // change handle to decoded one - PCSX::g_system->printf(_("OK\n"), tri->filepath); - } - tri->cddatype = trackinfo::BIN; - return len; -} diff --git a/src/cdrom/cdriso-mds.cc b/src/cdrom/cdriso-mds.cc index c00744deb..49a837b6f 100644 --- a/src/cdrom/cdriso-mds.cc +++ b/src/cdrom/cdriso-mds.cc @@ -38,7 +38,9 @@ bool PCSX::CDRIso::parsemds(const char *isofileString) { fi.asA()->startCaching(); } - memset(&m_ti, 0, sizeof(m_ti)); + for (auto &i : m_ti) { + i = {}; + } // check if it's a valid mds file i = fi->read(); diff --git a/src/cdrom/cdriso-toc.cc b/src/cdrom/cdriso-toc.cc index 8e4fbffc9..22bf0863b 100644 --- a/src/cdrom/cdriso-toc.cc +++ b/src/cdrom/cdriso-toc.cc @@ -68,7 +68,9 @@ bool PCSX::CDRIso::parsetoc(const char *isofileStr) { filename = tocname.parent_path(); - memset(&m_ti, 0, sizeof(m_ti)); + for (auto &i : m_ti) { + i = {}; + } m_cddaBigEndian = true; // cdrdao uses big-endian for CD Audio sector_size = PCSX::IEC60908b::FRAMESIZE_RAW; @@ -152,7 +154,9 @@ bool PCSX::CDRIso::parsetoc(const char *isofileStr) { } } } - if (m_numtracks > 0) m_cdHandle.setFile(new SubFile(m_ti[1].handle, 0, m_ti[1].handle->size())); + if ((m_numtracks > 0) && (m_ti[1].handle)) { + m_cdHandle.setFile(new SubFile(m_ti[1].handle, 0, m_ti[1].handle->size())); + } return true; } diff --git a/src/cdrom/cdriso.cc b/src/cdrom/cdriso.cc index da65e003f..ba5f48afd 100644 --- a/src/cdrom/cdriso.cc +++ b/src/cdrom/cdriso.cc @@ -251,10 +251,12 @@ uint8_t *PCSX::CDRIso::getBuffer() { void PCSX::CDRIso::printTracks() { for (int i = 1; i <= m_numtracks; i++) { - PCSX::g_system->printf( - _("Track %.2d (%s) - Start %.2d:%.2d:%.2d, Length %.2d:%.2d:%.2d\n"), i, - (m_ti[i].type == TrackType::DATA ? "DATA" : m_ti[i].cddatype == trackinfo::CCDDA ? "CZDA" : "CDDA"), - m_ti[i].start.m, m_ti[i].start.s, m_ti[i].start.f, m_ti[i].length.m, m_ti[i].length.s, m_ti[i].length.f); + PCSX::g_system->printf(_("Track %.2d (%s) - Start %.2d:%.2d:%.2d, Length %.2d:%.2d:%.2d\n"), i, + (m_ti[i].type == TrackType::DATA ? "DATA" + : m_ti[i].cddatype == trackinfo::CCDDA ? "CZDA" + : "CDDA"), + m_ti[i].start.m, m_ti[i].start.s, m_ti[i].start.f, m_ti[i].length.m, m_ti[i].length.s, + m_ti[i].length.f); } } @@ -285,6 +287,10 @@ bool PCSX::CDRIso::open(void) { m_useCompressed = false; m_cdimg_read_func = &CDRIso::cdread_normal; + for (auto &i : m_ti) { + i = {}; + } + if (parsecue(reinterpret_cast(m_isoPath.string().c_str()))) { PCSX::g_system->printf("[+cue]"); } else if (parsetoc(reinterpret_cast(m_isoPath.string().c_str()))) { @@ -331,6 +337,9 @@ bool PCSX::CDRIso::open(void) { m_numtracks = 1; m_ti[1].type = TrackType::DATA; m_ti[1].start = IEC60908b::MSF(0, 2, 0); + m_ti[1].pregap = IEC60908b::MSF(0, 0, 0); + m_ti[1].handle = m_cdHandle; + m_ti[1].length = IEC60908b::MSF(m_ti[1].handle->size() / 2352); } if (m_ppf.load(m_isoPath)) { @@ -370,9 +379,6 @@ void PCSX::CDRIso::close() { for (int i = 1; i <= m_numtracks; i++) { if (m_ti[i].handle) { m_ti[i].handle.reset(); - if (m_ti[i].decoded_buffer) { - free(m_ti[i].decoded_buffer); - } m_ti[i].cddatype = trackinfo::NONE; } } @@ -394,12 +400,32 @@ void PCSX::CDRIso::close() { } PCSX::IEC60908b::MSF PCSX::CDRIso::getTD(uint8_t track) { + if (track == 0) { + unsigned int sect; + sect = m_ti[m_numtracks].start.toLBA() + m_ti[m_numtracks].length.toLBA() - m_ti[m_numtracks].pregap.toLBA(); + return IEC60908b::MSF(sect); + } else if (m_numtracks > 0 && track <= m_numtracks) { + return IEC60908b::MSF(m_ti[track].start.toLBA()); + } + return IEC60908b::MSF(0, 2, 0); +} + +PCSX::IEC60908b::MSF PCSX::CDRIso::getLength(uint8_t track) { if (track == 0) { unsigned int sect; sect = m_ti[m_numtracks].start.toLBA() + m_ti[m_numtracks].length.toLBA(); return IEC60908b::MSF(sect); } else if (m_numtracks > 0 && track <= m_numtracks) { - return m_ti[track].start; + return m_ti[track].length; + } + return IEC60908b::MSF(0, 0, 0); +} + +PCSX::IEC60908b::MSF PCSX::CDRIso::getPregap(uint8_t track) { + if (track <= 1) { + return IEC60908b::MSF(0, 0, 0); + } else if (m_numtracks > 0 && track <= m_numtracks) { + return m_ti[track].pregap; } return IEC60908b::MSF(0, 2, 0); } @@ -460,10 +486,14 @@ unsigned PCSX::CDRIso::readSectors(uint32_t lba, void *buffer_, unsigned count) for (unsigned i = 0; i < count; i++) { auto ptr = buffer + actual * IEC60908b::FRAMESIZE_RAW; - IEC60908b::MSF time(lba + 150); - long ret = (*this.*m_cdimg_read_func)(m_cdHandle, 0, ptr, lba++); - m_ppf.maybePatchSector(ptr, time); - if (ret < 0) return actual; + if (lba < m_ti[1].length.toLBA()) { + IEC60908b::MSF time(lba + 150); + long ret = (*this.*m_cdimg_read_func)(m_cdHandle, 0, ptr, lba++); + m_ppf.maybePatchSector(ptr, time); + if (ret < 0) return actual; + } else { + if (!readCDDA(IEC60908b::MSF(lba++), ptr)) return actual; + } actual++; } @@ -484,12 +514,12 @@ bool PCSX::CDRIso::readCDDA(IEC60908b::MSF msf, unsigned char *buffer) { unsigned int file, track, track_start = 0; int ret; - m_cddaCurPos = msf.toLBA(); + uint32_t lba = msf.toLBA(); // find current track index for (track = m_numtracks;; track--) { track_start = m_ti[track].start.toLBA(); - if (track_start <= m_cddaCurPos) break; + if (track_start <= lba) break; if (track == 1) break; } @@ -499,6 +529,12 @@ bool PCSX::CDRIso::readCDDA(IEC60908b::MSF msf, unsigned char *buffer) { return true; } + // if above the track's end (likely next track's pregap), play silence + if (lba >= (track_start + m_ti[track].length.toLBA())) { + memset(buffer, 0, IEC60908b::FRAMESIZE_RAW); + return true; + } + file = 1; if (m_multifile) { // find the file that contains this track @@ -507,12 +543,7 @@ bool PCSX::CDRIso::readCDDA(IEC60908b::MSF msf, unsigned char *buffer) { } } - /* Need to decode audio track first if compressed still (lazy) */ - if (m_ti[file].cddatype > trackinfo::BIN) { - do_decode_cdda(&(m_ti[file]), file); - } - - ret = (*this.*m_cdimg_read_func)(m_ti[file].handle, m_ti[track].start_offset, buffer, m_cddaCurPos - track_start); + ret = (*this.*m_cdimg_read_func)(m_ti[file].handle, m_ti[track].start_offset, buffer, lba - track_start); if (ret != IEC60908b::FRAMESIZE_RAW) { memset(buffer, 0, IEC60908b::FRAMESIZE_RAW); return false; diff --git a/src/cdrom/cdriso.h b/src/cdrom/cdriso.h index 1d44e5ec6..828cddb6a 100644 --- a/src/cdrom/cdriso.h +++ b/src/cdrom/cdriso.h @@ -47,19 +47,19 @@ class CDRIso { const std::filesystem::path& getIsoPath() { return m_isoPath; } uint8_t getTN() { return std::max(m_numtracks, 1); } IEC60908b::MSF getTD(uint8_t track); + IEC60908b::MSF getLength(uint8_t track); + IEC60908b::MSF getPregap(uint8_t track); bool readTrack(const IEC60908b::MSF time); unsigned readSectors(uint32_t lba, void* buffer, unsigned count); uint8_t* getBuffer(); const IEC60908b::Sub* getBufferSub(); - bool readCDDA(IEC60908b::MSF msf, unsigned char* buffer); + bool readCDDA(const IEC60908b::MSF msf, unsigned char* buffer); bool failed(); unsigned m_cdrIsoMultidiskCount; unsigned m_cdrIsoMultidiskSelect; - int get_compressed_cdda_track_length(const char* filepath); - bool CheckSBI(const uint8_t* time); private: @@ -86,7 +86,6 @@ class CDRIso { IEC60908b::Sub m_subbuffer; bool m_cddaBigEndian = false; - uint32_t m_cddaCurPos = 0; /* Frame offset into CD image where pregap data would be found if it was there. * If a game seeks there we must *not* return subchannel data since it's * not in the CD image, so that cdrom code can fake subchannel data instead. @@ -132,13 +131,11 @@ class CDRIso { struct trackinfo { TrackType type = TrackType::CLOSED; + IEC60908b::MSF pregap; IEC60908b::MSF start; IEC60908b::MSF length; IO handle = nullptr; // for multi-track images CDDA enum cddatype_t { NONE = 0, BIN = 1, CCDDA = 2 } cddatype = NONE; // BIN, WAV, MP3, APE - char* decoded_buffer = nullptr; - uint32_t len_decoded_buffer = 0; - char filepath[256] = {0}; uint32_t start_offset = 0; // byte offset from start of above file }; @@ -152,7 +149,6 @@ class CDRIso { PPF m_ppf; void decodeRawSubData(); - int do_decode_cdda(struct trackinfo* tri, uint32_t tracknumber); bool parsetoc(const char* isofile); bool parsecue(const char* isofile); bool parseccd(const char* isofile); diff --git a/src/cdrom/iec-60908b.h b/src/cdrom/iec-60908b.h index 0f6b1141b..415ef38ad 100644 --- a/src/cdrom/iec-60908b.h +++ b/src/cdrom/iec-60908b.h @@ -155,6 +155,6 @@ struct fmt::formatter { template auto format(PCSX::IEC60908b::MSF const &msf, FormatContext &ctx) { - return fmt::format_to(ctx.out(), "{0}:{1}:{2}", msf.m, msf.s, msf.f); + return fmt::format_to(ctx.out(), "{0:02}:{1:02}:{2:02}", msf.m, msf.s, msf.f); } }; diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 773aca6d9..71caa1f46 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -34,6 +34,10 @@ namespace PCSX { +namespace Widgets { +class IsoBrowser; +} + struct CdrStat { uint32_t Type; uint32_t Status; @@ -156,6 +160,7 @@ class CDRom { friend SaveStates::SaveState SaveStates::constructSaveState(); private: + friend class Widgets::IsoBrowser; std::string m_cdromId; std::string m_cdromLabel; }; diff --git a/src/core/psxmem.cc b/src/core/psxmem.cc index e4cf59d99..569fb274e 100644 --- a/src/core/psxmem.cc +++ b/src/core/psxmem.cc @@ -34,6 +34,7 @@ #include "support/file.h" static const std::map s_knownBioses = { +#ifdef USE_ADLER {0x1002e6b5, "SCPH-1002 (EU)"}, {0x1ac46cf1, "SCPH-5000 (JP)"}, {0x24e21a0e, "SCPH-7003 (US)"}, @@ -58,6 +59,32 @@ static const std::map s_knownBioses = { {0xe7ca4fad, "????"}, {0xf380c9ff, "SCPH-5000"}, {0xfb4afc11, "SCPH-5000 (2)"}, +#else + {0x0bad7ea9, "????"}, + {0x171bdcec, "SCPH-101"}, + {0x1e26792f, "SCPH-1002 - DTLH-3002 (EU)"}, + {0x24fc7e17, "SCPH-5000 (JP)"}, + {0x318178bf, "SCPH-7502 (EU)"}, + {0x3539def6, "SCPH-3000 (JP)"}, + {0x37157331, "SCPH-1001 - DTLH-3000 (US)"}, + {0x3b601fc8, "SCPH-1000 (JP)"}, + {0x4d9e7c86, "SCPH-5502 - SCPH-5552 (2) (EU)"}, + {0x502224b6, "SCPH-7001 (US)"}, + {0x55847d8c, "????"}, + {0x76b880e5, "????"}, + {0x826ac185, "SCPH-5000"}, + {0x86c30531, "????"}, + {0x8c93a399, "SCPH-5000 (2)"}, + {0x8d8cb7e4, "SCPH-7003 (US)"}, + {0x9bb87c4b, "SCPH-1002 (EU)"}, + {0xaff00f2f, "????"}, + {0xbc190209, "SCPH-3500 (JP)"}, + {0xd786f0b9, "SCPH-5502 - SCPH-5552 (EU)"}, + {0xdecb22f5, "????"}, + {0xec541cd0, "SCPH-7000 (JP)"}, + {0xf2af798b, "????"}, + {0xff3eeb8c, "SCPH-5500 (JP)"}, +#endif }; int PCSX::Memory::init() { @@ -148,15 +175,15 @@ The distributed OpenBIOS.bin file can be an appropriate BIOS replacement. f->close(); PCSX::g_system->printf(_("Loaded BIOS: %s\n"), biosPath.string()); } - uint32_t adler = adler32(0L, Z_NULL, 0); - m_biosAdler32 = adler = adler32(adler, m_psxR, bios_size); - auto it = s_knownBioses.find(adler); + uint32_t crc = crc32(0L, Z_NULL, 0); + m_biosCRC = crc = crc32(crc, m_psxR, bios_size); + auto it = s_knownBioses.find(crc); if (it != s_knownBioses.end()) { - g_system->printf(_("Known BIOS detected: %s (%08x)\n"), it->second, adler); + g_system->printf(_("Known BIOS detected: %s (%08x)\n"), it->second, crc); } else if (strncmp((const char *)&m_psxR[0x78], "OpenBIOS", 8) == 0) { - g_system->printf(_("OpenBIOS detected (%08x)\n"), adler); + g_system->printf(_("OpenBIOS detected (%08x)\n"), crc); } else { - g_system->printf(_("Unknown bios loaded (%08x)\n"), adler); + g_system->printf(_("Unknown bios loaded (%08x)\n"), crc); } } @@ -431,7 +458,7 @@ void PCSX::Memory::setLuts() { } std::string_view PCSX::Memory::getBiosVersionString() { - auto it = s_knownBioses.find(m_biosAdler32); + auto it = s_knownBioses.find(m_biosCRC); if (it == s_knownBioses.end()) return "Unknown"; return it->second; } diff --git a/src/core/psxmem.h b/src/core/psxmem.h index 458dd37a6..b793d98c0 100644 --- a/src/core/psxmem.h +++ b/src/core/psxmem.h @@ -133,12 +133,12 @@ class Memory { void setLuts(); - uint32_t getBiosAdler32() { return m_biosAdler32; } + uint32_t getBiosAdler32() { return m_biosCRC; } std::string_view getBiosVersionString(); private: int m_writeok = 1; - uint32_t m_biosAdler32 = 0; + uint32_t m_biosCRC = 0; }; } // namespace PCSX diff --git a/src/gui/gui.cc b/src/gui/gui.cc index 77fe38fe5..bbcf7bc65 100644 --- a/src/gui/gui.cc +++ b/src/gui/gui.cc @@ -1131,6 +1131,8 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)")); ImGui::Separator(); ImGui::MenuItem(_("Show SIO1 debug"), nullptr, &m_sio1.m_show); ImGui::Separator(); + ImGui::MenuItem(_("Show Iso Browser"), nullptr, &m_isoBrowser.m_show); + ImGui::Separator(); if (ImGui::MenuItem(_("Start GPU dump"))) { PCSX::g_emulator->m_gpu->startDump(); } @@ -1348,6 +1350,10 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)")); m_sio1.draw(this, &PCSX::g_emulator->m_sio1->m_regs, _("SIO1 Debug")); } + if (m_isoBrowser.m_show) { + m_isoBrowser.draw(g_emulator->m_cdrom.get(), _("ISO Browser")); + } + if (m_showCfg) changed |= configure(); if (g_emulator->m_spu->m_showCfg) changed |= g_emulator->m_spu->configure(); g_emulator->m_spu->debug(); @@ -1520,7 +1526,7 @@ the update and manually apply it.)"))); ImGui::Text(_("Write rate: %s"), rate.c_str()); byteRateToString(UvFile::getDownloadRate(), rate); ImGui::Text(_("Download rate: %s"), rate.c_str()); - if (ImGui::BeginTable("UvFiles", 2)) { + if (ImGui::BeginTable("UvFiles", 2, ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn(_("Caching")); ImGui::TableSetupColumn(_("Filename")); ImGui::TableHeadersRow(); diff --git a/src/gui/gui.h b/src/gui/gui.h index d1a8ee229..25c61718e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -41,6 +41,7 @@ #include "gui/widgets/events.h" #include "gui/widgets/filedialog.h" #include "gui/widgets/handlers.h" +#include "gui/widgets/isobrowser.h" #include "gui/widgets/kernellog.h" #include "gui/widgets/log.h" #include "gui/widgets/luaeditor.h" @@ -99,6 +100,7 @@ class GUI final { typedef Setting ShowKernelLog; typedef Setting ShowCallstacks; typedef Setting ShowSIO1; + typedef Setting ShowIsoBrowser; typedef Setting WindowPosX; typedef Setting WindowPosY; typedef Setting WindowSizeX; @@ -113,8 +115,8 @@ class GUI final { IdleSwapInterval, ShowLuaConsole, ShowLuaInspector, ShowLuaEditor, ShowMainVRAMViewer, ShowCLUTVRAMViewer, ShowVRAMViewer1, ShowVRAMViewer2, ShowVRAMViewer3, ShowVRAMViewer4, ShowMemoryObserver, ShowTypedDebugger, ShowMemcardManager, ShowRegisters, ShowAssembly, ShowDisassembly, ShowBreakpoints, ShowEvents, - ShowHandlers, ShowKernelLog, ShowCallstacks, ShowSIO1, MainFontSize, MonoFontSize, GUITheme, - EnableRawMouseMotion, WidescreenRatio> + ShowHandlers, ShowKernelLog, ShowCallstacks, ShowSIO1, ShowIsoBrowser, MainFontSize, MonoFontSize, + GUITheme, EnableRawMouseMotion, WidescreenRatio> settings; // imgui can't handle more than one "instance", so... @@ -323,6 +325,7 @@ class GUI final { Widgets::FileDialog m_openBinaryDialog = {[]() { return _("Open Binary"); }}; Widgets::FileDialog m_selectBiosDialog = {[]() { return _("Select BIOS"); }}; Widgets::Breakpoints m_breakpoints = {settings.get().value}; + Widgets::IsoBrowser m_isoBrowser = {settings.get().value}; bool m_breakOnVSync = false; bool m_showCfg = false; diff --git a/src/gui/widgets/isobrowser.cc b/src/gui/widgets/isobrowser.cc new file mode 100644 index 000000000..9eed668ed --- /dev/null +++ b/src/gui/widgets/isobrowser.cc @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (C) 2022 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include "gui/widgets/isobrowser.h" + +#include + +#include + +#include "core/cdrom.h" +#include "fmt/format.h" +#include "imgui/imgui.h" +#include "support/uvfile.h" + +static void ShowHelpMarker(const char* desc) { + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +PCSX::Coroutine<> PCSX::Widgets::IsoBrowser::computeCRC(PCSX::CDRIso* iso) { + auto time = std::chrono::steady_clock::now(); + + uint32_t fullCRC = crc32(0L, Z_NULL, 0); + uint32_t lba = 0; + for (unsigned t = 1; t <= iso->getTN(); t++) { + uint32_t len = iso->getLength(t).toLBA(); + uint32_t crc = crc32(0L, Z_NULL, 0); + uint8_t buffer[2352]; + for (unsigned s = 0; s < len; s++) { + iso->readSectors(lba++, buffer, 1); + fullCRC = crc32(fullCRC, buffer, 2352); + crc = crc32(crc, buffer, 2352); + if (std::chrono::steady_clock::now() - time > std::chrono::milliseconds(50)) { + m_crcProgress = (float)s / (float)len; + co_yield m_crcCalculator.awaiter(); + time = std::chrono::steady_clock::now(); + } + } + m_crcs[t] = crc; + } + + m_fullCRC = fullCRC; +}; + +void PCSX::Widgets::IsoBrowser::draw(CDRom* cdrom, const char* title) { + if (!ImGui::Begin(title, &m_show, ImGuiWindowFlags_MenuBar)) { + ImGui::End(); + return; + } + + bool showOpenIsoFileDialog = false; + + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu(_("File"))) { + showOpenIsoFileDialog = ImGui::MenuItem(_("Open Disk Image")); + if (ImGui::MenuItem(_("Close Disk Image"))) { + g_emulator->m_cdrom->setIso(new CDRIso()); + g_emulator->m_cdrom->check(); + } + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + auto& isoPath = g_emulator->settings.get(); + + if (showOpenIsoFileDialog) { + if (!isoPath.empty()) { + m_openIsoFileDialog.m_currentPath = isoPath.value; + } + m_openIsoFileDialog.openDialog(); + } + if (m_openIsoFileDialog.draw()) { + isoPath.value = m_openIsoFileDialog.m_currentPath; + std::vector fileToOpen = m_openIsoFileDialog.selected(); + if (!fileToOpen.empty()) { + g_emulator->m_cdrom->setIso(new CDRIso(reinterpret_cast(fileToOpen[0].c_str()))); + g_emulator->m_cdrom->check(); + } + } + auto iso = cdrom->m_iso.get(); + + if (iso->failed()) { + ImGui::TextWrapped(_("No iso or invalid iso loaded.")); + ImGui::End(); + return; + } + ImGui::Text(_("GAME ID: %s"), g_emulator->m_cdrom->getCDRomID().c_str()); + ImGui::Text(_("GAME Label: %s"), g_emulator->m_cdrom->getCDRomLabel().c_str()); + + bool canCache = false; + bool isCaching = false; + float cacheProgress = 0.0f; + UvThreadOp::iterateOverAllOps([&canCache, &isCaching, &cacheProgress](UvThreadOp* f) { + if (f->caching() && (f->cacheProgress() < 1.0f)) { + isCaching = true; + cacheProgress = f->cacheProgress(); + } + if (f->canCache() && !f->caching()) canCache = true; + }); + if (isCaching) { + ImGui::ProgressBar(cacheProgress); + } else { + if (!canCache) ImGui::BeginDisabled(); + if (ImGui::Button(_("Cache files"))) { + UvThreadOp::iterateOverAllOps([](UvThreadOp* f) { + if (!f->caching() && f->canCache()) f->startCaching(); + }); + } + if (!canCache) ImGui::EndDisabled(); + } + + if (m_crcCalculator.done()) { + if (ImGui::Button(_("Compute CRCs"))) { + m_crcProgress = 0.0f; + m_crcCalculator = computeCRC(iso); + } + + ShowHelpMarker(_(R"(Computes the CRC32 of each track, and of +the whole disk. The CRC32 is computed on the raw data, +after decompression of the tracks. This is useful to +check the disk image against redump's information. + +The computation can be slow, and can be sped up +significantly by caching the files beforehand.)")); + } else { + ImGui::ProgressBar(m_crcProgress); + m_crcCalculator.resume(); + } + + auto str = fmt::format(f_("Disc size: {} ({}) - CRC32: {:08x}"), iso->getTD(0), iso->getTD(0).toLBA(), m_fullCRC); + ImGui::TextUnformatted(str.c_str()); + if (ImGui::BeginTable("Tracks", 5, ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn(_("Track")); + ImGui::TableSetupColumn(_("Start")); + ImGui::TableSetupColumn(_("Length")); + ImGui::TableSetupColumn(_("Pregap")); + ImGui::TableSetupColumn("CRC32"); + ImGui::TableHeadersRow(); + for (unsigned t = 1; t <= iso->getTN(); t++) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("%d", t); + ImGui::TableSetColumnIndex(1); + str = fmt::format("{} ({})", iso->getTD(t), iso->getTD(t).toLBA()); + ImGui::TextUnformatted(str.c_str()); + ImGui::TableSetColumnIndex(2); + str = fmt::format("{} ({})", iso->getLength(t), iso->getLength(t).toLBA()); + ImGui::TextUnformatted(str.c_str()); + ImGui::TableSetColumnIndex(3); + str = fmt::format("{} ({})", iso->getPregap(t), iso->getPregap(t).toLBA()); + ImGui::TextUnformatted(str.c_str()); + ImGui::TableSetColumnIndex(4); + str = fmt::format("{:08x}", m_crcs[t]); + ImGui::TextUnformatted(str.c_str()); + } + ImGui::EndTable(); + } + + ImGui::End(); +} diff --git a/src/gui/widgets/isobrowser.h b/src/gui/widgets/isobrowser.h new file mode 100644 index 000000000..932a780c2 --- /dev/null +++ b/src/gui/widgets/isobrowser.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2022 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#pragma once + +#include + +#include + +#include "gui/widgets/filedialog.h" +#include "support/coroutine.h" + +namespace PCSX { + +class CDRom; +class CDRIso; + +namespace Widgets { + +class IsoBrowser { + public: + IsoBrowser(bool& show) : m_show(show) {} + void draw(CDRom* cdrom, const char* title); + + bool& m_show; + + private: + uint32_t m_fullCRC = 0; + uint32_t m_crcs[100] = {0}; + Coroutine<> m_crcCalculator; + float m_crcProgress = 0.0f; + + Coroutine<> computeCRC(CDRIso*); + FileDialog m_openIsoFileDialog = {[]() { return _("Open Disk Image"); }}; +}; + +} // namespace Widgets +} // namespace PCSX diff --git a/src/support/coroutine.h b/src/support/coroutine.h new file mode 100644 index 000000000..ccbc2f8ff --- /dev/null +++ b/src/support/coroutine.h @@ -0,0 +1,112 @@ +/*************************************************************************** + * Copyright (C) 2022 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#pragma once + +#ifdef __APPLE__ +// Why has Apple become the Microsoft of Software Engineering? +#include +#else +#include +#endif +#include + +namespace PCSX { + +template +struct Coroutine { +#ifdef __APPLE__ + template + using CoroutineHandle = std::experimental::coroutine_handle; + using CoroutineHandleVoid = std::experimental::coroutine_handle; +#else + template + using CoroutineHandle = std::coroutine_handle; + using CoroutineHandleVoid = std::coroutine_handle; +#endif + struct Empty {}; + typedef typename std::conditional::value, Empty, T>::type SafeT; + + Coroutine() = default; + Coroutine(Coroutine &&other) = default; + Coroutine &operator=(Coroutine &&other) = default; + Coroutine(Coroutine const &) = delete; + Coroutine &operator=(Coroutine const &) = delete; + + struct Awaiter { + constexpr bool await_ready() const noexcept { return false; } + constexpr void await_suspend(CoroutineHandleVoid) const noexcept {} + constexpr void await_resume() const noexcept {} + }; + + Awaiter awaiter() { return {}; } + void resume() { + if (!m_handle) return; + m_handle.resume(); + } + + bool done() { + if (!m_handle) return true; + bool isDone = m_handle.done(); + if (isDone) { + m_handle.destroy(); + m_handle = nullptr; + } + return isDone; + } + + const SafeT &value() const { return m_value; } + + private: + struct PromiseVoid { + Coroutine get_return_object() { return Coroutine{std::move(CoroutineHandle::from_promise(*this))}; } + Awaiter initial_suspend() { return {}; } + Awaiter final_suspend() noexcept { return {}; } + void unhandled_exception() {} + template + Awaiter yield_value(From &&from) { + return {}; + } + void return_void() {} + }; + struct PromiseValue { + PromiseValue(Coroutine *c) : coroutine(c) {} + Coroutine get_return_object() { return Coroutine{std::move(CoroutineHandle::from_promise(*this))}; } + Awaiter initial_suspend() { return {}; } + Awaiter final_suspend() noexcept { return {}; } + void unhandled_exception() {} + // This should be an std::convertible_to, but Apple still doesn't have a fully C++-20 conformant library. + template + Awaiter yield_value(From &&from) { + coroutine->m_value = std::forward(from); + return {}; + } + void return_value(T &&value) { coroutine->m_value = std::forward(value); } + Coroutine coroutine = nullptr; + }; + typedef typename std::conditional::value, PromiseVoid, PromiseValue>::type Promise; + Coroutine(CoroutineHandle &&handle) : m_handle(std::move(handle)) {} + CoroutineHandle m_handle; + [[no_unique_address]] SafeT m_value; + + public: + using promise_type = Promise; +}; + +} // namespace PCSX diff --git a/src/support/ffmpeg-audio-file.cc b/src/support/ffmpeg-audio-file.cc new file mode 100644 index 000000000..58f5d68a3 --- /dev/null +++ b/src/support/ffmpeg-audio-file.cc @@ -0,0 +1,247 @@ +/*************************************************************************** + * Copyright (C) 2022 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include "support/ffmpeg-audio-file.h" + +PCSX::FFmpegAudioFile::FFmpegAudioFile(IO file, Channels channels, Endianness endianess, unsigned frequency) + : File(RO_SEEKABLE), m_file(file), m_channels(channels), m_endianess(endianess) { + av_log_set_level(AV_LOG_QUIET); + + unsigned char *buffer = reinterpret_cast(av_malloc(4096)); + int ret; + + m_ioContext = avio_alloc_context( + buffer, 4096, 0, this, + [](void *opaque, uint8_t *buf, int bufSize) -> int { + FFmpegAudioFile *f = reinterpret_cast(opaque); + if (f->m_file->failed()) return AVERROR_STREAM_NOT_FOUND; + auto ret = f->m_file->read(buf, bufSize); + if (ret <= 0) { + if (f->m_file->eof()) return AVERROR_EOF; + return AVERROR_UNKNOWN; + } + return ret; + }, + nullptr, + [](void *opaque, int64_t offset, int whence) -> int64_t { + FFmpegAudioFile *f = reinterpret_cast(opaque); + if (f->m_file->failed()) return AVERROR_STREAM_NOT_FOUND; + if ((whence & AVSEEK_SIZE) == AVSEEK_SIZE) { + return f->m_file->size(); + } + auto ret = f->m_file->rSeek(offset, whence & 0xffff); + if (ret < 0) return AVERROR_UNKNOWN; + return ret; + }); + + if (!m_ioContext) { + av_freep(&buffer); + m_failed = true; + return; + } + + m_formatContext = avformat_alloc_context(); + if (!m_formatContext) { + m_failed = true; + return; + } + + m_formatContext->pb = m_ioContext; + if (avformat_open_input(&m_formatContext, nullptr, nullptr, nullptr) < 0) { + m_failed = true; + return; + } + + if (avformat_find_stream_info(m_formatContext, nullptr) < 0) { + m_failed = true; + return; + } + double duration = ((double)m_formatContext->duration) / ((double)AV_TIME_BASE); + unsigned sampleSize = 2; + if (channels == CHANNELS_STEREO) sampleSize *= 2; + m_size = ceil(duration * frequency * sampleSize); + + m_packet = av_packet_alloc(); + m_decodedFrame = av_frame_alloc(); + m_resampledFrame = av_frame_alloc(); + m_resampledFrame->sample_rate = frequency; + m_resampledFrame->format = AV_SAMPLE_FMT_S16; + m_resampledFrame->ch_layout.nb_channels = channels == CHANNELS_STEREO ? 2 : 1; + m_resampledFrame->ch_layout.order = AV_CHANNEL_ORDER_NATIVE; + m_resampledFrame->ch_layout.u.mask = channels == CHANNELS_STEREO ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; + m_resampledFrame->nb_samples = 0; + const AVCodec *codec; + ret = av_find_best_stream(m_formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); + + if (!m_packet || !m_decodedFrame || !m_resampledFrame || (ret < 0)) { + m_failed = true; + return; + } + m_audioStreamIndex = ret; + + m_codecContext = avcodec_alloc_context3(codec); + if (!m_codecContext) { + m_failed = true; + return; + } + + avcodec_parameters_to_context(m_codecContext, m_formatContext->streams[m_audioStreamIndex]->codecpar); + + if (avcodec_open2(m_codecContext, codec, nullptr) < 0) { + m_failed = true; + return; + } + + AVChannelLayout layout; + if (channels == CHANNELS_STEREO) { + layout = AV_CHANNEL_LAYOUT_STEREO; + } else { + layout = AV_CHANNEL_LAYOUT_MONO; + } + if (swr_alloc_set_opts2(&m_resamplerContext, &layout, AV_SAMPLE_FMT_S16, frequency, &m_codecContext->ch_layout, + m_codecContext->sample_fmt, m_codecContext->sample_rate, 0, nullptr) < 0) { + m_failed = true; + return; + } + + swr_init(m_resamplerContext); + if (!swr_is_initialized(m_resamplerContext)) { + m_failed = true; + return; + } + + m_failed = av_seek_frame(m_formatContext, m_audioStreamIndex, 0, AVSEEK_FLAG_BYTE) < 0; +} + +PCSX::FFmpegAudioFile::~FFmpegAudioFile() { + if (m_ioContext) { + av_freep(&m_ioContext->buffer); + avio_context_free(&m_ioContext); + } + if (m_formatContext) avformat_free_context(m_formatContext); + if (m_packet) av_packet_free(&m_packet); + if (m_decodedFrame) av_frame_free(&m_decodedFrame); + if (m_resampledFrame) av_frame_free(&m_resampledFrame); + if (m_codecContext) avcodec_free_context(&m_codecContext); + if (m_resamplerContext) swr_free(&m_resamplerContext); +} + +ssize_t PCSX::FFmpegAudioFile::rSeek(ssize_t pos, int wheel) { + switch (wheel) { + case SEEK_SET: + m_filePtr = pos; + break; + case SEEK_END: + m_filePtr = m_size + pos; + break; + case SEEK_CUR: + m_filePtr += pos; + break; + } + return m_filePtr; +} + +ssize_t PCSX::FFmpegAudioFile::read(void *dest_, size_t size) { + uint8_t *dest = reinterpret_cast(dest_); + + ssize_t dumpDelta = m_filePtr - m_totalOut; + if (dumpDelta < 0) { + dumpDelta = m_filePtr; + m_filePtr = 0; + m_hitEOF = false; + if (av_seek_frame(m_formatContext, m_audioStreamIndex, 0, AVSEEK_FLAG_BYTE) < 0) return -1; + m_totalOut = 0; + } + if (m_hitEOF) return -1; + ssize_t ret = 0; + while (dumpDelta) { + uint8_t dummy[256]; + ssize_t toDump = std::min(ssize_t(sizeof(dummy)), dumpDelta); + ssize_t p = decompSome(dummy, toDump); + if (p < 0) return p; + dumpDelta -= p; + if (m_hitEOF || !p) break; + } + while (size) { + if (m_hitEOF) break; + ssize_t p = decompSome(dest, size); + if (p < 0) return p; + if (!p) break; + size -= p; + ret += p; + dest += p; + } + + return ret; +} + +ssize_t PCSX::FFmpegAudioFile::decompSome(void *dest_, ssize_t size) { + unsigned sampleSize = 2; + if (m_channels == CHANNELS_STEREO) sampleSize *= 2; + ssize_t dataRead = 0; + uint8_t *dest = reinterpret_cast(dest_); + ssize_t available = m_resampledFrame->nb_samples * sampleSize - m_packetPtr; + AVFrame *inFrame = nullptr; + + while (true) { + ssize_t toCopy = std::min(available, size); + if (toCopy) { + memcpy(dest, m_resampledFrame->data[0] + m_packetPtr, toCopy); + m_packetPtr += toCopy; + m_totalOut += toCopy; + size -= toCopy; + dest += toCopy; + dataRead += toCopy; + available -= toCopy; + } + if (size == 0) return dataRead; + + m_packetPtr = 0; + + if (swr_convert_frame(m_resamplerContext, m_resampledFrame, inFrame) < 0) return -1; + available = m_resampledFrame->nb_samples * sampleSize; + inFrame = nullptr; + if (available == 0) { + while (true) { + if (av_read_frame(m_formatContext, m_packet) < 0) { + m_hitEOF = true; + return dataRead; + } + if (m_packet->stream_index != m_audioStreamIndex) { + av_packet_unref(m_packet); + continue; + } + break; + } + if (avcodec_send_packet(m_codecContext, m_packet) < 0) return -1; + int ret = avcodec_receive_frame(m_codecContext, m_decodedFrame); + av_packet_unref(m_packet); + int eagain = AVERROR(EAGAIN); + int eof = AVERROR_EOF; + if ((ret == eagain) || (ret == eof)) { + return dataRead; + } else if (ret < 0) { + return -1; + } + inFrame = m_decodedFrame; + } + } + + return -1; +} diff --git a/src/support/ffmpeg-audio-file.h b/src/support/ffmpeg-audio-file.h new file mode 100644 index 000000000..a37704363 --- /dev/null +++ b/src/support/ffmpeg-audio-file.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2022 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#pragma once + +extern "C" { +#include +#include +#include +#include +#include +} + +#include "support/file.h" + +namespace PCSX { + +class FFmpegAudioFile : public File { + public: + enum Channels { CHANNELS_STEREO, CHANNELS_MONO }; + enum Endianness { ENDIANNESS_LITTLE, ENDIANNESS_BIG }; + FFmpegAudioFile(IO file, Channels, Endianness, unsigned frequency); + ~FFmpegAudioFile(); + virtual void close() final override {} + virtual ssize_t rSeek(ssize_t pos, int wheel) final override; + virtual ssize_t rTell() final override { return m_filePtr; } + virtual ssize_t read(void* dest, size_t size) final override; + virtual size_t size() final override { + if (m_size >= 0) return m_size; + throw std::runtime_error("Unable to determine file size"); + } + virtual bool eof() final override { return m_hitEOF; } + virtual File* dup() final override { return new FFmpegAudioFile(m_file, m_channels, m_endianess, m_frequency); }; + virtual bool failed() final override { return m_failed || m_file->failed(); } + + private: + ssize_t decompSome(void* dest, ssize_t size); + IO m_file; + ssize_t m_filePtr = 0; + ssize_t m_size = 0; + bool m_hitEOF = false; + bool m_failed = false; + Channels m_channels; + Endianness m_endianess; + unsigned m_frequency; + AVFormatContext* m_formatContext = nullptr; + AVIOContext* m_ioContext = nullptr; + AVFrame* m_decodedFrame = nullptr; + AVFrame* m_resampledFrame = nullptr; + AVPacket* m_packet = nullptr; + AVCodecContext* m_codecContext = nullptr; + SwrContext* m_resamplerContext = nullptr; + int m_audioStreamIndex = -1; + ssize_t m_totalOut = 0; + size_t m_packetPtr = 0; +}; + +} // namespace PCSX diff --git a/src/support/uvfile.cc b/src/support/uvfile.cc index 8b9072542..f493f0627 100644 --- a/src/support/uvfile.cc +++ b/src/support/uvfile.cc @@ -505,6 +505,7 @@ void PCSX::UvFile::write(Slice &&slice) { } ssize_t PCSX::UvFile::readAt(void *dest, size_t size, size_t ptr) { + if (ptr >= m_size) return -1; size = std::min(m_size - ptr, size); if (size == 0) return -1; float progress = m_cacheProgress.load(std::memory_order_acquire); diff --git a/src/support/zfile.cc b/src/support/zfile.cc index d0216fd4c..4cfe26d2b 100644 --- a/src/support/zfile.cc +++ b/src/support/zfile.cc @@ -38,7 +38,6 @@ ssize_t PCSX::ZReader::rSeek(ssize_t pos, int wheel) { } ssize_t PCSX::ZReader::read(void *dest_, size_t size) { - if (m_hitEOF) return -1; uint8_t *dest = reinterpret_cast(dest_); ssize_t dumpDelta = m_filePtr - m_zstream.total_out; @@ -51,6 +50,7 @@ ssize_t PCSX::ZReader::read(void *dest_, size_t size) { m_zstream.next_in = m_inBuffer; inflateInit2(&m_zstream, m_raw ? -MAX_WBITS : MAX_WBITS); } + if (m_hitEOF) return -1; auto decompSome = [this](void *dest, ssize_t size) -> ssize_t { m_zstream.avail_out = size; m_zstream.next_out = reinterpret_cast(dest); diff --git a/third_party/cueparser/.clang-format b/third_party/cueparser/.clang-format new file mode 100644 index 000000000..5e3751481 --- /dev/null +++ b/third_party/cueparser/.clang-format @@ -0,0 +1,4 @@ +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 120 +AccessModifierOffset: -2 diff --git a/third_party/cueparser/LICENSE b/third_party/cueparser/LICENSE new file mode 100644 index 000000000..495bda3c3 --- /dev/null +++ b/third_party/cueparser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/cueparser/cueparser.c b/third_party/cueparser/cueparser.c new file mode 100644 index 000000000..067bcb4d4 --- /dev/null +++ b/third_party/cueparser/cueparser.c @@ -0,0 +1,705 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "cueparser/cueparser.h" + +#include +#include +#include +#include +#include + +#include "cueparser/disc.h" +#include "cueparser/fileabstract.h" +#include "cueparser/scheduler.h" + +#pragma GCC diagnostic ignored "-Wswitch" + +enum Keyword { + KW_EMPTY = 0x00001505, + KW_4CH = 0x0b86667a, + KW_AIFF = 0x7c7ea8ad, + KW_AUDIO = 0x0c561a33, + KW_BINARY = 0x9da252ca, + KW_CATALOG = 0x2316d516, + KW_CDG = 0x0b87cf65, + KW_CDI_2336 = 0x2f162780, + KW_CDI_2352 = 0x2f1626c2, + KW_CDTEXTFILE = 0xbbd08639, + KW_DCP = 0x0b87bad2, + KW_FILE = 0x7c7e1383, + KW_FLAGS = 0x0c434e1a, + KW_INDEX = 0x0cda305b, + KW_ISRC = 0x7c8344ae, + KW_MODE1_2048 = 0x10a03916, + KW_MODE1_2352 = 0x10a02cbe, + KW_MODE2_2336 = 0x0e924c9f, + KW_MODE2_2352 = 0x0e924d5d, + KW_MOTOROLA = 0x7ba98cec, + KW_MP3 = 0x0b87c98b, + KW_PERFORMER = 0xcc7e14e3, + KW_POSTGAP = 0xb61fc6ab, + KW_PRE = 0x0b880de2, + KW_PREGAP = 0xc625ff94, + KW_REM = 0x0b88069f, + KW_SCMS = 0x7c8a8e8b, + KW_SONGWRITER = 0xb05cc69f, + KW_TITLE = 0x0d83f885, + KW_TRACK = 0x0d7ac5ca, + KW_WAVE = 0x7c885360, +}; + +static void new_keyword(struct CueParser* parser) { parser->keyword = 5381; } + +static void keyword_add_char(struct CueParser* parser, int c) { + uint32_t hash = parser->keyword; + hash = ((hash << 5) + hash) ^ c; + parser->keyword = hash; +} + +struct end_Closure { + struct CueScheduler* scheduler; + void (*destroy)(struct CueClosure*); + void (*call)(struct CueClosure*); + struct CueClosure* next; + struct CueParser* parser; + const char* error; +}; + +static void closure_generic_free(struct CueClosure* closure) { free(closure); } + +static void end_closure_call(struct CueClosure* closure_) { + struct end_Closure* closure = (struct end_Closure*)closure_; + closure->parser->cb(closure->parser, closure->scheduler, closure->error); +} + +static void reset_word(char* word) { *(uint8_t*)&word[255] = 255; } + +static int append_to_word(char* word, int c) { + uint8_t* b = (uint8_t*)word; + int len = 255 - b[255]; + if (len == 255) return 0; + word[len++] = c; + b[255] = 255 - len; + return 1; +} + +static int word_len(char* word) { + uint8_t* b = (uint8_t*)word; + return 255 - b[255]; +} + +static void end_word(char* word) { + int len = word_len(word); + word[len] = 0; + reset_word(word); +} + +void CueParser_construct(struct CueParser* parser, struct CueDisc* disc) { + parser->disc = disc; + parser->cursor = 0; + reset_word(parser->word); + parser->amount = 0; + parser->state = CUE_PARSER_START; + parser->inQuotes = 0; + parser->afterQuotes = 0; + parser->inRem = 0; + parser->gotSpace = 1; + parser->currentFileSize = 0; + parser->currentFile = NULL; + parser->currentTrack = 0; + parser->currentSectorNumber = 0; + parser->isTrackANewFile = 0; + disc->catalog[0] = 0; + disc->isrc[0] = 0; + disc->trackCount = 0; + for (unsigned i = 0; i < MAXTRACK; i++) { + disc->tracks[i].file = NULL; + } + new_keyword(parser); +} + +static void close_cb(struct CueFile* file, struct CueScheduler* scheduler) { + struct CueParser* parser = file->user; + parser->cb(parser, scheduler, NULL); +} + +void CueParser_close(struct CueParser* parser, struct CueScheduler* scheduler, + void (*cb)(struct CueParser*, struct CueScheduler*, const char*)) { + if (parser->currentFile) { + if (--parser->currentFile->references) { + parser->cb = cb; + parser->currentFile->user = parser; + parser->currentFile->close(parser->currentFile, scheduler, close_cb); + } + } else { + parser->currentFile = NULL; + } +} + +void CueParser_destroy(struct CueParser* parser) {} + +static int needs_argument(struct CueParser* parser) { + switch (parser->state) { + case CUE_PARSER_CATALOG: + case CUE_PARSER_CDTEXTFILE: + case CUE_PARSER_FILE_FILENAME: + case CUE_PARSER_INDEX_NUMBER: + case CUE_PARSER_INDEX_TIMECODE: + case CUE_PARSER_ISRC: + case CUE_PARSER_PERFORMER: + case CUE_PARSER_POSTGAP: + case CUE_PARSER_PREGAP: + case CUE_PARSER_SONGWRITER: + case CUE_PARSER_TITLE: + case CUE_PARSER_TRACK_NUMBER: + return 1; + } + return 0; +} + +static void schedule_read(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler); +static void size_cb(struct CueFile* file, struct CueScheduler* scheduler, uint64_t size); + +static int32_t timecodeToSectorNumber(char* timecode) { + char* endptr; + int min = strtol(timecode, &endptr, 10); + if (*endptr != ':' || (min < 0) || (min >= 100)) { + return -1; + } + int sec = strtol(endptr + 1, &endptr, 10); + if (*endptr != ':' || (sec < 0) || (sec >= 60)) { + return -1; + } + int fra = strtol(endptr + 1, &endptr, 10); + if (*endptr || (fra < 0) || (fra >= 75)) { + return -1; + } + return fra + sec * 75 + min * 60 * 75; +} + +void end_parse(struct CueParser* parser, struct CueScheduler* scheduler, const char* error) { + struct end_Closure* closure = malloc(sizeof(struct end_Closure)); + assert(closure); + closure->destroy = closure_generic_free; + closure->call = end_closure_call; + closure->parser = parser; + closure->error = error; + Scheduler_schedule(scheduler, (struct CueClosure*)closure); +} + +static void parse(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler) { + while (parser->amount--) { + int c = *parser->start++; + int isEOW = isspace(c); + int isEOL = (c == '\r') || (c == '\n'); + int isSpace = isEOW && !isEOL; + if (!isEOW) keyword_add_char(parser, c); + if (parser->inQuotes) { + if (c == '"') { + parser->inQuotes = 0; + parser->afterQuotes = 1; + } else { + if (!append_to_word(parser->word, c)) { + end_parse(parser, scheduler, "cuesheet argument too long"); + return; + } + } + continue; + } + if (parser->inRem) { + if (isEOL) parser->inRem = 0; + continue; + } + if (parser->afterQuotes) { + if (!isEOW) { + end_parse(parser, scheduler, "cuesheet quote imbalance (got characters after quotes)"); + return; + } + parser->afterQuotes = 0; + } + if (!isEOW) { + if (needs_argument(parser)) { + if (c == '"') { + if (parser->gotSpace) { + parser->inQuotes = 1; + } else { + end_parse(parser, scheduler, + "cuesheet quote imbalance (got a quote in the middle of an argument)"); + return; + } + } else if (!append_to_word(parser->word, c)) { + end_parse(parser, scheduler, "cuesheet argument too long"); + return; + } + } + parser->gotSpace = 0; + continue; + } + parser->gotSpace = 1; + enum Keyword keyword = parser->keyword; + if ((keyword == KW_EMPTY) && isSpace) continue; + int len = word_len(parser->word); + if (needs_argument(parser)) end_word(parser->word); + new_keyword(parser); + switch (parser->state) { + case CUE_PARSER_START: + switch (keyword) { + case KW_EMPTY: + break; + case KW_REM: + parser->inRem = 1; + break; + case KW_CATALOG: + if (parser->disc->catalog[0]) { + end_parse(parser, scheduler, "cuesheet has too many CATALOG arguments"); + return; + } + parser->state = CUE_PARSER_CATALOG; + break; + case KW_CDTEXTFILE: + parser->state = CUE_PARSER_CDTEXTFILE; + break; + case KW_FILE: + parser->state = CUE_PARSER_FILE_FILENAME; + break; + case KW_FLAGS: + parser->state = CUE_PARSER_FLAGS; + break; + case KW_INDEX: + parser->state = CUE_PARSER_INDEX_NUMBER; + break; + case KW_ISRC: + if (parser->disc->isrc[0]) { + end_parse(parser, scheduler, "cuesheet has too many ISRC arguments"); + return; + } + parser->state = CUE_PARSER_ISRC; + break; + case KW_PERFORMER: + parser->state = CUE_PARSER_PERFORMER; + break; + case KW_POSTGAP: + parser->state = CUE_PARSER_POSTGAP; + break; + case KW_PREGAP: + parser->state = CUE_PARSER_PREGAP; + break; + case KW_SONGWRITER: + parser->state = CUE_PARSER_SONGWRITER; + break; + case KW_TITLE: + parser->state = CUE_PARSER_SONGWRITER; + break; + case KW_TRACK: + parser->state = CUE_PARSER_TRACK_NUMBER; + break; + default: + end_parse(parser, scheduler, "unknown keyword in cuesheet"); + return; + } + break; + case CUE_PARSER_CATALOG: + if (len != 13) { + end_parse(parser, scheduler, "cuesheet CATALOG argument isn't 13 characters"); + return; + } + memcpy(parser->disc->catalog, parser->word, 14); + parser->state = CUE_PARSER_START; + break; + case CUE_PARSER_CDTEXTFILE: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet CDTEXTFILE missing its filename argument"); + return; + } + parser->state = CUE_PARSER_START; + end_parse(parser, scheduler, "cuesheet CDTEXTFILE not supported at the moment"); + return; + break; + case CUE_PARSER_FILE_FILENAME: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet FILE missing its filename argument"); + return; + } else { + struct CueFile* binaryFile = malloc(sizeof(struct CueFile)); + assert(binaryFile); + binaryFile->user = file; + if (parser->isTrackANewFile) { + end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK"); + return; + } + if (parser->currentFile) { + if (parser->currentFile->references == 1) { + end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK"); + return; + } + parser->currentFile->references--; + parser->currentFile = NULL; + } + if (!parser->open(binaryFile, scheduler, parser->word)) { + binaryFile->destroy(binaryFile); + free(binaryFile); + end_parse(parser, scheduler, "cuesheet references a file that can't be found"); + return; + } + parser->isTrackANewFile = 1; + parser->currentFile = binaryFile; + } + parser->state = CUE_PARSER_FILE_FILETYPE; + break; + case CUE_PARSER_FILE_FILETYPE: + parser->state = CUE_PARSER_START; + switch (keyword) { + case KW_EMPTY: + end_parse(parser, scheduler, "cuesheet FILE missing its filetype argument"); + return; + break; + case KW_BINARY: + parser->currentFileType = CUE_FILE_TYPE_BINARY; + parser->currentFile->size(parser->currentFile, scheduler, 0, size_cb); + return; + break; + case KW_MOTOROLA: + parser->currentFileType = CUE_FILE_TYPE_MOTOROLA; + end_parse(parser, scheduler, "cuesheet FILETYPE MOTOROLA not supported at the moment"); + return; + break; + case KW_AIFF: + parser->currentFileType = CUE_FILE_TYPE_AIFF; + end_parse(parser, scheduler, "cuesheet FILETYPE AIFF not supported at the moment"); + return; + break; + case KW_WAVE: + parser->currentFileType = CUE_FILE_TYPE_WAVE; + parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb); + return; + break; + case KW_MP3: + parser->currentFileType = CUE_FILE_TYPE_MP3; + parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb); + return; + break; + default: + end_parse(parser, scheduler, "cuesheet unknown FILE filetype"); + return; + } + break; + case CUE_PARSER_FLAGS: + switch (keyword) { + case KW_EMPTY: + assert(isEOL); + parser->state = CUE_PARSER_START; + break; + case KW_DCP: + case KW_4CH: + case KW_PRE: + case KW_SCMS: + end_parse(parser, scheduler, "cuesheet FLAGS not supported at the moment"); + return; + break; + default: + end_parse(parser, scheduler, "cuesheet FLAGS argument unknown"); + return; + } + break; + case CUE_PARSER_INDEX_NUMBER: { + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet INDEX missing index number argument"); + return; + } + char* endptr; + int indexNum = strtol(parser->word, &endptr, 10); + if (*endptr || (indexNum < 0) || (indexNum >= MAXINDEX)) { + end_parse(parser, scheduler, "cuesheet INDEX number invalid"); + return; + } + struct CueTrack* track = &parser->disc->tracks[parser->currentTrack]; + if ((track->indexCount == -1) & (indexNum == 1)) { + parser->implicitIndex = 1; + } else if (track->indexCount != (indexNum - 1)) { + end_parse(parser, scheduler, "cuesheet INDEX not consecutive"); + return; + } + track->indexCount = indexNum; + track->compressed = parser->currentFileType != CUE_FILE_TYPE_BINARY; + parser->state = CUE_PARSER_INDEX_TIMECODE; + } break; + case CUE_PARSER_INDEX_TIMECODE: { + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet INDEX missing timecode argument"); + return; + } + int32_t sectorNumber = timecodeToSectorNumber(parser->word); + if (sectorNumber < 0) { + end_parse(parser, scheduler, "cuesheet INDEX timecode invalid"); + return; + } + struct CueTrack* track = &parser->disc->tracks[parser->currentTrack]; + track->indices[track->indexCount] = parser->currentSectorNumber + sectorNumber; + if (parser->implicitIndex) { + if (parser->currentTrack == 1) { + track->indices[0] = 0; + } else { + track->indices[0] = parser->currentSectorNumber; + } + parser->implicitIndex = 0; + } + parser->state = CUE_PARSER_START; + } break; + case CUE_PARSER_ISRC: + if (len != 12) { + end_parse(parser, scheduler, "cuesheet ISRC doesn't have 12 characters"); + return; + } + memcpy(parser->disc->isrc, parser->word, 13); + parser->state = CUE_PARSER_START; + break; + case CUE_PARSER_PERFORMER: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet PERFORMER missing its argument"); + return; + } + parser->state = CUE_PARSER_START; + end_parse(parser, scheduler, "cuesheet PERFORMER argument not supported at the moment"); + return; + break; + case CUE_PARSER_POSTGAP: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet POSTGAP missing its argument"); + return; + } + parser->state = CUE_PARSER_START; + end_parse(parser, scheduler, "cuesheet POSTGAP argument not supported at the moment"); + return; + break; + case CUE_PARSER_PREGAP: { + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet PREGAP missing its argument"); + return; + } + if (parser->currentTrack == 0) { + end_parse(parser, scheduler, "cuesheet PREGAP before any TRACK"); + return; + } + struct CueTrack* track = &parser->disc->tracks[parser->currentTrack]; + if (track->indexCount != -1) { + end_parse(parser, scheduler, "cuesheet PREGAP after an INDEX"); + return; + } + if (parser->currentSectorNumber != track->fileOffset) { + end_parse(parser, scheduler, "cuesheet PREGAP not at the beginning of a FILE isn't supported"); + return; + } + int32_t pregapLength = timecodeToSectorNumber(parser->word); + if (pregapLength < 0) { + end_parse(parser, scheduler, "cuesheet PREGAP length invalid"); + return; + } + track->indexCount = 0; + track->indices[0] = parser->currentSectorNumber; + track->fileOffset += pregapLength; + parser->currentSectorNumber += pregapLength; + parser->state = CUE_PARSER_START; + } break; + case CUE_PARSER_SONGWRITER: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet SONGWRITER missing its argument"); + return; + } + parser->state = CUE_PARSER_START; + end_parse(parser, scheduler, "cuesheet SONGWRITER argument not supported at the moment"); + return; + break; + case CUE_PARSER_TITLE: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet TITLE missing its argument"); + return; + } + parser->state = CUE_PARSER_START; + end_parse(parser, scheduler, "cuesheet TITLE argument not supported at the moment"); + return; + break; + case CUE_PARSER_TRACK_NUMBER: + if (keyword == KW_EMPTY) { + end_parse(parser, scheduler, "cuesheet TRACK missing its track number argument"); + return; + } else { + parser->state = CUE_PARSER_TRACK_DATATYPE; + char* endptr; + int trackNum = strtol(parser->word, &endptr, 10); + if (*endptr || (trackNum < 1) || (trackNum >= MAXTRACK)) { + end_parse(parser, scheduler, "cuesheet TRACK number invalid"); + return; + } + if (parser->currentTrack != (trackNum - 1)) { + end_parse(parser, scheduler, "cuesheet TRACK not consecutive"); + return; + } + parser->currentTrack = trackNum; + struct CueTrack* track = &parser->disc->tracks[trackNum]; + if (track->file) { + end_parse(parser, scheduler, "cuesheet TRACK already exists"); + return; + } + if (!parser->currentFile) { + end_parse(parser, scheduler, "cuesheet TRACK without a FILE first"); + return; + } + track->file = parser->currentFile; + track->file->references++; + track->fileOffset = 0; + track->indexCount = -1; + track->postgap = 0; + track->size = 0; + track->trackType = TRACK_TYPE_UNKNOWN; + track->compressed = 0; + if (parser->isTrackANewFile) { + parser->currentSectorNumber += (parser->previousFileSize + 2351) / 2352; + parser->isTrackANewFile = 0; + track->fileOffset = parser->currentSectorNumber; + } else { + track->fileOffset = parser->disc->tracks[parser->currentTrack - 1].fileOffset; + } + parser->disc->trackCount = trackNum; + } + break; + case CUE_PARSER_TRACK_DATATYPE: + switch (keyword) { + case KW_AUDIO: + parser->disc->tracks[parser->currentTrack].trackType = TRACK_TYPE_AUDIO; + parser->state = CUE_PARSER_START; + break; + case KW_CDG: + end_parse(parser, scheduler, "cuesheet TRACK type CDG not supported at the moment"); + parser->state = CUE_PARSER_START; + return; + break; + case KW_CDI_2336: + end_parse(parser, scheduler, "cuesheet TRACK type CDI_2336 not supported at the moment"); + parser->state = CUE_PARSER_START; + return; + break; + case KW_CDI_2352: + end_parse(parser, scheduler, "cuesheet TRACK type CDI_2352 not supported at the moment"); + parser->state = CUE_PARSER_START; + return; + break; + case KW_MODE1_2048: + end_parse(parser, scheduler, "cuesheet TRACK type MODE1_2048 not supported at the moment"); + parser->state = CUE_PARSER_START; + return; + break; + case KW_MODE1_2352: + parser->disc->tracks[parser->currentTrack].trackType = TRACK_TYPE_DATA; + parser->state = CUE_PARSER_START; + break; + case KW_MODE2_2336: + end_parse(parser, scheduler, "cuesheet TRACK type MODE2_2336 not supported at the moment"); + parser->state = CUE_PARSER_START; + return; + break; + case KW_MODE2_2352: + parser->disc->tracks[parser->currentTrack].trackType = TRACK_TYPE_DATA; + parser->state = CUE_PARSER_START; + break; + default: + end_parse(parser, scheduler, "cuesheet TRACK has unknown or missing datatype"); + return; + } + break; + default: + end_parse(parser, scheduler, "cuesheet parser internal error"); + return; + } + } + parser->amount = 0; + schedule_read(parser, file, scheduler); +} + +static void parse_eof(struct CueParser* parser, struct CueScheduler* scheduler) { + if (parser->currentFile) { + if (parser->currentFile->references == 1) { + end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK"); + return; + } + parser->currentFile->references--; + } + parser->currentFile = NULL; + if (parser->disc->trackCount == 0) { + end_parse(parser, scheduler, "cuesheet has no track"); + return; + } + for (unsigned i = 1; i < parser->disc->trackCount; i++) { + struct CueTrack* track = &parser->disc->tracks[i]; + if (track->indexCount < 1) { + end_parse(parser, scheduler, "cuesheet TRACK doesn't have enough indices"); + return; + } + } + for (unsigned i = 2; i <= parser->disc->trackCount; i++) { + struct CueTrack* prevTrack = &parser->disc->tracks[i - 1]; + struct CueTrack* track = &parser->disc->tracks[i]; + prevTrack->size = track->indices[0] - prevTrack->indices[0]; + } + parser->currentSectorNumber += (parser->currentFileSize + 2351) / 2352; + struct CueTrack* track = &parser->disc->tracks[parser->disc->trackCount]; + track->size = parser->currentSectorNumber - track->indices[0]; + end_parse(parser, scheduler, NULL); +} + +static void read_bytes(struct CueFile* file, struct CueScheduler* scheduler, int error, uint32_t amount, + uint8_t* buffer) { + struct CueParser* parser = file->user; + if (amount == 0) { + parse_eof(parser, scheduler); + } else { + parser->amount = amount; + parser->cursor += amount; + parse(parser, file, scheduler); + } +} +static void size_cb(struct CueFile* binaryFile, struct CueScheduler* scheduler, uint64_t size) { + struct CueFile* file = binaryFile->user; + struct CueParser* parser = file->user; + parser->previousFileSize = parser->currentFileSize; + parser->currentFileSize = size; + parse(parser, file, scheduler); +} + +static void schedule_read(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler) { + assert(parser->amount == 0); + parser->start = parser->buffer; + file->read(file, scheduler, sizeof(parser->buffer), parser->cursor, (uint8_t*)parser->buffer, read_bytes); +} + +void CueParser_parse(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler, + struct CueFile* (*fileopen)(struct CueFile*, struct CueScheduler*, const char*), + void (*cb)(struct CueParser*, struct CueScheduler*, const char*)) { + file->user = parser; + parser->cb = cb; + parser->open = fileopen; + schedule_read(parser, file, scheduler); +} diff --git a/third_party/cueparser/cueparser.h b/third_party/cueparser/cueparser.h new file mode 100644 index 000000000..1928afb86 --- /dev/null +++ b/third_party/cueparser/cueparser.h @@ -0,0 +1,102 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct CueDisc; +struct CueFile; +struct CueScheduler; + +enum CueParserState { + CUE_PARSER_START, + CUE_PARSER_CATALOG, + CUE_PARSER_CDTEXTFILE, + CUE_PARSER_FILE_FILENAME, + CUE_PARSER_FILE_FILETYPE, + CUE_PARSER_FLAGS, + CUE_PARSER_INDEX_NUMBER, + CUE_PARSER_INDEX_TIMECODE, + CUE_PARSER_ISRC, + CUE_PARSER_PERFORMER, + CUE_PARSER_POSTGAP, + CUE_PARSER_PREGAP, + CUE_PARSER_SONGWRITER, + CUE_PARSER_TITLE, + CUE_PARSER_TRACK_NUMBER, + CUE_PARSER_TRACK_DATATYPE, +}; + +enum CueFileType { + CUE_FILE_TYPE_BINARY, + CUE_FILE_TYPE_MOTOROLA, + CUE_FILE_TYPE_AIFF, + CUE_FILE_TYPE_WAVE, + CUE_FILE_TYPE_MP3, +}; + +struct CueParser { + struct CueDisc* disc; + int gotSpace; + int inQuotes; + int afterQuotes; + int inRem; + void (*cb)(struct CueParser*, struct CueScheduler*, const char* error); + char buffer[256]; // adjustable without issue + char* start; + unsigned amount; + char word[256]; // non-adjustable + uint32_t keyword; + enum CueParserState state; + uint64_t cursor; + void* user; + struct CueFile* (*open)(struct CueFile*, struct CueScheduler*, const char*); + struct CueFile* currentFile; + enum CueFileType currentFileType; + uint64_t previousFileSize; + uint64_t currentFileSize; + unsigned currentTrack; + uint32_t currentSectorNumber; + int implicitIndex; + int isTrackANewFile; +}; + +void CueParser_construct(struct CueParser*, struct CueDisc*); +void CueParser_close(struct CueParser*, struct CueScheduler*, + void (*)(struct CueParser*, struct CueScheduler*, const char* error)); +void CueParser_destroy(struct CueParser*); +void CueParser_parse(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler, + struct CueFile* (*fileopen)(struct CueFile*, struct CueScheduler*, const char* filename), + void (*cb)(struct CueParser*, struct CueScheduler*, const char* error)); + +#ifdef __cplusplus +} +#endif diff --git a/third_party/cueparser/disc.h b/third_party/cueparser/disc.h new file mode 100644 index 000000000..4b3ccc0a5 --- /dev/null +++ b/third_party/cueparser/disc.h @@ -0,0 +1,71 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct CueFile; + +#ifndef MAXTRACK +#define MAXTRACK 100 +#endif + +#ifndef MAXINDEX +#define MAXINDEX 100 +#endif + +enum CueTrackType { TRACK_TYPE_UNKNOWN, TRACK_TYPE_AUDIO, TRACK_TYPE_DATA }; + +struct CueTrack { + struct CueFile* file; + uint32_t size; // size of the track in sectors, including pregaps and postgaps + uint32_t fileOffset; // offset in sectors within the disc at which this file begins + int indexCount; + uint32_t indices[MAXINDEX]; // each index is an absolute value in sectors from the beginning + // of the disc; adjust using fileOffset + // index 0 is for the pregap, and shouldn't be considered + // physically present within the data files + // the lead-in isn't taken into account + uint32_t postgap; // size of the postgap in sectors + enum CueTrackType trackType; + int compressed; +}; + +struct CueDisc { + int trackCount; + struct CueTrack tracks[MAXTRACK]; // track 0 isn't valid; technically can be considered the lead-in + char catalog[14]; + char isrc[13]; +}; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/third_party/cueparser/fileabstract.c b/third_party/cueparser/fileabstract.c new file mode 100644 index 000000000..c159740d3 --- /dev/null +++ b/third_party/cueparser/fileabstract.c @@ -0,0 +1,143 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "cueparser/fileabstract.h" + +#include +#include + +#include "cueparser/scheduler.h" + +static void closure_generic_free(struct CueClosure *closure) { free(closure); } + +struct close_Closure { + struct CueScheduler *scheduler; + void (*destroy)(struct CueClosure *); + void (*call)(struct CueClosure *); + struct CueClosure *next; + struct CueFile *file; + void (*cb)(struct CueFile *, struct CueScheduler *); +}; + +static void close_closure_call(struct CueClosure *closure_) { + struct close_Closure *closure = (struct close_Closure *)closure_; + closure->cb(closure->file, closure->scheduler); +} + +void File_schedule_close(struct CueFile *file, struct CueScheduler *scheduler, + void (*cb)(struct CueFile *, struct CueScheduler *)) { + struct close_Closure *closure = malloc(sizeof(struct close_Closure)); + closure->destroy = closure_generic_free; + closure->call = close_closure_call; + closure->file = file; + closure->cb = cb; + Scheduler_schedule(scheduler, (struct CueClosure *)closure); +} + +struct size_Closure { + struct CueScheduler *scheduler; + void (*destroy)(struct CueClosure *); + void (*call)(struct CueClosure *); + struct CueClosure *next; + struct CueFile *file; + uint64_t size; + void (*cb)(struct CueFile *, struct CueScheduler *, uint64_t); +}; + +static void size_closure_call(struct CueClosure *closure_) { + struct size_Closure *closure = (struct size_Closure *)closure_; + closure->cb(closure->file, closure->scheduler, closure->size); +} + +void File_schedule_size(struct CueFile *file, struct CueScheduler *scheduler, uint64_t size, + void (*cb)(struct CueFile *, struct CueScheduler *, uint64_t)) { + struct size_Closure *closure = malloc(sizeof(struct size_Closure)); + closure->destroy = closure_generic_free; + closure->call = size_closure_call; + closure->file = file; + closure->size = size; + closure->cb = cb; + Scheduler_schedule(scheduler, (struct CueClosure *)closure); +} + +struct read_Closure { + struct CueScheduler *scheduler; + void (*destroy)(struct CueClosure *); + void (*call)(struct CueClosure *); + struct CueClosure *next; + struct CueFile *file; + int error; + uint32_t amount; + uint8_t *buffer; + void (*cb)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount, uint8_t *buffer); +}; +static void read_closure_call(struct CueClosure *closure_) { + struct read_Closure *closure = (struct read_Closure *)closure_; + closure->cb(closure->file, closure->scheduler, closure->error, closure->amount, closure->buffer); +} + +void File_schedule_read(struct CueFile *file, struct CueScheduler *scheduler, int error, uint32_t amount, + uint8_t *buffer, + void (*cb)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount, + uint8_t *buffer)) { + struct read_Closure *closure = malloc(sizeof(struct read_Closure)); + closure->destroy = closure_generic_free; + closure->call = read_closure_call; + closure->file = file; + closure->error = error; + closure->amount = amount; + closure->buffer = buffer; + closure->cb = cb; + Scheduler_schedule(scheduler, (struct CueClosure *)closure); +} + +struct write_Closure { + struct CueScheduler *scheduler; + void (*destroy)(struct CueClosure *); + void (*call)(struct CueClosure *); + struct CueClosure *next; + struct CueFile *file; + int error; + uint32_t amount; + void (*cb)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount); +}; + +static void write_closure_call(struct CueClosure *closure_) { + struct write_Closure *closure = (struct write_Closure *)closure_; + closure->cb(closure->file, closure->scheduler, closure->error, closure->amount); +} + +void File_schedule_write(struct CueFile *file, struct CueScheduler *scheduler, int error, uint32_t amount, + void (*cb)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount)) { + struct write_Closure *closure = malloc(sizeof(struct write_Closure)); + closure->destroy = closure_generic_free; + closure->call = write_closure_call; + closure->file = file; + closure->error = error; + closure->amount = amount; + closure->cb = cb; + Scheduler_schedule(scheduler, (struct CueClosure *)closure); +} diff --git a/third_party/cueparser/fileabstract.h b/third_party/cueparser/fileabstract.h new file mode 100644 index 000000000..312c29731 --- /dev/null +++ b/third_party/cueparser/fileabstract.h @@ -0,0 +1,63 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct CueScheduler; + +struct CueFile { + void (*destroy)(struct CueFile *); + void (*close)(struct CueFile *, struct CueScheduler *, void (*)(struct CueFile *, struct CueScheduler *)); + void (*size)(struct CueFile *, struct CueScheduler *, int compressed, + void (*)(struct CueFile *, struct CueScheduler *, uint64_t)); + void (*read)(struct CueFile *, struct CueScheduler *, uint32_t amount, uint64_t cursor, uint8_t *buffer, + void (*)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount, uint8_t *buffer)); + void (*write)(struct CueFile *, struct CueScheduler *, uint32_t amount, uint64_t cursor, const uint8_t *buffer, + void (*)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount)); + int references; + const char *cfilename; + char *filename; + void *user; + void *opaque; +}; + +void File_schedule_close(struct CueFile *, struct CueScheduler *, void (*)(struct CueFile *, struct CueScheduler *)); +void File_schedule_size(struct CueFile *, struct CueScheduler *, uint64_t size, + void (*)(struct CueFile *, struct CueScheduler *, uint64_t)); +void File_schedule_read(struct CueFile *, struct CueScheduler *, int error, uint32_t amount, uint8_t *buffer, + void (*)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount, uint8_t *buffer)); +void File_schedule_write(struct CueFile *, struct CueScheduler *, int error, uint32_t amount, + void (*)(struct CueFile *, struct CueScheduler *, int error, uint32_t amount)); + +#ifdef __cplusplus +} +#endif diff --git a/third_party/cueparser/scheduler.c b/third_party/cueparser/scheduler.c new file mode 100644 index 000000000..ade8828ef --- /dev/null +++ b/third_party/cueparser/scheduler.c @@ -0,0 +1,67 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "cueparser/scheduler.h" + +#include + +#ifndef CUSTOM_SCHEDULER +int Scheduler_hasPendingEvents(struct CueScheduler *scheduler) { return 0; } +void Scheduler_processEvents(struct CueScheduler *scheduler) {} +#endif + +void Scheduler_construct(struct CueScheduler *scheduler) { scheduler->top = NULL; } + +void Scheduler_schedule(struct CueScheduler *scheduler, struct CueClosure *closure) { + closure->scheduler = scheduler; + closure->next = scheduler->top; + scheduler->top = closure; +} + +void Scheduler_run(struct CueScheduler *scheduler) { + while (scheduler->top || Scheduler_hasPendingEvents(scheduler)) { + Scheduler_run_once(scheduler); + Scheduler_processEvents(scheduler); + } +} + +void Scheduler_run_once(struct CueScheduler *scheduler) { + struct CueClosure *closure = scheduler->top; + scheduler->top = NULL; + while (closure) { + struct CueClosure *next = closure->next; + closure->call(closure); + closure->destroy(closure); + closure = next; + } +} + +void Scheduler_run_one(struct CueScheduler *scheduler) { + struct CueClosure *closure = scheduler->top; + scheduler->top = closure->next; + closure->call(closure); + closure->destroy(closure); +} diff --git a/third_party/cueparser/scheduler.h b/third_party/cueparser/scheduler.h new file mode 100644 index 000000000..10e8e4e0b --- /dev/null +++ b/third_party/cueparser/scheduler.h @@ -0,0 +1,57 @@ +/* + +MIT License + +Copyright (c) 2022 Nicolas "Pixel" Noble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct CueScheduler; + +struct CueClosure { + struct CueScheduler *scheduler; + void (*destroy)(struct CueClosure *); + void (*call)(struct CueClosure *); + struct CueClosure *next; +}; + +struct CueScheduler { + struct CueClosure *top; + void *opaque; +}; + +void Scheduler_construct(struct CueScheduler *); +int Scheduler_hasPendingEvents(struct CueScheduler *); +void Scheduler_processEvents(struct CueScheduler *); +void Scheduler_schedule(struct CueScheduler *, struct CueClosure *); +void Scheduler_run(struct CueScheduler *); +void Scheduler_run_once(struct CueScheduler *); +void Scheduler_run_one(struct CueScheduler *); + +#ifdef __cplusplus +} +#endif diff --git a/tools/build/Dockerfile b/tools/build/Dockerfile index a6e338767..70fd2fc05 100644 --- a/tools/build/Dockerfile +++ b/tools/build/Dockerfile @@ -1,6 +1,6 @@ # Dockerfile for grumpycoders/pcsx-redux-build -FROM ubuntu:22.04 +FROM ubuntu:22.10 # The tzdata package isn't docker-friendly, and something pulls it. ENV DEBIAN_FRONTEND noninteractive @@ -55,7 +55,7 @@ RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | b && nvm alias default $NODE_VERSION \ && nvm use default RUN . $NVM_DIR/nvm.sh && npm install -g appcenter-cli -RUN apt install -y zip +RUN apt install -y squashfs-tools zip zsync RUN mkdir /project RUN mkdir -p /home/coder/dconf diff --git a/vsprojects/cdrom/cdrom.vcxproj b/vsprojects/cdrom/cdrom.vcxproj index f9a71c3b0..f52229eb4 100644 --- a/vsprojects/cdrom/cdrom.vcxproj +++ b/vsprojects/cdrom/cdrom.vcxproj @@ -285,7 +285,6 @@ - @@ -295,6 +294,9 @@ + + + @@ -304,20 +306,24 @@ + + + + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/vsprojects/cdrom/cdrom.vcxproj.filters b/vsprojects/cdrom/cdrom.vcxproj.filters index b31226570..a9bef51ed 100644 --- a/vsprojects/cdrom/cdrom.vcxproj.filters +++ b/vsprojects/cdrom/cdrom.vcxproj.filters @@ -13,6 +13,12 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {2b0f228a-f193-4dd9-9989-28718172d09c} + + + {b3b9e7f8-7847-454a-b04b-23e2cffc389c} + @@ -30,9 +36,6 @@ Source Files - - Source Files - Source Files @@ -57,6 +60,15 @@ Source Files + + Source Files\cueparser + + + Source Files\cueparser + + + Source Files\cueparser + @@ -80,6 +92,18 @@ Header Files + + Header Files\cueparser + + + Header Files\cueparser + + + Header Files\cueparser + + + Header Files\cueparser + diff --git a/vsprojects/cdrom/packages.config b/vsprojects/cdrom/packages.config index 939c9587f..0ec6ac21c 100644 --- a/vsprojects/cdrom/packages.config +++ b/vsprojects/cdrom/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/vsprojects/gui/gui.vcxproj b/vsprojects/gui/gui.vcxproj index 17a0f1fe6..ef932f6d8 100644 --- a/vsprojects/gui/gui.vcxproj +++ b/vsprojects/gui/gui.vcxproj @@ -248,6 +248,7 @@ + @@ -275,6 +276,7 @@ + diff --git a/vsprojects/gui/gui.vcxproj.filters b/vsprojects/gui/gui.vcxproj.filters index c9d60308f..bc2f4b829 100644 --- a/vsprojects/gui/gui.vcxproj.filters +++ b/vsprojects/gui/gui.vcxproj.filters @@ -80,6 +80,9 @@ Source Files\widgets + + Source Files\widgets + @@ -155,6 +158,9 @@ Header Files\widgets + + Header Files\widgets + diff --git a/vsprojects/mainthunk/mainthunk.vcxproj b/vsprojects/mainthunk/mainthunk.vcxproj index 555bf0c76..a09182944 100644 --- a/vsprojects/mainthunk/mainthunk.vcxproj +++ b/vsprojects/mainthunk/mainthunk.vcxproj @@ -208,6 +208,7 @@ true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) Windows + false PerMonitorHighDPIAware @@ -228,6 +229,7 @@ true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) Windows + false PerMonitorHighDPIAware @@ -248,6 +250,7 @@ true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) Windows + false PerMonitorHighDPIAware @@ -268,6 +271,7 @@ true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) Console + false PerMonitorHighDPIAware @@ -288,6 +292,7 @@ true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) Windows + false PerMonitorHighDPIAware @@ -487,16 +492,16 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/vsprojects/mainthunk/packages.config b/vsprojects/mainthunk/packages.config index 2870a5c43..9bfbba3ee 100644 --- a/vsprojects/mainthunk/packages.config +++ b/vsprojects/mainthunk/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/vsprojects/support/packages.config b/vsprojects/support/packages.config index 4fa4eac8f..0ec6ac21c 100644 --- a/vsprojects/support/packages.config +++ b/vsprojects/support/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/vsprojects/support/support.vcxproj b/vsprojects/support/support.vcxproj index 61772e3bd..7e5991617 100644 --- a/vsprojects/support/support.vcxproj +++ b/vsprojects/support/support.vcxproj @@ -286,8 +286,10 @@ + + @@ -314,6 +316,7 @@ + @@ -331,11 +334,13 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + \ No newline at end of file diff --git a/vsprojects/support/support.vcxproj.filters b/vsprojects/support/support.vcxproj.filters index 9411058c8..4e1724766 100644 --- a/vsprojects/support/support.vcxproj.filters +++ b/vsprojects/support/support.vcxproj.filters @@ -102,9 +102,15 @@ Header Files + + Header Files + Header Files + + Header Files + @@ -140,6 +146,9 @@ Source Files + + Source Files + diff --git a/vsprojects/tests/pcsxrunner/packages.config b/vsprojects/tests/pcsxrunner/packages.config index 2870a5c43..9bfbba3ee 100644 --- a/vsprojects/tests/pcsxrunner/packages.config +++ b/vsprojects/tests/pcsxrunner/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj b/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj index 619c16da3..81c72f8c7 100644 --- a/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj +++ b/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj @@ -169,6 +169,7 @@ Console true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) + false @@ -186,6 +187,7 @@ true true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) + false @@ -203,6 +205,7 @@ true true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) + false @@ -220,6 +223,7 @@ true true imm32.lib;iphlpapi.lib;kernel32.lib;opengl32.lib;psapi.lib;setupapi.lib;shlwapi.lib;userenv.lib;version.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) + false @@ -385,16 +389,16 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file