From 9e22e50b5fca34e99ecd5c7f2b813c5c6630e671 Mon Sep 17 00:00:00 2001 From: Robert Broglia Date: Sun, 5 Nov 2023 03:15:24 -0500 Subject: [PATCH] Save state handling updates and optimizations * Imagine: Add DynArray class * Imagine: Add Gzip utility functions * Imagine: Allow rewinding ArchiveIterator after incrementing to the end * Imagine: Add IOBuffer constructor to IO and span constructor to MapIO * Imagine: Add PosixIO and MapIO constructors to PosixFileIO * Imagine: Rename IOUtils.hh to IOUtils-impl.hh and make it an external header * EmuFramework: Add new readState()/writeState() API for working with states in memory buffers, needed to support future features like rewinding * EmuFramework: Cache FileIO for autosave states to reduce overhead when writing data mid-emulation * EmuFramework: Add OutSizeTracker class * EmuFramework: Only consider autosave state older than backup memory if the timestamps differ by more than 1 second * C64.emu: Cache the system files archive iterator to speed up successive calls to sysfile_locate() and sysfile_load() * NEO.emu: Fix crash when opening content list menu and the content folder doesn't exist * Convert all systems to new save state API --- 2600.emu/src/main/Main.cc | 29 +- 2600.emu/src/main/MainSystem.hh | 6 +- C64.emu/src/main/EmuMenuViews.cc | 21 +- C64.emu/src/main/Main.cc | 74 +- C64.emu/src/main/MainSystem.hh | 10 +- C64.emu/src/main/VicePlugin.cc | 6 +- C64.emu/src/main/VicePlugin.hh | 6 +- C64.emu/src/main/sysfile.cc | 65 +- C64.emu/src/main/zfile.cc | 42 +- .../include/emuframework/AutosaveManager.hh | 13 +- .../include/emuframework/EmuSystem.hh | 15 +- .../include/emuframework/EmuSystemInlines.hh | 17 +- .../include/emuframework/OutSizeTracker.hh | 69 ++ EmuFramework/src/AutosaveManager.cc | 72 +- EmuFramework/src/EmuApp.cc | 2 +- EmuFramework/src/EmuSystem.cc | 32 + .../src/shared/mednafen-emuex/MDFNUtils.hh | 53 ++ .../src/shared/mednafen-emuex/StreamImpl.cc | 3 + EmuFramework/src/shared/mednafen/FileStream.h | 2 + GBA.emu/src/main/Main.cc | 30 +- GBA.emu/src/main/MainSystem.hh | 7 +- GBA.emu/src/main/VbamApi.cc | 57 ++ GBA.emu/src/vbam/Util.h | 8 +- GBA.emu/src/vbam/gba/Cheats.h | 2 + GBA.emu/src/vbam/gba/EEprom.cpp | 15 +- GBA.emu/src/vbam/gba/EEprom.h | 6 +- GBA.emu/src/vbam/gba/Flash.cpp | 16 +- GBA.emu/src/vbam/gba/Flash.h | 6 +- GBA.emu/src/vbam/gba/GBA.cpp | 41 +- GBA.emu/src/vbam/gba/GBA.h | 10 +- GBA.emu/src/vbam/gba/RTC.cpp | 5 +- GBA.emu/src/vbam/gba/RTC.h | 6 +- GBA.emu/src/vbam/gba/Sound.cpp | 14 +- GBA.emu/src/vbam/gba/Sound.h | 8 +- GBC.emu/src/main/Main.cc | 21 +- GBC.emu/src/main/MainSystem.hh | 7 +- Lynx.emu/src/main/Main.cc | 14 +- Lynx.emu/src/main/MainSystem.hh | 5 +- MD.emu/src/main/Main.cc | 17 +- MD.emu/src/main/MainSystem.hh | 7 +- MSX.emu/src/main/Main.cc | 18 +- MSX.emu/src/main/MainSystem.hh | 7 +- MSX.emu/src/main/ziphelper.cc | 71 +- NEO.emu/build.mk | 2 +- NEO.emu/src/gngeo/cyclone_interf.c | 2 +- NEO.emu/src/gngeo/mamez80_interf.c | 2 +- NEO.emu/src/gngeo/musashi_interf.cc | 2 +- NEO.emu/src/gngeo/pd4990a.c | 2 +- NEO.emu/src/gngeo/state.c | 687 ------------------ NEO.emu/src/gngeo/state.h | 3 +- NEO.emu/src/gngeo/timer.c | 2 +- NEO.emu/src/gngeo/ym2610/ym2610.c | 2 +- NEO.emu/src/main/EmuMenuViews.cc | 27 +- NEO.emu/src/main/Main.cc | 82 ++- NEO.emu/src/main/MainSystem.hh | 9 +- NEO.emu/src/main/state.cc | 134 ++++ NES.emu/src/main/EmuFileIO.cc | 17 +- NES.emu/src/main/EmuFileIO.hh | 13 +- NES.emu/src/main/Main.cc | 19 +- NES.emu/src/main/MainSystem.hh | 6 +- NGP.emu/src/main/Main.cc | 14 +- NGP.emu/src/main/MainSystem.hh | 5 +- PCE.emu/src/main/Main.cc | 14 +- PCE.emu/src/main/MainSystem.hh | 5 +- Saturn.emu/src/main/Main.cc | 20 +- Saturn.emu/src/main/MainSystem.hh | 5 +- Snes9x/1.43/build.mk | 1 - Snes9x/src/main/Main.cc | 61 +- Snes9x/src/main/MainSystem.hh | 6 +- Snes9x/src/main/S9XApi.cc | 1 + Swan.emu/src/main/Main.cc | 14 +- Swan.emu/src/main/MainSystem.hh | 5 +- imagine/include/imagine/fs/ArchiveFS.hh | 12 +- imagine/include/imagine/gui/NavView.hh | 4 +- imagine/include/imagine/io/ArchiveIO.hh | 1 + imagine/include/imagine/io/IO.hh | 2 + .../imagine/io/IOUtils-impl.hh} | 16 +- imagine/include/imagine/io/MapIO.hh | 1 + imagine/include/imagine/io/PosixFileIO.hh | 2 + imagine/include/imagine/util/bit.hh | 7 + .../include/imagine/util/memory/DynArray.hh | 60 ++ imagine/include/imagine/util/span.hh | 35 + imagine/include/imagine/util/zlib.hh | 58 ++ imagine/src/fs/ArchiveFS.cc | 10 +- imagine/src/gui/NavView.cc | 9 +- imagine/src/io/AAssetIO.cc | 2 +- imagine/src/io/ArchiveIO.cc | 9 +- imagine/src/io/IO.cc | 2 +- imagine/src/io/MapIO.cc | 2 +- imagine/src/io/PosixFileIO.cc | 7 +- imagine/src/io/PosixIO.cc | 2 +- imagine/src/io/utils.hh | 11 - 92 files changed, 1278 insertions(+), 1071 deletions(-) create mode 100644 EmuFramework/include/emuframework/OutSizeTracker.hh delete mode 100644 NEO.emu/src/gngeo/state.c create mode 100644 NEO.emu/src/main/state.cc rename imagine/{src/io/IOUtils.hh => include/imagine/io/IOUtils-impl.hh} (89%) create mode 100644 imagine/include/imagine/util/memory/DynArray.hh create mode 100644 imagine/include/imagine/util/span.hh create mode 100644 imagine/include/imagine/util/zlib.hh diff --git a/2600.emu/src/main/Main.cc b/2600.emu/src/main/Main.cc index 8a43fd713..187b82d4e 100755 --- a/2600.emu/src/main/Main.cc +++ b/2600.emu/src/main/Main.cc @@ -136,6 +136,9 @@ void A2600System::loadContent(IO &io, EmuSystemCreateParams, OnLoadProgressDeleg console.initializeVideo(); console.initializeAudio(); logMsg("is PAL: %s", videoSystem() == VideoSystem::PAL ? "yes" : "no"); + Serializer state; + osystem.state().saveState(state); + saveStateSize = state.size(); } static auto consoleFrameRate(const OSystem &osystem) @@ -222,23 +225,23 @@ void A2600System::reset(EmuApp &, ResetMode mode) } } -void A2600System::saveState(IG::CStringView path) +void A2600System::readState(EmuApp &app, std::span buff) { - Serializer state{path.data()}; - if(!osystem.state().saveState(state)) - { - throwFileWriteError(); - } + Serializer state; + state.putByteArray(buff.data(), buff.size()); + if(!osystem.state().loadState(state)) + throw std::runtime_error("Invalid state data"); + updateSwitchValues(); } -void A2600System::loadState(EmuApp &, IG::CStringView path) +size_t A2600System::writeState(std::span buff, SaveStateFlags flags) { - Serializer state{path.data(), Serializer::Mode::ReadOnly}; - if(!osystem.state().loadState(state)) - { - throwFileReadError(); - } - updateSwitchValues(); + Serializer state; + osystem.state().saveState(state); + assert(state.size() == saveStateSize); + assert(state.size() <= buff.size()); + state.getByteArray(buff.data(), buff.size()); + return saveStateSize; } void EmuApp::onCustomizeNavView(EmuApp::NavView &view) diff --git a/2600.emu/src/main/MainSystem.hh b/2600.emu/src/main/MainSystem.hh index 69ef50519..6ab20ac4e 100644 --- a/2600.emu/src/main/MainSystem.hh +++ b/2600.emu/src/main/MainSystem.hh @@ -66,6 +66,7 @@ class A2600System final: public EmuSystem { public: OSystem osystem; + size_t saveStateSize{}; float configuredInputVideoFrameRate{}; Properties defaultGameProps{}; bool p1DiffB = true, p2DiffB = true, vcsColor = true; @@ -101,8 +102,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".sta"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize() { return saveStateSize; } + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &io, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/C64.emu/src/main/EmuMenuViews.cc b/C64.emu/src/main/EmuMenuViews.cc index 33f66d56b..6aee6e557 100644 --- a/C64.emu/src/main/EmuMenuViews.cc +++ b/C64.emu/src/main/EmuMenuViews.cc @@ -324,24 +324,21 @@ class CustomFilePathOptionView : public FilePathOptionView, public MainAppHelper app().validSearchPath(system().sysFilePath[0]), [this](CStringView path, FS::file_type type) { - if(type == FS::file_type::directory && !appContext().fileUriExists(FS::uriString(path, "DRIVES"))) + const auto &sysFilePath = system().sysFilePath; + if(type == FS::file_type::none && sysFilePath.size() > 1) { - app().postErrorMessage("Path is missing DRIVES folder"); - return false; + system().setSystemFilesPath(appContext(), path, type); + app().postMessage(5, false, std::format("Using fallback paths:\n{}\n{}", sysFilePath[1], sysFilePath[2])); } - logMsg("set firmware path:%s", path.data()); - systemFilePath.compile(sysPathMenuEntryStr(path), renderer()); - auto &sysFilePath = system().sysFilePath; - sysFilePath[0] = path; - if(type == FS::file_type::none) + else { - if constexpr(Config::envIsLinux) - app().postMessage(5, false, std::format("Using fallback paths:\n{}\n{}", sysFilePath[3], sysFilePath[4])); - else + if(!system().setSystemFilesPath(appContext(), path, type)) { - app().postMessage(5, false, std::format("Using fallback paths:\n{}\n{}", sysFilePath[1], sysFilePath[2])); + app().postErrorMessage("Path is missing DRIVES folder"); + return false; } } + systemFilePath.compile(sysPathMenuEntryStr(path), renderer()); return true; }); view->appendItem(downloadSystemFiles); diff --git a/C64.emu/src/main/Main.cc b/C64.emu/src/main/Main.cc index c33fa75e5..9410eb086 100755 --- a/C64.emu/src/main/Main.cc +++ b/C64.emu/src/main/Main.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include extern "C" @@ -65,6 +66,7 @@ extern "C" namespace EmuEx { +constexpr SystemLogger log{"main"}; const char *EmuSystem::creditsViewStr = CREDITS_INFO_STRING "(c) 2013-2023\nRobert Broglia\nwww.explusalpha.com\n\nPortions (c) the\nVice Team\nvice-emu.sourceforge.io"; bool EmuSystem::hasPALVideoSystem = true; bool EmuSystem::hasResetModes = true; @@ -243,54 +245,86 @@ FS::FileString C64System::stateFilename(int slot, std::string_view name) const struct SnapshotTrapData { - VicePlugin &plugin; - const char *pathStr{}; - bool hasError = true; + const VicePlugin &plugin; + uint8_t *buffData{}; + size_t buffSize{}; + bool hasError{true}; }; +static std::array snapshotVPath(SnapshotTrapData &data) +{ + std::array fileStr; + // Encode the data/size pointers after the string null terminator + if(data.buffData) + { + auto it = std::ranges::copy(":::B", std::begin(fileStr)).out; + it = std::ranges::copy(addressAsBytes(data.buffSize), it).out; + std::ranges::copy(asBytes(data.buffData), it); + //log.info("encoded size ptr:{} data ptr:{}", (void*)&data.buffSize, (void*)data.buffData); + } + else + { + auto it = std::ranges::copy(":::N", std::begin(fileStr)).out; + std::ranges::copy(addressAsBytes(data.buffSize), it); + //log.info("encoded size ptr:{}", (void*)&data.buffSize); + } + return fileStr; +} + static void loadSnapshotTrap(uint16_t, void *data) { - auto snapData = (SnapshotTrapData*)data; - logMsg("loading state: %s", snapData->pathStr); - if(snapData->plugin.machine_read_snapshot(snapData->pathStr, 0) < 0) - snapData->hasError = true; + auto &snapData = *((SnapshotTrapData*)data); + assumeExpr(snapData.buffData); + log.info("loading state at:{} size:{}", (void*)snapData.buffData, snapData.buffSize); + if(snapData.plugin.machine_read_snapshot(snapshotVPath(snapData).data(), 0) < 0) + snapData.hasError = true; else - snapData->hasError = false; + snapData.hasError = false; } static void saveSnapshotTrap(uint16_t, void *data) { - auto snapData = (SnapshotTrapData*)data; - logMsg("saving state: %s", snapData->pathStr); - if(snapData->plugin.machine_write_snapshot(snapData->pathStr, 1, 1, 0) < 0) - snapData->hasError = true; + auto &snapData = *((SnapshotTrapData*)data); + log.info("saving state at:{} size:{}", (void*)snapData.buffData, snapData.buffSize); + if(snapData.plugin.machine_write_snapshot(snapshotVPath(snapData).data(), 1, 1, 0) < 0) + snapData.hasError = true; else - snapData->hasError = false; + snapData.hasError = false; } -void C64System::saveState(IG::CStringView path) +size_t C64System::stateSize() { - SnapshotTrapData data{.plugin{plugin}, .pathStr{path}}; + SnapshotTrapData data{.plugin{plugin}}; plugin.interrupt_maincpu_trigger_trap(saveSnapshotTrap, (void*)&data); execC64Frame(); // execute cpu trap if(data.hasError) - throwFileWriteError(); + return 0; + return data.buffSize; } -void C64System::loadState(EmuApp &, IG::CStringView path) +void C64System::readState(EmuApp &app, std::span buff) { plugin.vsync_set_warp_mode(0); - SnapshotTrapData data{.plugin{plugin}, .pathStr{path}}; + SnapshotTrapData data{.plugin{plugin}, .buffData = buff.data(), .buffSize = buff.size()}; execC64Frame(); // run extra frame in case C64 was just started plugin.interrupt_maincpu_trigger_trap(loadSnapshotTrap, (void*)&data); execC64Frame(); // execute cpu trap, snapshot load may cause reboot from a C64 model change if(data.hasError) - return throwFileReadError(); + throw std::runtime_error("Invalid state data"); // reload snapshot in case last load caused a reboot plugin.interrupt_maincpu_trigger_trap(loadSnapshotTrap, (void*)&data); execC64Frame(); // execute cpu trap if(data.hasError) - return throwFileReadError(); + throw std::runtime_error("Invalid state data"); +} + +size_t C64System::writeState(std::span buff, SaveStateFlags flags) +{ + SnapshotTrapData data{.plugin{plugin}, .buffData = buff.data(), .buffSize = buff.size()}; + plugin.interrupt_maincpu_trigger_trap(saveSnapshotTrap, (void*)&data); + execC64Frame(); // execute cpu trap + assert(!data.hasError); + return data.buffSize; } VideoSystem C64System::videoSystem() const diff --git a/C64.emu/src/main/MainSystem.hh b/C64.emu/src/main/MainSystem.hh index 14cb39947..67ed6d66e 100644 --- a/C64.emu/src/main/MainSystem.hh +++ b/C64.emu/src/main/MainSystem.hh @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,7 @@ public: struct video_canvas_s *activeCanvas{}; const char *sysFileDir{}; VicePlugin plugin{}; + mutable FS::ArchiveIterator viceSysFilesArchiveIt; std::string defaultPaletteName{}; std::string lastMissingSysFile; IG::PixmapView canvasSrcPix{}; @@ -182,14 +184,18 @@ public: bool currSystemIsC64Or128() const; void setRuntimeReuSize(int size); void resetCanvasSourcePixmap(struct video_canvas_s *c); + FS::ArchiveIterator &systemFilesArchiveIterator(ApplicationContext, std::string_view path) const; + void returnSystemFilesArchiveIO(ArchiveIO); + bool setSystemFilesPath(ApplicationContext, CStringView path, FS::file_type); // required API functions void loadContent(IO &, EmuSystemCreateParams, OnLoadProgressDelegate); [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".vsf"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &io, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/C64.emu/src/main/VicePlugin.cc b/C64.emu/src/main/VicePlugin.cc index b02203ac3..68866bda1 100644 --- a/C64.emu/src/main/VicePlugin.cc +++ b/C64.emu/src/main/VicePlugin.cc @@ -277,14 +277,14 @@ int VicePlugin::resources_get_default_value(const char *name, void *value_return return -1; } -int VicePlugin::machine_write_snapshot(const char *name, int save_roms, int save_disks, int even_mode) +int VicePlugin::machine_write_snapshot(const char *name, int save_roms, int save_disks, int even_mode) const { if(machine_write_snapshot_) return machine_write_snapshot_(name, save_roms, save_disks, even_mode); return -1; } -int VicePlugin::machine_read_snapshot(const char *name, int event_mode) +int VicePlugin::machine_read_snapshot(const char *name, int event_mode) const { if(machine_read_snapshot_) return machine_read_snapshot_(name, event_mode); @@ -308,7 +308,7 @@ struct drive_type_info_s *VicePlugin::machine_drive_get_type_info_list() return machine_drive_get_type_info_list_(); } -void VicePlugin::interrupt_maincpu_trigger_trap(void trap_func(uint16_t, void *data), void *data) +void VicePlugin::interrupt_maincpu_trigger_trap(void trap_func(uint16_t, void *data), void *data) const { if(interrupt_maincpu_trigger_trap_) interrupt_maincpu_trigger_trap_(trap_func, data); diff --git a/C64.emu/src/main/VicePlugin.hh b/C64.emu/src/main/VicePlugin.hh index 6bd902655..b4a3442e0 100644 --- a/C64.emu/src/main/VicePlugin.hh +++ b/C64.emu/src/main/VicePlugin.hh @@ -111,12 +111,12 @@ struct VicePlugin int resources_get_int(const char *name, int *value_return) const; int resources_set_int(const char *name, int value); int resources_get_default_value(const char *name, void *value_return) const; - int machine_write_snapshot(const char *name, int save_roms, int save_disks, int even_mode); - int machine_read_snapshot(const char *name, int event_mode); + int machine_write_snapshot(const char *name, int save_roms, int save_disks, int even_mode) const; + int machine_read_snapshot(const char *name, int event_mode) const; void machine_set_restore_key(int v); void machine_trigger_reset(const unsigned int mode); struct drive_type_info_s *machine_drive_get_type_info_list(); - void interrupt_maincpu_trigger_trap(void trap_func(uint16_t, void *data), void *data); + void interrupt_maincpu_trigger_trap(void trap_func(uint16_t, void *data), void *data) const; int init_main(); void maincpu_mainloop(); int autostart_autodetect(const char *file_name, const char *program_name, diff --git a/C64.emu/src/main/sysfile.cc b/C64.emu/src/main/sysfile.cc index d1cd4a717..48b0b8d8f 100644 --- a/C64.emu/src/main/sysfile.cc +++ b/C64.emu/src/main/sysfile.cc @@ -33,6 +33,8 @@ extern "C" namespace EmuEx { +constexpr SystemLogger log{"sysfile"}; + static int loadSysFile(Readable auto &file, const char *name, uint8_t *dest, int minsize, int maxsize) { //logMsg("loading system file: %s", complete_path); @@ -76,12 +78,12 @@ static int loadSysFile(Readable auto &file, const char *name, uint8_t *dest, int return (int)rsize; } -static ArchiveIO archiveIOForSysFile(IG::CStringView archivePath, std::string_view sysFileName, std::string_view subPath, char **complete_path_return) +static ArchiveIO archiveIOForSysFile(C64System &system, IG::CStringView archivePath, std::string_view sysFileName, std::string_view subPath, char **complete_path_return) { auto sysFilePath = FS::pathString(subPath, sysFileName); try { - for(auto &entry : FS::ArchiveIterator{gAppContext().openFileUri(archivePath)}) + for(auto &entry : system.systemFilesArchiveIterator(gAppContext(), archivePath)) { if(entry.type() == FS::file_type::directory) { @@ -121,6 +123,51 @@ static AssetIO assetIOForSysFile(IG::ApplicationContext ctx, std::string_view sy return file; } +FS::ArchiveIterator &C64System::systemFilesArchiveIterator(ApplicationContext ctx, std::string_view path) const +{ + if(!viceSysFilesArchiveIt.hasArchive()) + { + log.info("{} not cached, opening archive", path); + viceSysFilesArchiveIt = {ctx.openFileUri(path)}; + } + else + { + viceSysFilesArchiveIt.rewind(); + } + return viceSysFilesArchiveIt; +} + +void C64System::returnSystemFilesArchiveIO(ArchiveIO entry) +{ + viceSysFilesArchiveIt = {entry.releaseArchive()}; +} + +static bool archiveHasDirectory(CStringView path, std::string_view dirName) +{ + for(auto &entry : FS::ArchiveIterator{path}) + { + if(entry.type() == FS::file_type::directory && + entry.name().ends_with(dirName)) + { + return true; + } + } + return false; +} + +bool C64System::setSystemFilesPath(ApplicationContext ctx, CStringView path, FS::file_type type) +{ + log.info("set firmware path:{}", path); + if((type == FS::file_type::directory && !ctx.fileUriExists(FS::uriString(path, "DRIVES"))) + || (EmuApp::hasArchiveExtension(path) && !archiveHasDirectory(path, "DRIVES/"))) + { + return false; + } + sysFilePath[0] = path; + viceSysFilesArchiveIt = {}; + return true; +} + std::vector C64System::systemFilesWithExtension(const char *ext) const { logMsg("looking for system files with extension:%s", ext); @@ -137,7 +184,7 @@ std::vector C64System::systemFilesWithExtension(const char *ext) co continue; if(EmuApp::hasArchiveExtension(displayName)) { - for(auto &entry : FS::ArchiveIterator{appContext.openFileUri(basePath)}) + for(auto &entry : systemFilesArchiveIterator(appContext, basePath)) { if(entry.type() == FS::file_type::directory) { @@ -201,7 +248,7 @@ CLINK FILE *sysfile_open(const char *name, const char *subPath, char **complete_ continue; if(EmuApp::hasArchiveExtension(displayName)) { - auto io = archiveIOForSysFile(basePath, name, subPath, complete_path_return); + auto io = archiveIOForSysFile(system, basePath, name, subPath, complete_path_return); if(!io) continue; // Uncompress file into memory and wrap in FILE @@ -238,8 +285,8 @@ CLINK int sysfile_locate(const char *name, const char *subPath, char **complete_ { logMsg("sysfile locate:%s subPath:%s", name, subPath); auto appContext = gAppContext(); - auto &sysFilePath = static_cast(gSystem()).sysFilePath; - for(const auto &basePath : sysFilePath) + auto &system = static_cast(gSystem()); + for(const auto &basePath : system.sysFilePath) { if(basePath.empty()) continue; @@ -248,9 +295,10 @@ CLINK int sysfile_locate(const char *name, const char *subPath, char **complete_ continue; if(EmuApp::hasArchiveExtension(displayName)) { - auto io = archiveIOForSysFile(basePath, name, subPath, complete_path_return); + auto io = archiveIOForSysFile(system, basePath, name, subPath, complete_path_return); if(!io) continue; + system.returnSystemFilesArchiveIO(std::move(io)); return 0; } else @@ -296,10 +344,11 @@ CLINK int sysfile_load(const char *name, const char *subPath, uint8_t *dest, int continue; if(EmuApp::hasArchiveExtension(displayName)) { - auto io = archiveIOForSysFile(basePath, name, subPath, nullptr); + auto io = archiveIOForSysFile(system, basePath, name, subPath, nullptr); if(!io) continue; auto size = loadSysFile(io, name, dest, minsize, maxsize); + system.returnSystemFilesArchiveIO(std::move(io)); if(size == -1) { logErr("failed loading system file:%s from:%s", name, basePath.data()); diff --git a/C64.emu/src/main/zfile.cc b/C64.emu/src/main/zfile.cc index ff9976b1c..8321653b6 100644 --- a/C64.emu/src/main/zfile.cc +++ b/C64.emu/src/main/zfile.cc @@ -14,29 +14,55 @@ along with C64.emu. If not, see */ #include -#include -#include #include +#include #include #include +#include #include "MainSystem.hh" +#include extern "C" { #include "zfile.h" } +using namespace IG; using namespace EmuEx; -CLINK FILE *zfile_fopen(const char *path, const char *mode_) +namespace EmuEx +{ +constexpr SystemLogger log{"zfile"}; +} + +CLINK FILE *zfile_fopen(const char *path_, const char *mode_) { + using namespace IG; + std::string_view path{path_}; std::string_view mode{mode_}; auto appContext = gAppContext(); - if(EmuApp::hasArchiveExtension(appContext.fileUriDisplayName(path))) + if(path == ":::N") // null output + { + size_t *buffSizePtr; + std::ranges::copy(std::span{path_ + 5, sizeof(buffSizePtr)}, asWritableBytes(buffSizePtr).begin()); + //EmuEx::log.info("decoded size ptr:{}", (void*)buffSizePtr); + return OutSizeTracker{buffSizePtr}.toFileStream(mode_); + } + else if(path == ":::B") // buffer input/output + { + size_t *buffSizePtr; + auto it = std::ranges::copy(std::span{path_ + 5, sizeof(buffSizePtr)}, asWritableBytes(buffSizePtr).begin()).in; + uint8_t *buffDataPtr; + std::ranges::copy(std::span{it, sizeof(buffDataPtr)}, asWritableBytes(buffDataPtr).begin()); + //EmuEx::log.info("decoded size ptr:{} ({}) buff ptr:{}", (void*)buffSizePtr, *buffSizePtr, (void*)buffDataPtr); + return MapIO{IOBuffer{std::span{buffDataPtr, *buffSizePtr}, + [buffSizePtr](const uint8_t *, size_t size){ *buffSizePtr = size; }}}.toFileStream(mode_); + } + else if(EmuApp::hasArchiveExtension(appContext.fileUriDisplayName(path))) { if(mode.contains('w') || mode.contains('+')) { - logErr("opening archive %s with write mode not supported", path); + EmuEx::log.error("opening archive {} with write mode not supported", path); return nullptr; } try @@ -49,15 +75,15 @@ CLINK FILE *zfile_fopen(const char *path, const char *mode_) } if(EmuSystem::defaultFsFilter(entry.name())) { - logMsg("archive file entry:%s", entry.name().data()); + EmuEx::log.info("archive file entry:{}", entry.name()); return MapIO{entry.releaseIO()}.toFileStream(mode_); } } - logErr("no recognized file extensions in archive:%s", path); + EmuEx::log.error("no recognized file extensions in archive:{}", path); } catch(...) { - logErr("error opening archive:%s", path); + EmuEx::log.error("error opening archive:{}", path); } return nullptr; } diff --git a/EmuFramework/include/emuframework/AutosaveManager.hh b/EmuFramework/include/emuframework/AutosaveManager.hh index 0b32433f6..11db88a87 100644 --- a/EmuFramework/include/emuframework/AutosaveManager.hh +++ b/EmuFramework/include/emuframework/AutosaveManager.hh @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +58,12 @@ public: bool load(LoadAutosaveMode m) { return load(AutosaveActionSource::Auto, m); } bool load(AutosaveActionSource src = AutosaveActionSource::Auto) { return load(src, LoadAutosaveMode::Normal); } bool setSlot(std::string_view name); - void resetSlot(std::string_view name = "") { autoSaveSlot = name; } + void resetSlot(std::string_view name = "") + { + autoSaveSlot = name; + autoSaveTimerElapsedTime = {}; + stateIO = {}; + } bool renameSlot(std::string_view name, std::string_view newName); bool deleteSlot(std::string_view name); std::string_view slotName() const { return autoSaveSlot; } @@ -82,9 +88,14 @@ public: private: EmuApp &app; std::string autoSaveSlot; + FileIO stateIO; Timer autoSaveTimer; SteadyClockTimePoint autoSaveTimerStartTime{}; SteadyClockTime autoSaveTimerElapsedTime{}; + + bool saveState(); + bool loadState(); + public: Minutes autosaveTimerMins{}; AutosaveLaunchMode autosaveLaunchMode{}; diff --git a/EmuFramework/include/emuframework/EmuSystem.hh b/EmuFramework/include/emuframework/EmuSystem.hh index 82aa57fb4..e348152b3 100755 --- a/EmuFramework/include/emuframework/EmuSystem.hh +++ b/EmuFramework/include/emuframework/EmuSystem.hh @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -131,6 +132,11 @@ using FrameTime = Nanoseconds; constexpr const char *optionUserPathContentToken = ":CONTENT:"; +struct SaveStateFlags +{ + uint8_t uncompressed:1{}; +}; + class EmuSystem { public: @@ -194,8 +200,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const; - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &io, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); @@ -236,6 +243,10 @@ public: bool isActive() const { return state == State::ACTIVE; } bool isStarted() const { return state == State::ACTIVE || state == State::PAUSED; } bool isPaused() const { return state == State::PAUSED; } + void loadState(EmuApp &, CStringView uri); + void saveState(CStringView uri); + DynArray saveState(); + DynArray uncompressGzipState(std::span buff, size_t expectedSize); bool stateExists(int slot) const; static std::string_view stateSlotName(int slot); std::string_view stateSlotName() { return stateSlotName(stateSlot()); } diff --git a/EmuFramework/include/emuframework/EmuSystemInlines.hh b/EmuFramework/include/emuframework/EmuSystemInlines.hh index d7e05c102..718ac4ad8 100644 --- a/EmuFramework/include/emuframework/EmuSystemInlines.hh +++ b/EmuFramework/include/emuframework/EmuSystemInlines.hh @@ -199,14 +199,23 @@ void EmuSystem::runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *a static_cast(this)->runFrame(task, video, audio); } -void EmuSystem::loadState(EmuApp &app, CStringView uri) +size_t EmuSystem::stateSize() { - static_cast(this)->loadState(app, uri); + if(&MainSystem::stateSize != &EmuSystem::stateSize) + return static_cast(this)->stateSize(); + return 0; } -void EmuSystem::saveState(CStringView uri) +void EmuSystem::readState(EmuApp &app, std::span buff) { - static_cast(this)->saveState(uri); + if(&MainSystem::readState != &EmuSystem::readState) + static_cast(this)->readState(app, buff); +} + +size_t EmuSystem::writeState(std::span buff, SaveStateFlags flags) +{ + if(&MainSystem::writeState != &EmuSystem::writeState) + return static_cast(this)->writeState(buff, flags); } void EmuSystem::clearInputBuffers(EmuInputView &view) diff --git a/EmuFramework/include/emuframework/OutSizeTracker.hh b/EmuFramework/include/emuframework/OutSizeTracker.hh new file mode 100644 index 000000000..d338f38bb --- /dev/null +++ b/EmuFramework/include/emuframework/OutSizeTracker.hh @@ -0,0 +1,69 @@ +#pragma once + +/* This file is part of EmuFramework. + + Imagine 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 3 of the License, or + (at your option) any later version. + + Imagine 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 EmuFramework. If not, see */ + +#include +#include + +namespace EmuEx +{ + +using namespace IG; + +class OutSizeTracker : public IOUtils +{ +public: + using IOUtilsBase = IOUtils; + using IOUtilsBase::read; + using IOUtilsBase::write; + using IOUtilsBase::seek; + using IOUtilsBase::toFileStream; + + OutSizeTracker(size_t *sizePtr): sizePtr{sizePtr} {} + ssize_t read(void *buff, size_t bytes, std::optional offset = {}) { return 0; } + + off_t seek(off_t offset, SeekMode mode) + { + size_t newPos = transformOffsetToAbsolute(mode, offset, 0, off_t(size()), off_t(currPos)); + if(newPos > size()) + return -1; + currPos = newPos; + return newPos; + } + + size_t size() const { return *sizePtr; } + std::span map() { return {}; } + explicit operator bool() const { return sizePtr; } + + ssize_t write(const void *buff, size_t bytes, std::optional offset = {}) + { + currPos += bytes; + if(currPos > *sizePtr) + *sizePtr = currPos; + return bytes; + } + +private: + size_t currPos{}; + size_t *sizePtr{}; +}; + +} + +namespace IG +{ +template class IOUtils; +} diff --git a/EmuFramework/src/AutosaveManager.cc b/EmuFramework/src/AutosaveManager.cc index 3f720311c..f6bef5e6b 100644 --- a/EmuFramework/src/AutosaveManager.cc +++ b/EmuFramework/src/AutosaveManager.cc @@ -20,10 +20,13 @@ #include "pathUtils.hh" #include #include +#include namespace EmuEx { +constexpr SystemLogger log{"AutosaveMgr"}; + AutosaveManager::AutosaveManager(EmuApp &app_): app{app_}, autoSaveTimer @@ -31,8 +34,7 @@ AutosaveManager::AutosaveManager(EmuApp &app_): "AutosaveManager::autosaveTimer", [this]() { - logMsg("running autosave timer"); - app.syncEmulationThread(); + log.info("running autosave timer"); save(); resetTimer(); return true; @@ -43,11 +45,11 @@ bool AutosaveManager::save(AutosaveActionSource src) { if(autoSaveSlot == noAutosaveName) return true; - logMsg("saving autosave slot:%s", autoSaveSlot.c_str()); + log.info("saving autosave slot:{}", autoSaveSlot); system().flushBackupMemory(app); if(saveOnlyBackupMemory && src == AutosaveActionSource::Auto) return true; - return app.saveState(statePath()); + return saveState(); } bool AutosaveManager::load(AutosaveActionSource src, LoadAutosaveMode mode) @@ -57,6 +59,24 @@ bool AutosaveManager::load(AutosaveActionSource src, LoadAutosaveMode mode) try { system().loadBackupMemory(app); + if(saveOnlyBackupMemory && src == AutosaveActionSource::Auto) + return true; + if(!stateIO) + stateIO = appContext().openFileUri(statePath(), {}, OpenFlags::createFile()); + if(stateIO.getExpected(0)) // check if state contains data + { + if(mode == LoadAutosaveMode::NoState) + { + log.info("skipped loading autosave state"); + return true; + } + return loadState(); + } + else + { + log.info("autosave state doesn't exist, creating"); + return saveState(); + } } catch(std::exception &err) { @@ -66,23 +86,36 @@ bool AutosaveManager::load(AutosaveActionSource src, LoadAutosaveMode mode) app.postErrorMessage(4, err.what()); return false; } - if(saveOnlyBackupMemory && src == AutosaveActionSource::Auto) - return true; - auto path = statePath(); - if(appContext().fileUriExists(path)) +} + +bool AutosaveManager::saveState() +{ + log.info("saving autosave state"); + stateIO.truncate(0); + app.syncEmulationThread(); + auto state = app.system().saveState(); + if(stateIO.write(state.span(), 0).bytes != ssize_t(state.size())) { - if(mode == LoadAutosaveMode::NoState) - { - logMsg("skipped loading autosave state"); - return true; - } - logMsg("loading autosave state"); - return app.loadState(path); + app.postErrorMessage(4, "Error writing autosave state"); + return false; } - else + return true; +} + +bool AutosaveManager::loadState() +{ + log.info("loading autosave state"); + app.syncEmulationThread(); + try { - logMsg("autosave state doesn't exist, creating"); - return app.saveState(path); + app.system().readState(app, stateIO.buffer(IOBufferMode::Direct)); + resetTimer(); + return true; + } + catch(std::exception &err) + { + app.postErrorMessage(4, std::format("Error loading autosave state:\n{}", err.what())); + return false; } } @@ -97,8 +130,7 @@ bool AutosaveManager::setSlot(std::string_view name) if(!system().createContentLocalSaveDirectory(name)) return false; } - autoSaveSlot = name; - autoSaveTimerElapsedTime = {}; + resetSlot(name); return load(); } diff --git a/EmuFramework/src/EmuApp.cc b/EmuFramework/src/EmuApp.cc index 42a6c1c3f..365f77c3f 100644 --- a/EmuFramework/src/EmuApp.cc +++ b/EmuFramework/src/EmuApp.cc @@ -830,7 +830,7 @@ void EmuApp::launchSystem(const Input::Event &e) auto stateIsOlderThanBackupMemory = [&] { auto stateTime = autosaveManager_.stateTime(); - return hasTime(stateTime) && stateTime < autosaveManager_.backupMemoryTime(); + return hasTime(stateTime) && (autosaveManager_.backupMemoryTime() - stateTime) > Seconds{1}; }; if(system().usesBackupMemory() && loadMode == LoadAutosaveMode::Normal && !autosaveManager_.saveOnlyBackupMemory && stateIsOlderThanBackupMemory()) diff --git a/EmuFramework/src/EmuSystem.cc b/EmuFramework/src/EmuSystem.cc index dbd6aa5ec..d47ef7435 100644 --- a/EmuFramework/src/EmuSystem.cc +++ b/EmuFramework/src/EmuSystem.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "pathUtils.hh" @@ -67,6 +68,37 @@ bool EmuApp::shouldOverwriteExistingState() const return !confirmOverwriteState || !system().stateExists(system().stateSlot()); } +void EmuSystem::loadState(EmuApp &app, CStringView uri) +{ + auto file = appContext().openFileUri(uri, IOAccessHint::All, {}); + readState(app, file.buffer(IOBufferMode::Release)); +} + +void EmuSystem::saveState(CStringView uri) +{ + auto file = appContext().openFileUri(uri, {}, OpenFlags::newFile()); + file.write(saveState().span()); +} + +DynArray EmuSystem::saveState() +{ + DynArray stateArr{stateSize()}; + stateArr.trim(writeState(stateArr)); + return stateArr; +} + +DynArray EmuSystem::uncompressGzipState(std::span buff, size_t expectedSize) +{ + assert(expectedSize); + auto uncompArr = DynArray{expectedSize}; + auto size = uncompressGzip(uncompArr, buff); + if(!size) + throw std::runtime_error("Error uncompressing state"); + if(size != expectedSize) + throw std::runtime_error("Invalid state size"); + return uncompArr; +} + void EmuSystem::setSpeedMultiplier(EmuAudio &emuAudio, double speed) { emuTiming.setSpeedMultiplier(speed); diff --git a/EmuFramework/src/shared/mednafen-emuex/MDFNUtils.hh b/EmuFramework/src/shared/mednafen-emuex/MDFNUtils.hh index adf617c3f..15c253f8f 100644 --- a/EmuFramework/src/shared/mednafen-emuex/MDFNUtils.hh +++ b/EmuFramework/src/shared/mednafen-emuex/MDFNUtils.hh @@ -18,11 +18,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include
#include @@ -137,4 +139,55 @@ inline void runFrame(EmuSystem &sys, Mednafen::MDFNGI &mdfnGameInfo, EmuSystemTa } } +// Save states + +inline size_t stateSizeMDFN() +{ + using namespace Mednafen; + MemoryStream s; + MDFNSS_SaveSM(&s); + return s.size(); +} + +inline void readStateMDFN(EmuApp &app, std::span buff) +{ + using namespace Mednafen; + if(hasGzipHeader(buff)) + { + MemoryStream s{stateSizeMDFN(), -1}; + auto outputSize = uncompressGzip({s.map(), size_t(s.size())}, buff); + if(!outputSize) + throw std::runtime_error("Error uncompressing state"); + if(outputSize <= 32) + throw std::runtime_error("Invalid state size"); + auto sizeFromHeader = MDFN_de32lsb(s.map() + 16 + 4) & 0x7FFFFFFF; + if(sizeFromHeader != outputSize) + throw std::runtime_error(std::format("Bad state header size, got {} but expected {}", sizeFromHeader, outputSize)); + s.setSize(outputSize); + MDFNSS_LoadSM(&s); + } + else + { + FileStream s{buff}; + MDFNSS_LoadSM(&s); + } +} + +inline size_t writeStateMDFN(std::span buff, SaveStateFlags flags) +{ + using namespace Mednafen; + if(flags.uncompressed) + { + FileStream s{buff}; + MDFNSS_SaveSM(&s); + return s.size(); + } + else + { + MemoryStream s; + MDFNSS_SaveSM(&s); + return compressGzip(buff, {s.map(), size_t(s.size())}, MDFN_GetSettingI("filesys.state_comp_level")); + } +} + } diff --git a/EmuFramework/src/shared/mednafen-emuex/StreamImpl.cc b/EmuFramework/src/shared/mednafen-emuex/StreamImpl.cc index 75c6cf17d..5512e15a1 100644 --- a/EmuFramework/src/shared/mednafen-emuex/StreamImpl.cc +++ b/EmuFramework/src/shared/mednafen-emuex/StreamImpl.cc @@ -67,6 +67,9 @@ catch(std::system_error &err) throw MDFN_Error(ene.Errno(), _("Error opening file \"%s\": %s"), path.c_str(), ene.StrError()); } +FileStream::FileStream(std::span buff): + io{IG::MapIO{buff}} {} + FileStream::~FileStream() {} uint64 FileStream::attributes(void) diff --git a/EmuFramework/src/shared/mednafen/FileStream.h b/EmuFramework/src/shared/mednafen/FileStream.h index 0018435d2..ac657fda2 100644 --- a/EmuFramework/src/shared/mednafen/FileStream.h +++ b/EmuFramework/src/shared/mednafen/FileStream.h @@ -25,6 +25,7 @@ #include "Stream.h" #include "VirtualFS.h" #include +#include namespace Mednafen { @@ -61,6 +62,7 @@ class FileStream : public Stream }; FileStream(const std::string& path, const uint32 mode, const int do_lock = false, const uint32 buffer_size = 4096); + FileStream(std::span buff); virtual ~FileStream() override; virtual uint64 attributes(void) override; diff --git a/GBA.emu/src/main/Main.cc b/GBA.emu/src/main/Main.cc index 9ec4728fe..7b79fcc98 100755 --- a/GBA.emu/src/main/Main.cc +++ b/GBA.emu/src/main/Main.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -78,16 +79,32 @@ FS::FileString GbaSystem::stateFilename(int slot, std::string_view name) const return IG::format("{}{}.sgm", name, saveSlotChar(slot)); } -void GbaSystem::saveState(IG::CStringView path) +void GbaSystem::readState(EmuApp &app, std::span buff) { - if(!CPUWriteState(appContext(), gGba, path)) - return throwFileWriteError(); + DynArray uncompArr; + if(hasGzipHeader(buff)) + { + uncompArr = uncompressGzipState(buff, saveStateSize); + buff = uncompArr; + } + if(!CPUReadState(gGba, buff.data())) + throw std::runtime_error("Invalid state data"); } -void GbaSystem::loadState(EmuApp &app, IG::CStringView path) +size_t GbaSystem::writeState(std::span buff, SaveStateFlags flags) { - if(!CPUReadState(app.appContext(), gGba, path)) - return throwFileReadError(); + assert(buff.size() >= saveStateSize); + if(flags.uncompressed) + { + return CPUWriteState(gGba, buff.data()); + } + else + { + assert(saveStateSize); + auto stateArr = DynArray(saveStateSize); + CPUWriteState(gGba, stateArr.data()); + return compressGzip(buff, stateArr, Z_DEFAULT_COMPRESSION); + } } void GbaSystem::loadBackupMemory(EmuApp &app) @@ -184,6 +201,7 @@ void GbaSystem::loadContent(IO &io, EmuSystemCreateParams, OnLoadProgressDelegat applyGamePatches(gGba.mem.rom, size); CPUInit(gGba, 0, 0); CPUReset(gGba); + saveStateSize = CPUWriteState(gGba, DynArray{maxStateSize}.data()); readCheatFile(*this); } diff --git a/GBA.emu/src/main/MainSystem.hh b/GBA.emu/src/main/MainSystem.hh index 848282e78..e6a27d980 100644 --- a/GBA.emu/src/main/MainSystem.hh +++ b/GBA.emu/src/main/MainSystem.hh @@ -58,6 +58,8 @@ public: Byte1Option optionRtcEmulation{CFGKEY_RTC_EMULATION, std::to_underlying(RtcMode::AUTO), 0, optionIsValidWithMax<2>}; Byte4Option optionSaveTypeOverride{CFGKEY_SAVE_TYPE_OVERRIDE, GBA_SAVE_AUTO, 0, optionSaveTypeOverrideIsValid}; FileIO saveFileIO; + static constexpr size_t maxStateSize{0x1FFFFF}; + size_t saveStateSize{}; int detectedSaveSize{}; int sensorX{}, sensorY{}, sensorZ{}; float lightSensorScaleLux{lightSensorScaleLuxDefault}; @@ -83,8 +85,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".gqs"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize() { return saveStateSize; } + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/GBA.emu/src/main/VbamApi.cc b/GBA.emu/src/main/VbamApi.cc index f1d7e7174..bd63ece32 100644 --- a/GBA.emu/src/main/VbamApi.cc +++ b/GBA.emu/src/main/VbamApi.cc @@ -321,3 +321,60 @@ void setSaveMemory(IG::ByteBuffer buff) eepromData = std::move(buff); } } + +void utilWriteIntMem(uint8_t*& data, int val) +{ + memcpy(data, &val, sizeof(int)); + data += sizeof(int); +} + +void utilWriteMem(uint8_t*& data, const void* in_data, unsigned size) +{ + memcpy(data, in_data, size); + data += size; +} + +void utilWriteDataMem(uint8_t*& data, const variable_desc* desc) +{ + while (desc->address) + { + utilWriteMem(data, desc->address, desc->size); + desc++; + } +} + +int utilReadIntMem(const uint8_t*& data) +{ + int res; + memcpy(&res, data, sizeof(int)); + data += sizeof(int); + return res; +} + +void utilReadMem(void* buf, const uint8_t*& data, unsigned size) +{ + memcpy(buf, data, size); + data += size; +} + +void utilReadDataMem(const uint8_t*& data, const variable_desc* desc) +{ + while (desc->address) + { + utilReadMem(desc->address, data, desc->size); + desc++; + } +} + +void cheatsSaveGame(uint8_t*& data) +{ + utilWriteIntMem(data, cheatsList.size()); + utilWriteMem(data, cheatsList.data(), CHEATS_LIST_DATA_SIZE); +} + +void cheatsReadGame(const uint8_t*& data) +{ + int cheats = utilReadIntMem(data); + cheatsList.resize(cheats); + utilReadMem(cheatsList.data(), data, CHEATS_LIST_DATA_SIZE); +} diff --git a/GBA.emu/src/vbam/Util.h b/GBA.emu/src/vbam/Util.h index 67539ad07..6218d940b 100644 --- a/GBA.emu/src/vbam/Util.h +++ b/GBA.emu/src/vbam/Util.h @@ -46,15 +46,14 @@ void utilGBAFindSave(const uint8_t *rom, const int); void utilUpdateSystemColorMaps(int colorDepth, int redShift, int greenShift, int blueShift, bool lcd = false); bool utilFileExists(const char *filename); -#ifdef __LIBRETRO__ void utilWriteIntMem(uint8_t *&data, int); void utilWriteMem(uint8_t *&data, const void *in_data, unsigned size); -void utilWriteDataMem(uint8_t *&data, variable_desc *); +void utilWriteDataMem(uint8_t *&data, const variable_desc *); int utilReadIntMem(const uint8_t *&data); void utilReadMem(void *buf, const uint8_t *&data, unsigned size); -void utilReadDataMem(const uint8_t *&data, variable_desc *); -#else +void utilReadDataMem(const uint8_t *&data, const variable_desc *); + gzFile utilGzOpen(int fd, const char *mode); gzFile utilMemGzOpen(char *memory, int available, const char *mode); int utilGzWrite(gzFile file, const voidp buffer, unsigned int len); @@ -67,6 +66,5 @@ void utilReadData(gzFile, const variable_desc *); void utilReadDataSkip(gzFile, const variable_desc *); int utilReadInt(gzFile); void utilWriteInt(gzFile, int); -#endif #endif // UTIL_H diff --git a/GBA.emu/src/vbam/gba/Cheats.h b/GBA.emu/src/vbam/gba/Cheats.h index 57bc4027c..4e9e51ce9 100644 --- a/GBA.emu/src/vbam/gba/Cheats.h +++ b/GBA.emu/src/vbam/gba/Cheats.h @@ -40,6 +40,8 @@ void cheatsDisable(ARM7TDMI &cpu, int number); #ifndef __LIBRETRO__ void cheatsSaveGame(gzFile file); void cheatsReadGame(gzFile file, int version); +void cheatsSaveGame(uint8_t*& data); +void cheatsReadGame(const uint8_t*& data); void cheatsReadGameSkip(gzFile file, int version); void cheatsSaveCheatList(IG::ApplicationContext, const char *file); bool cheatsLoadCheatList(IG::ApplicationContext, const char *file); diff --git a/GBA.emu/src/vbam/gba/EEprom.cpp b/GBA.emu/src/vbam/gba/EEprom.cpp index cf72fc57a..351c0fc82 100644 --- a/GBA.emu/src/vbam/gba/EEprom.cpp +++ b/GBA.emu/src/vbam/gba/EEprom.cpp @@ -48,22 +48,24 @@ void eepromReset() eepromAddress = 0; } -#ifdef __LIBRETRO__ void eepromSaveGame(uint8_t*& data) { - utilWriteDataMem(data, eepromSaveData); + uint8_t eepromDataTemp[SIZE_EEPROM_8K]{}; + IG::copy_n(eepromData.data(), eepromData.size(), eepromDataTemp); + utilWriteDataMem(data, eepromSaveData(eepromDataTemp).data()); utilWriteIntMem(data, eepromSize); - utilWriteMem(data, eepromData, SIZE_EEPROM_8K); + utilWriteMem(data, eepromDataTemp, SIZE_EEPROM_8K); } void eepromReadGame(const uint8_t*& data) { - utilReadDataMem(data, eepromSaveData); + uint8_t eepromDataTemp[SIZE_EEPROM_8K]{}; + utilReadDataMem(data, eepromSaveData(eepromDataTemp).data()); eepromSize = utilReadIntMem(data); - utilReadMem(eepromData, data, SIZE_EEPROM_8K); + utilReadMem(eepromDataTemp, data, SIZE_EEPROM_8K); + IG::copy_n(eepromDataTemp, eepromData.size(), eepromData.data()); } -#else // !__LIBRETRO__ void eepromSaveGame(gzFile gzFile) { @@ -101,7 +103,6 @@ void eepromReadGameSkip(gzFile gzFile, int version) utilGzSeek(gzFile, SIZE_EEPROM_8K, SEEK_CUR); } } -#endif int eepromRead(uint32_t /* address */) { diff --git a/GBA.emu/src/vbam/gba/EEprom.h b/GBA.emu/src/vbam/gba/EEprom.h index 16e15adad..534af68c8 100644 --- a/GBA.emu/src/vbam/gba/EEprom.h +++ b/GBA.emu/src/vbam/gba/EEprom.h @@ -4,14 +4,14 @@ #include "../common/Types.h" #include -#ifdef __LIBRETRO__ + extern void eepromSaveGame(uint8_t*& data); extern void eepromReadGame(const uint8_t*& data); -#else // !__LIBRETRO__ + extern void eepromSaveGame(gzFile _gzFile); extern void eepromReadGame(gzFile _gzFile, int version); extern void eepromReadGameSkip(gzFile _gzFile, int version); -#endif + extern IG::ByteBuffer eepromData; extern int eepromRead(uint32_t address); extern void eepromWrite(uint32_t address, uint8_t value, int cpuDmaCount); diff --git a/GBA.emu/src/vbam/gba/Flash.cpp b/GBA.emu/src/vbam/gba/Flash.cpp index 7f6bb9278..d0389ee7b 100644 --- a/GBA.emu/src/vbam/gba/Flash.cpp +++ b/GBA.emu/src/vbam/gba/Flash.cpp @@ -232,18 +232,25 @@ static auto flashSaveData3(uint8_t *flashSaveMemory, int &flashSize) }}; }; -#ifdef __LIBRETRO__ void flashSaveGame(uint8_t*& data) { - utilWriteDataMem(data, flashSaveData3); + uint8_t flashSaveMemoryTemp[SIZE_FLASH1M]{}; + IG::copy_n(flashSaveMemory.data(), flashSaveMemory.size(), flashSaveMemoryTemp); + utilWriteDataMem(data, flashSaveData3(flashSaveMemoryTemp, flashSize).data()); } void flashReadGame(const uint8_t*& data) { - utilReadDataMem(data, flashSaveData3); + uint8_t flashSaveMemoryTemp[SIZE_FLASH1M]{}; + int flashSizeTemp = flashSize; + utilReadDataMem(data, flashSaveData3(flashSaveMemoryTemp, flashSizeTemp).data()); + if(flashSizeTemp != flashSize) + { + logWarn("expected flash size:%d but got %d from state", flashSize, flashSizeTemp); + } + IG::copy_n(flashSaveMemoryTemp, flashSaveMemory.size(), flashSaveMemory.data()); } -#else // !__LIBRETRO__ static auto flashSaveData(uint8_t *flashSaveMemory) { return std::array @@ -306,4 +313,3 @@ void flashReadGameSkip(gzFile gzFile, int version) utilReadDataSkip(gzFile, flashSaveData3(flashSaveMemoryTemp, flashSizeTemp).data()); } } -#endif diff --git a/GBA.emu/src/vbam/gba/Flash.h b/GBA.emu/src/vbam/gba/Flash.h index 2ec3a9b15..a347efc84 100644 --- a/GBA.emu/src/vbam/gba/Flash.h +++ b/GBA.emu/src/vbam/gba/Flash.h @@ -6,14 +6,14 @@ #define FLASH_128K_SZ 0x20000 -#ifdef __LIBRETRO__ + extern void flashSaveGame(uint8_t*& data); extern void flashReadGame(const uint8_t*& data); -#else + extern void flashSaveGame(gzFile _gzFile); extern void flashReadGame(gzFile _gzFile, int version); extern void flashReadGameSkip(gzFile _gzFile, int version); -#endif + extern IG::ByteBuffer flashSaveMemory; extern uint8_t flashRead(uint32_t address); extern void flashWrite(uint32_t address, uint8_t byte); diff --git a/GBA.emu/src/vbam/gba/GBA.cpp b/GBA.emu/src/vbam/gba/GBA.cpp index ee5c9ef3a..7bb078edc 100644 --- a/GBA.emu/src/vbam/gba/GBA.cpp +++ b/GBA.emu/src/vbam/gba/GBA.cpp @@ -706,18 +706,20 @@ static void CPUUpdateRenderBuffers(GBASys &gba, bool force) #define CPUUpdateRenderBuffers(force) CPUUpdateRenderBuffers(gba, force) -#ifdef __LIBRETRO__ #include -unsigned int CPUWriteState(uint8_t* data) +unsigned int CPUWriteState(GBASys &gba, uint8_t* data) { + auto &cpu = gba.cpu; uint8_t* orig = data; utilWriteIntMem(data, SAVE_GAME_VERSION); utilWriteMem(data, &rom[0xa0], 16); utilWriteIntMem(data, coreOptions.useBios); - utilWriteMem(data, ®[0], sizeof(reg)); + utilWriteMem(data, &cpu.reg[0], sizeof(cpu.reg)); + saveNFlag = gba.cpu.nFlag(); + saveZFlag = gba.cpu.zFlag(); utilWriteDataMem(data, saveGameStruct); utilWriteIntMem(data, stopState); @@ -728,19 +730,22 @@ unsigned int CPUWriteState(uint8_t* data) utilWriteMem(data, workRAM, SIZE_WRAM); utilWriteMem(data, vram, SIZE_VRAM); utilWriteMem(data, oam, SIZE_OAM); - utilWriteMem(data, pix, SIZE_PIX); - utilWriteMem(data, ioMem, SIZE_IOMEM); + uint32_t dummyPix[241*162]{}; + utilWriteMem(data, dummyPix, SIZE_PIX); + utilWriteMem(data, gba.mem.ioMem.b, SIZE_IOMEM); eepromSaveGame(data); flashSaveGame(data); soundSaveGame(data); + cheatsSaveGame(data); rtcSaveGame(data); return (ptrdiff_t)data - (ptrdiff_t)orig; } -bool CPUReadState(const uint8_t* data) +bool CPUReadState(GBASys &gba, const uint8_t* data) { + auto &cpu = gba.cpu; // Don't really care about version. int version = utilReadIntMem(data); if (version != SAVE_GAME_VERSION) @@ -754,9 +759,10 @@ bool CPUReadState(const uint8_t* data) // Don't care about use bios ... utilReadIntMem(data); - utilReadMem(®[0], data, sizeof(reg)); + utilReadMem(&cpu.reg[0], data, sizeof(cpu.reg)); utilReadDataMem(data, saveGameStruct); + gba.cpu.updateNZFlags(saveNFlag, saveZFlag); stopState = utilReadIntMem(data) ? true : false; @@ -773,19 +779,21 @@ bool CPUReadState(const uint8_t* data) utilReadMem(workRAM, data, SIZE_WRAM); utilReadMem(vram, data, SIZE_VRAM); utilReadMem(oam, data, SIZE_OAM); - utilReadMem(pix, data, SIZE_PIX); - utilReadMem(ioMem, data, SIZE_IOMEM); + uint32_t dummyPix[241*162]; + utilReadMem(dummyPix, data, SIZE_PIX); + utilReadMem(gba.mem.ioMem.b, data, SIZE_IOMEM); eepromReadGame(data); flashReadGame(data); - soundReadGame(data); + soundReadGame(gba, data); + cheatsReadGame(data); rtcReadGame(data); //// Copypasta stuff ... // set pointers! layerEnable = coreOptions.layerSettings & DISPCNT; - CPUUpdateRender(); + CPUUpdateRender(gba); // CPU Update Render Buffers set to true CLEAR_ARRAY(line0); @@ -800,19 +808,17 @@ bool CPUReadState(const uint8_t* data) SetSaveType(coreOptions.saveType); systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED; - if (armState) { - ARM_PREFETCH; + if (gba.cpu.armState) { + gba.cpu.ARM_PREFETCH(); } else { - THUMB_PREFETCH; + gba.cpu.THUMB_PREFETCH(); } - CPUUpdateRegister(0x204, CPUReadHalfWordQuick(0x4000204)); + CPUUpdateRegister(gba.cpu, 0x204, CPUReadHalfWordQuick(gba.cpu, 0x4000204)); return true; } -#else // !__LIBRETRO__ - static bool CPUWriteState(GBASys &gba, gzFile gzFile) { auto &cpu = gba.cpu; @@ -1073,7 +1079,6 @@ bool CPUReadState(IG::ApplicationContext ctx, GBASys &gba, const char * file) return res; } -#endif bool CPUExportEepromFile(const char* fileName) { diff --git a/GBA.emu/src/vbam/gba/GBA.h b/GBA.emu/src/vbam/gba/GBA.h index 2d815aee1..4bd5d1581 100644 --- a/GBA.emu/src/vbam/gba/GBA.h +++ b/GBA.emu/src/vbam/gba/GBA.h @@ -162,13 +162,13 @@ extern void CPUUpdateRender(GBASys &gba); extern void CPUUpdateRenderBuffers(bool); extern bool CPUReadMemState(GBASys &gba, char *, int); extern bool CPUWriteMemState(GBASys &gba, char *, int); -#ifdef __LIBRETRO__ -extern bool CPUReadState(const uint8_t*); -extern unsigned int CPUWriteState(uint8_t* data, unsigned int size); -#else + +extern bool CPUReadState(GBASys &gba, const uint8_t*); +extern unsigned int CPUWriteState(GBASys &gba, uint8_t* data); + extern bool CPUReadState(GBASys &gba, const char*); extern bool CPUWriteState(GBASys &gba, const char*); -#endif + extern int CPULoadRom(GBASys &gba, const char *); extern int CPULoadRomWithIO(GBASys &gba, IG::IO &); extern void doMirroring(GBASys &gba, bool); diff --git a/GBA.emu/src/vbam/gba/RTC.cpp b/GBA.emu/src/vbam/gba/RTC.cpp index 5920de266..6a3039654 100644 --- a/GBA.emu/src/vbam/gba/RTC.cpp +++ b/GBA.emu/src/vbam/gba/RTC.cpp @@ -306,7 +306,7 @@ void rtcReset() SetGBATime(); } -#ifdef __LIBRETRO__ + void rtcSaveGame(uint8_t*& data) { utilWriteMem(data, &rtcClockData, sizeof(rtcClockData)); @@ -316,7 +316,7 @@ void rtcReadGame(const uint8_t*& data) { utilReadMem(&rtcClockData, data, sizeof(rtcClockData)); } -#else + void rtcSaveGame(gzFile gzFile) { utilGzWrite(gzFile, &rtcClockData, sizeof(rtcClockData)); @@ -326,4 +326,3 @@ void rtcReadGame(gzFile gzFile) { utilGzRead(gzFile, &rtcClockData, sizeof(rtcClockData)); } -#endif diff --git a/GBA.emu/src/vbam/gba/RTC.h b/GBA.emu/src/vbam/gba/RTC.h index 9ff3b5c27..6bfe50e71 100644 --- a/GBA.emu/src/vbam/gba/RTC.h +++ b/GBA.emu/src/vbam/gba/RTC.h @@ -9,12 +9,12 @@ void rtcEnableRumble(bool e); bool rtcIsEnabled(); void rtcReset(); -#ifdef __LIBRETRO__ + void rtcReadGame(const uint8_t*& data); void rtcSaveGame(uint8_t*& data); -#else + void rtcReadGame(gzFile gzFile); void rtcSaveGame(gzFile gzFile); -#endif + #endif // RTC_H diff --git a/GBA.emu/src/vbam/gba/Sound.cpp b/GBA.emu/src/vbam/gba/Sound.cpp index a347f6eed..2082aa10c 100644 --- a/GBA.emu/src/vbam/gba/Sound.cpp +++ b/GBA.emu/src/vbam/gba/Sound.cpp @@ -858,10 +858,9 @@ void soundReadGame(GBASys &gba, gzFile in, int version) #endif // !__LIBRETRO__ -#ifdef __LIBRETRO__ void soundSaveGame(uint8_t*& out) { - gb_apu->save_state(&state.apu); + gb_apu.save_state(&state.apu); // Be sure areas for expansion get written as zero memset(dummy_state, 0, sizeof dummy_state); @@ -869,17 +868,16 @@ void soundSaveGame(uint8_t*& out) utilWriteDataMem(out, gba_state); } -void soundReadGame(const uint8_t*& in) +void soundReadGame(GBASys &gba, const uint8_t*& in) { // Prepare APU and default state reset_apu(); - gb_apu->save_state(&state.apu); + gb_apu.save_state(&state.apu); utilReadDataMem(in, gba_state); - gb_apu->load_state(state.apu); - write_SGCNT0_H(READ16LE(&ioMem[SGCNT0_H]) & 0x770F); + gb_apu.load_state(state.apu); + write_SGCNT0_H(gba, READ16LE(&ioMem[SGCNT0_H]) & 0x770F); - apply_muting(); + apply_muting(gba); } -#endif // __LIBRETRO__ diff --git a/GBA.emu/src/vbam/gba/Sound.h b/GBA.emu/src/vbam/gba/Sound.h index 47a3170f3..a9d6d809e 100644 --- a/GBA.emu/src/vbam/gba/Sound.h +++ b/GBA.emu/src/vbam/gba/Sound.h @@ -81,13 +81,13 @@ extern const int SOUND_CLOCK_TICKS; // Number of 16.8 MHz clocks between calls extern int &soundTicks; // Number of 16.8 MHz clocks until soundTick() will be called // Saves/loads emulator state -#ifdef __LIBRETRO__ + void soundSaveGame(uint8_t*&); -void soundReadGame(const uint8_t*& in); -#else +void soundReadGame(GBASys &gba, const uint8_t*& in); + void soundSaveGame(gzFile); void soundReadGame(GBASys &gba, gzFile, int version); -#endif + class Multi_Buffer; diff --git a/GBC.emu/src/main/Main.cc b/GBC.emu/src/main/Main.cc index 2f887869f..bd410acbe 100755 --- a/GBC.emu/src/main/Main.cc +++ b/GBC.emu/src/main/Main.cc @@ -16,6 +16,7 @@ #define LOGTAG "main" #include #include +#include #include #include #include @@ -90,18 +91,19 @@ FS::FileString GbcSystem::stateFilename(int slot, std::string_view name) const return IG::format("{}.0{}.gqs", name, saveSlotCharUpper(slot)); } -void GbcSystem::saveState(IG::CStringView path) +void GbcSystem::readState(EmuApp &app, std::span buff) { - OFStream stream{appContext().openFileUri(path, OpenFlags::newFile())}; - if(!gbEmu.saveState(frameBuffer, gambatte::lcd_hres, stream)) - throwFileWriteError(); + IStream stream{buff}; + if(!gbEmu.loadState(stream)) + throw std::runtime_error("Invalid state data"); } -void GbcSystem::loadState(EmuApp &app, IG::CStringView path) +size_t GbcSystem::writeState(std::span buff, SaveStateFlags flags) { - IFStream stream{app.appContext().openFileUri(path, IO::AccessHint::All)}; - if(!gbEmu.loadState(stream)) - throwFileReadError(); + assert(saveStateSize == buff.size()); + OStream stream{buff}; + gbEmu.saveState(frameBuffer, gambatte::lcd_hres, stream); + return saveStateSize; } void GbcSystem::loadBackupMemory(EmuApp &app) @@ -188,6 +190,9 @@ void GbcSystem::loadContent(IO &io, EmuSystemCreateParams, OnLoadProgressDelegat } readCheatFile(*this); applyCheats(); + saveStateSize = 0; + OStream stream{&saveStateSize}; + gbEmu.saveState(frameBuffer, gambatte::lcd_hres, stream); } bool GbcSystem::onVideoRenderFormatChange(EmuVideo &video, IG::PixelFormat fmt) diff --git a/GBC.emu/src/main/MainSystem.hh b/GBC.emu/src/main/MainSystem.hh index 981c0df4a..be8a427a9 100644 --- a/GBC.emu/src/main/MainSystem.hh +++ b/GBC.emu/src/main/MainSystem.hh @@ -42,6 +42,7 @@ public: FileIO saveFileIO; FileIO rtcFileIO; std::string cheatsDir; + size_t saveStateSize{}; uint64_t totalSamples{}; uint32_t totalFrames{}; uint8_t activeResampler = 1; @@ -69,8 +70,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".sta"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize() { return saveStateSize; } + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); @@ -90,6 +92,7 @@ public: bool resetSessionOptions(EmuApp &); bool onVideoRenderFormatChange(EmuVideo &, IG::PixelFormat); void renderFramebuffer(EmuVideo &); + protected: uint_least32_t makeOutputColor(uint_least32_t rgb888) const; size_t runUntilVideoFrame(gambatte::uint_least32_t *videoBuf, std::ptrdiff_t pitch, diff --git a/Lynx.emu/src/main/Main.cc b/Lynx.emu/src/main/Main.cc index c005ca112..55e132549 100755 --- a/Lynx.emu/src/main/Main.cc +++ b/Lynx.emu/src/main/Main.cc @@ -63,17 +63,9 @@ FS::FileString LynxSystem::stateFilename(int slot, std::string_view name) const return stateFilenameMDFN(*MDFNGameInfo, slot, name, 'a', noMD5InFilenames); } -void LynxSystem::saveState(IG::CStringView path) -{ - if(!MDFNI_SaveState(path, 0, 0, 0, 0)) - throwFileWriteError(); -} - -void LynxSystem::loadState(EmuApp &, IG::CStringView path) -{ - if(!MDFNI_LoadState(path, 0)) - throwFileReadError(); -} +size_t LynxSystem::stateSize() { return stateSizeMDFN(); } +void LynxSystem::readState(EmuApp &app, std::span buff) { readStateMDFN(app, buff); } +size_t LynxSystem::writeState(std::span buff, SaveStateFlags flags) { return writeStateMDFN(buff, flags); } void LynxSystem::closeSystem() { diff --git a/Lynx.emu/src/main/MainSystem.hh b/Lynx.emu/src/main/MainSystem.hh index fedd7ddcc..d9db25f42 100644 --- a/Lynx.emu/src/main/MainSystem.hh +++ b/Lynx.emu/src/main/MainSystem.hh @@ -67,8 +67,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".mca"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/MD.emu/src/main/Main.cc b/MD.emu/src/main/Main.cc index 0e588e91d..730c54fe8 100755 --- a/MD.emu/src/main/Main.cc +++ b/MD.emu/src/main/Main.cc @@ -145,22 +145,15 @@ static FS::PathString bramSaveFilename(EmuApp &app) return app.contentSaveFilePath(".brm"); } -static const unsigned maxSaveStateSize = STATE_SIZE+4; - -void MdSystem::saveState(IG::CStringView path) +void MdSystem::readState(EmuApp &app, std::span buff) { - auto stateData = std::make_unique(maxSaveStateSize); - logMsg("saving state data"); - size_t size = state_save(stateData.get()); - logMsg("writing to file"); - if(FileUtils::writeToUri(appContext(), path, {stateData.get(), size}) == -1) - throwFileWriteError(); - logMsg("wrote %zu byte state", size); + state_load(buff.data()); } -void MdSystem::loadState(EmuApp &app, IG::CStringView path) +size_t MdSystem::writeState(std::span buff, SaveStateFlags flags) { - state_load(FileUtils::bufferFromUri(app.appContext(), path).data()); + assert(buff.size() == maxSaveStateSize); + return state_save(buff.data()); } static bool sramHasContent(std::span sram) diff --git a/MD.emu/src/main/MainSystem.hh b/MD.emu/src/main/MainSystem.hh index dbe198108..1a6ba8a47 100644 --- a/MD.emu/src/main/MainSystem.hh +++ b/MD.emu/src/main/MainSystem.hh @@ -4,6 +4,7 @@ #include #include "genplus-config.h" #include "system.h" +#include "state.h" extern t_config config; @@ -49,6 +50,7 @@ public: #ifndef NO_SCD FS::PathString cdBiosUSAPath{}, cdBiosJpnPath{}, cdBiosEurPath{}; #endif + static constexpr size_t maxSaveStateSize = STATE_SIZE + 4; static constexpr auto ntscFrameTime{fromSeconds(262. * MCYCLES_PER_LINE / 53693175.)}; // ~59.92Hz static constexpr auto palFrameTime{fromSeconds(313. * MCYCLES_PER_LINE / 53203424.)}; // ~49.70Hz @@ -61,8 +63,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".gp"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize() { return maxSaveStateSize; } + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/MSX.emu/src/main/Main.cc b/MSX.emu/src/main/Main.cc index 06eabacf7..a15bc68d8 100755 --- a/MSX.emu/src/main/Main.cc +++ b/MSX.emu/src/main/Main.cc @@ -516,11 +516,6 @@ void MsxSystem::saveBlueMSXState(const char *filename) zipEndWrite(); } -void MsxSystem::saveState(IG::CStringView path) -{ - return saveBlueMSXState(path); -} - static FS::FileString saveStateGetFileString(SaveState* state, const char* tagName) { FS::FileStringArray name{}; @@ -592,9 +587,18 @@ void MsxSystem::loadBlueMSXState(EmuApp &app, const char *filename) logMsg("state loaded with machine:%s", machine->name); } -void MsxSystem::loadState(EmuApp &app, IG::CStringView path) +void MsxSystem::readState(EmuApp &app, std::span buff) +{ + setZipMemBuffer(buff); + loadBlueMSXState(app, ":::B"); +} + +size_t MsxSystem::writeState(std::span buff, SaveStateFlags flags) { - return loadBlueMSXState(app, path); + assert(buff.size() == stateSize()); + setZipMemBuffer(buff); + saveBlueMSXState(":::B"); + return zipMemBufferSize(); } void MsxSystem::closeSystem() diff --git a/MSX.emu/src/main/MainSystem.hh b/MSX.emu/src/main/MainSystem.hh index 359eba060..d8c1291f6 100644 --- a/MSX.emu/src/main/MainSystem.hh +++ b/MSX.emu/src/main/MainSystem.hh @@ -19,6 +19,8 @@ extern Machine *machine; bool zipStartWrite(const char *fileName); void zipEndWrite(); +void setZipMemBuffer(std::span buff); +size_t zipMemBufferSize(); IG::PixmapView frameBufferPixmap(); HdType boardGetHdType(int hdIndex); @@ -81,8 +83,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".sta"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize() { return 0x200000; } + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/MSX.emu/src/main/ziphelper.cc b/MSX.emu/src/main/ziphelper.cc index 29ab1f797..740208952 100644 --- a/MSX.emu/src/main/ziphelper.cc +++ b/MSX.emu/src/main/ziphelper.cc @@ -28,6 +28,8 @@ namespace EmuEx { IG::ApplicationContext gAppContext(); + +constexpr SystemLogger log{"zipHelper"}; } using namespace EmuEx; @@ -35,20 +37,46 @@ using namespace EmuEx; static struct archive *writeArch{}; static FS::ArchiveIterator cachedZipIt{}; static FS::PathString cachedZipName{}; +static uint8_t *buffData{}; +static size_t buffSize{}; + +void setZipMemBuffer(std::span buff) +{ + buffData = buff.data(); + buffSize = buff.size(); +} + +size_t zipMemBufferSize() { return buffSize; } -void zipCacheReadOnlyZip(const char* zipName) +static void unsetCachedReadZip() { - if(zipName && strlen(zipName)) + cachedZipIt = {}; + cachedZipName = {}; + EmuEx::log.info("unset cached read zip archive"); +} + +void zipCacheReadOnlyZip(const char* zipName_) +{ + if(!zipName_ || !strlen(zipName_)) { - logMsg("setting cached read zip archive:%s", zipName); - cachedZipIt = {EmuEx::gAppContext().openFileUri(zipName)}; - cachedZipName = zipName; + unsetCachedReadZip(); + return; } else { - cachedZipIt = {}; - cachedZipName = {}; - logMsg("unset cached read zip archive"); + std::string_view zipName{zipName_}; + cachedZipName = zipName; + if(zipName == ":::B") + { + EmuEx::log.info("using memory buffer as cached read zip archive"); + std::span buff{buffData, buffSize}; + cachedZipIt = IO{buff}; + } + else + { + EmuEx::log.info("setting cached read zip archive:{}", zipName); + cachedZipIt = {EmuEx::gAppContext().openFileUri(zipName)}; + } } } @@ -104,14 +132,27 @@ bool zipStartWrite(const char *fileName) assert(!writeArch); writeArch = archive_write_new(); archive_write_set_format_zip(writeArch); - int fd = EmuEx::gAppContext().openFileUriFd(fileName, IG::OpenFlags::testNewFile()).release(); - if(archive_write_open_fd(writeArch, fd) != ARCHIVE_OK) + if(std::string_view{fileName} == ":::B") { - archive_write_free(writeArch); - writeArch = {}; - if(fd != -1) - ::close(fd); - return false; + EmuEx::log.info("using memory buffer for zip write"); + if(archive_write_open_memory(writeArch, buffData, buffSize, &buffSize) != ARCHIVE_OK) + { + archive_write_free(writeArch); + writeArch = {}; + return false; + } + } + else + { + int fd = EmuEx::gAppContext().openFileUriFd(fileName, IG::OpenFlags::testNewFile()).release(); + if(archive_write_open_fd(writeArch, fd) != ARCHIVE_OK) + { + archive_write_free(writeArch); + writeArch = {}; + if(fd != -1) + ::close(fd); + return false; + } } return true; } diff --git a/NEO.emu/build.mk b/NEO.emu/build.mk index e5991f214..f111069ca 100644 --- a/NEO.emu/build.mk +++ b/NEO.emu/build.mk @@ -6,6 +6,7 @@ include $(IMAGINE_PATH)/make/imagineAppBase.mk SRC += main/Main.cc \ main/input.cc \ main/options.cc \ +main/state.cc \ main/unzip.cc \ main/EmuMenuViews.cc @@ -31,7 +32,6 @@ $(GEO)/neoboot.c \ $(GEO)/neocrypt.c \ $(GEO)/pd4990a.c \ $(GEO)/roms.c \ -$(GEO)/state.c \ $(GEO)/timer.c \ $(GEO)/video.c diff --git a/NEO.emu/src/gngeo/cyclone_interf.c b/NEO.emu/src/gngeo/cyclone_interf.c index c5cb136d4..420de9e39 100644 --- a/NEO.emu/src/gngeo/cyclone_interf.c +++ b/NEO.emu/src/gngeo/cyclone_interf.c @@ -281,7 +281,7 @@ static void MyWrite32(unsigned int a,unsigned int d) { mem68k_store_invalid_long(a, d); } -void cpu_68k_mkstate(gzFile gzf,int mode) { +void cpu_68k_mkstate(Stream *gzf,int mode) { Uint8 save_buffer[128]; logMsg("Save state mode %s PC=%08x",(mode==STREAD?"READ":"WRITE"),MyCyclone.pc-MyCyclone.membase); if (mode==STWRITE) CyclonePack(&MyCyclone, save_buffer); diff --git a/NEO.emu/src/gngeo/mamez80_interf.c b/NEO.emu/src/gngeo/mamez80_interf.c index 7758f0834..6f1cd9a7e 100644 --- a/NEO.emu/src/gngeo/mamez80_interf.c +++ b/NEO.emu/src/gngeo/mamez80_interf.c @@ -133,7 +133,7 @@ int mame_z80_irq_callback(int a) return 0; } -void cpu_z80_mkstate(gzFile gzf,int mode) { +void cpu_z80_mkstate(Stream *gzf,int mode) { mkstate_data(gzf, z80_stateData(), z80_stateDataSize, mode); mkstate_data(gzf, mame_z80mem, 0x10000, mode); if (mode==STREAD) { diff --git a/NEO.emu/src/gngeo/musashi_interf.cc b/NEO.emu/src/gngeo/musashi_interf.cc index d519661d4..236640aea 100644 --- a/NEO.emu/src/gngeo/musashi_interf.cc +++ b/NEO.emu/src/gngeo/musashi_interf.cc @@ -368,7 +368,7 @@ CLINK Uint32 cpu_68k_getpc(void) return mm68k.pc; } -CLINK void cpu_68k_mkstate(gzFile gzf,int mode) +CLINK void cpu_68k_mkstate(Stream *gzf,int mode) { mkstate_data(gzf, &mm68k.cycleCount, sizeof(mm68k.cycleCount), mode); if(mode == STWRITE) diff --git a/NEO.emu/src/gngeo/pd4990a.c b/NEO.emu/src/gngeo/pd4990a.c index 22480b0c3..501c10532 100644 --- a/NEO.emu/src/gngeo/pd4990a.c +++ b/NEO.emu/src/gngeo/pd4990a.c @@ -98,7 +98,7 @@ void pd4990a_init_save_state(void) { } #endif -void pd4990a_mkstate(gzFile gzf,int mode) +void pd4990a_mkstate(Stream *gzf,int mode) { mkstate_data(gzf, &pd4990a, sizeof(pd4990a), mode); } diff --git a/NEO.emu/src/gngeo/state.c b/NEO.emu/src/gngeo/state.c deleted file mode 100644 index bdb53674d..000000000 --- a/NEO.emu/src/gngeo/state.c +++ /dev/null @@ -1,687 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include -#endif - -//#include "SDL.h" -//#include "SDL_endian.h" -#include -#include -#include -#if defined(HAVE_LIBZ) && defined (HAVE_MMAP) -#include -#endif - -#include "memory.h" -#include "state.h" -#include "fileio.h" -#include "screen.h" -#include "sound.h" -#include "emu.h" -#include "timer.h" -//#include "streams.h" -#include - -#ifdef USE_STARSCREAM -static int m68k_flag=0x1; -#elif USE_GENERATOR68K -static int m68k_flag=0x2; -#elif USE_CYCLONE -static int m68k_flag=0x3; -#elif USE_MUSASHI -static int m68k_flag=0x4; -#endif - -#ifdef USE_RAZE -static int z80_flag=0x4; -#elif USE_MAMEZ80 -static int z80_flag=0x8; -#elif USE_DRZ80 -static int z80_flag=0xC; -#endif - -#ifdef WORDS_BIGENDIAN -static int endian_flag=0x10; -#else -static int endian_flag=0x0; -#endif - -#if defined (WII) -#define ROOTPATH "sd:/apps/gngeo/" -#elif defined (__AMIGA__) -#define ROOTPATH "/PROGDIR/data/" -#else -#define ROOTPATH "" -#endif - -#if !defined(HAVE_LIBZ) || !defined (HAVE_MMAP) -#define gzopen fopen -#define gzread(f,data,size) fread(data,size,1,f) -#define gzwrite(f,data,size) fwrite(data,size,1,f) -#define gzclose fclose -#define gzFile FILE -#define gzeof feof -#define gzseek fseek - -#endif - -static ST_REG *reglist; -static ST_MODULE st_mod[ST_MODULE_END]; -static GN_Rect buf_rect = {24, 16, 304, 224}; -static GN_Rect screen_rect = { 0, 0, 304, 224}; -//SDL_Surface *state_img_tmp; - -void cpu_68k_mkstate(gzFile gzf,int mode); -void cpu_z80_mkstate(gzFile gzf,int mode); -void ym2610_mkstate(gzFile gzf,int mode); -void timer_mkstate(gzFile gzf,int mode); -void pd4990a_mkstate(gzFile gzf,int mode); - -#if 0 -void create_state_register(ST_MODULE_TYPE module,const char *reg_name, - Uint8 num,void *data,int size,ST_DATA_TYPE type) { - ST_REG *t=(ST_REG*)calloc(1,sizeof(ST_REG)); - t->next=st_mod[module].reglist; - st_mod[module].reglist=t; - t->reg_name=strdup(reg_name); - t->data=data; - t->size=size; - t->type=type; - t->num=num; -} - -void set_pre_save_function(ST_MODULE_TYPE module,void (*func)(void)) { - st_mod[module].pre_save_state=func; -} - -void set_post_load_function(ST_MODULE_TYPE module,void (*func)(void)) { - st_mod[module].post_load_state=func; -} - -static void *find_data_by_name(ST_MODULE_TYPE module,Uint8 num,char *name) { - ST_REG *t=st_mod[module].reglist; - while(t) { - if ((!strcmp(name,t->reg_name)) && (t->num==num)) { - /* - *len=t->size; - *type=t->type; - */ - return t->data; - } - t=t->next; - } - return NULL; -} - -static int sizeof_st_type(ST_DATA_TYPE type) { - switch (type) { - case REG_UINT8: - case REG_INT8: - return 1; - case REG_UINT16: - case REG_INT16: - return 2; - case REG_UINT32: - case REG_INT32: - return 4; - } - return 0; /* never go here */ -} - -void swap_buf16_if_need(Uint8 src_endian,Uint16* buf,Uint32 size) -{ - int i; -#ifdef WORDS_BIGENDIAN - Uint8 my_endian=1; -#else - Uint8 my_endian=0; -#endif - if (my_endian!=src_endian) { - for (i=0;ipixels,304*224*2); - gzclose(gzf); - return state_img_tmp; -} - -bool load_state(char *game,int slot) { - char *st_name; -// char *st_name_len; -#ifdef EMBEDDED_FS - char *gngeo_dir="save/"; -#else - char *gngeo_dir=get_gngeo_dir(); -#endif - -#ifdef WORDS_BIGENDIAN - Uint8 my_endian=1; -#else - Uint8 my_endian=0; -#endif - - int i; - gzFile gzf; - char string[20]; - Uint8 a,num; - ST_DATA_TYPE type; - void *data; - Uint32 len; - - Uint8 endian; - Uint32 rate; - - st_name=(char*)alloca(strlen(gngeo_dir)+strlen(game)+5); - sprintf(st_name,"%s%s.%03d",gngeo_dir,game,slot); - - if ((gzf=gzopen(st_name,"rb"))==NULL) { - logMsg("%s not found\n",st_name); - return false; - } - - memset(string,0,20); - gzread(gzf,string,6); - - if (strcmp(string,"GNGST1")) { - logMsg("%s is not a valid gngeo st file\n",st_name); - gzclose(gzf); - return false; - } - - - gzread(gzf,&endian,1); - - if (my_endian!=endian) { - logMsg("This save state comme from a different endian architecture.\n" - "This is not currently supported :(\n"); - return false; - } - - gzread(gzf,&rate,4); - swap_buf32_if_need(endian,&rate,1); - -#ifdef GP2X - if (rate==0 && conf.sound) { - gn_popup_error("Failed!", - "This save state is incompatible " - "because you have sound enabled " - "and this save state don't have sound data"); - return false; - } - if (rate!=0 && conf.sound==0) { - gn_popup_error("Failed!", - "This save state is incompatible " - "because you don't have sound enabled " - "and this save state need it"); - return false; - } else if (rate!=conf.sample_rate && conf.sound) { - conf.sample_rate=rate; - close_sdl_audio(); - init_sdl_audio(); - } -#else - if (rate==0 && conf.sound) { - /* disable sound */ - conf.sound=0; - pause_audio(1); - close_sdl_audio(); - } else if (rate!=0 && conf.sound==0) { - /* enable sound */ - conf.sound=1; - conf.sample_rate=rate; - if (!conf.snd_st_reg_create) { - cpu_z80_init(); - init_sdl_audio(); - //streams_sh_start(); - YM2610_sh_start(); - conf.snd_st_reg_create=1; - } else - init_sdl_audio(); - pause_audio(0); - } else if (rate!=conf.sample_rate && conf.sound) { - conf.sample_rate=rate; - close_sdl_audio(); - init_sdl_audio(); - } -#endif - - - gzread(gzf,state_img->pixels,304*224*2); - swap_buf16_if_need(endian,state_img->pixels,304*224); - - - - while(!gzeof(gzf)) { - gzread(gzf,&a,1); /* name size */ - memset(string,0,20); - gzread(gzf,string,a); /* regname */ - gzread(gzf,&num,1); /* regname num */ - gzread(gzf,&a,1); /* module id */ - gzread(gzf,&len,4); - gzread(gzf,&type,1); - data=find_data_by_name(a,num,string); - if (data) { - gzread(gzf,data,len); - switch(type) { - case REG_UINT16: - case REG_INT16: - swap_buf16_if_need(endian,data,len>>1); - break; - case REG_UINT32: - case REG_INT32: - swap_buf32_if_need(endian,data,len>>2); - break; - case REG_INT8: - case REG_UINT8: - /* nothing */ - break; - } - } else { - /* unknow reg, ignore it*/ - logMsg("skeeping unknow reg %s\n",string); - gzseek(gzf,len,SEEK_CUR); - } - - - // /*if (a==ST_68k)*/ printf("LO %02d %20s %02x %08x \n",a,string,num,len/*,*(Uint32*)data*/); - } - gzclose(gzf); - - for(i=0;ipixels,304*224*2); - for(i=0;ireg_name,t->num,t->size/*,*(Uint32*)t->data*/); - - a=strlen(t->reg_name); - gzwrite(gzf,&a,1); /* strlen(regname) */ - gzwrite(gzf,t->reg_name,strlen(t->reg_name)); /* regname */ - gzwrite(gzf,&t->num,1); /* regname num */ - gzwrite(gzf,&i,1); /* module id */ - gzwrite(gzf,&t->size,4); - gzwrite(gzf,&t->type,1); - gzwrite(gzf,t->data,t->size); - - t=t->next; - } - } - gzclose(gzf); - return true; - -} - -#else - -static gzFile open_state(void *contextPtr, const char *st_name, int mode) { - char string[20]; - char *m=(mode==STWRITE?"wb":"rb"); - gzFile gzf; - int flags; - Uint32 rate; - - if ((gzf = gzopenHelper(contextPtr, st_name, m)) == NULL) { - logMsg("%s not found\n", st_name); - return NULL; - } - - static const char *stateSig = "GNGST3"; - - if(mode==STREAD) { - - memset(string, 0, 20); - gzread(gzf, string, 6); - - if (strcmp(string, stateSig)) { - logMsg("%s is not a valid gngeo st file", st_name); - gzclose(gzf); - return NULL; - } - - gzread(gzf, &flags, sizeof (int)); - - if (flags != (m68k_flag | z80_flag | endian_flag)) { - logMsg("This save state comes from a different endian architecture.\n" - "This is not currently supported :("); - gzclose(gzf); - return NULL; - } - } else { - int flags=m68k_flag | z80_flag | endian_flag; - gzwrite(gzf, stateSig, 6); - gzwrite(gzf, &flags, sizeof(int)); - } - return gzf; -} - -/*static gzFile open_state(char *game,int slot,int mode) { - char *st_name=(char*)alloca(strlen(getGngeoDir())+strlen(game)+5); - make_state_name(game,slot,st_name); - return open_stateWithName(st_name, mode); -}*/ - -int mkstate_data(gzFile gzf,void *data,int size,int mode) { - if (mode==STREAD) - return gzread(gzf,data,size); - return gzwrite(gzf,data,size); -} - -/*SDL_Surface *load_state_img(char *game,int slot) { - gzFile *gzf; - - if ((gzf = open_state(game, slot, STREAD)) == NULL) - return NULL; - - gzread(gzf, state_img_tmp->pixels, 304 * 224 * 2); - - - gzclose(gzf); - return state_img_tmp; -}*/ - -static void neogeo_mkstate(gzFile gzf,int mode) { - GAME_ROMS r; - memcpy(&r,&memory.rom,sizeof(GAME_ROMS)); - mkstate_data(gzf, &memory, sizeof (memory), mode); - - /* Roms info are needed (at least) for z80 bankswitch, so we need to restore - * it asap */ - if (mode==STREAD) memcpy(&memory.rom,&r,sizeof(GAME_ROMS)); - - - mkstate_data(gzf, &bankaddress, sizeof (Uint32), mode); - mkstate_data(gzf, &sram_lock, sizeof (Uint8), mode); - mkstate_data(gzf, &sound_code, sizeof (Uint8), mode); - mkstate_data(gzf, &pending_command, sizeof (Uint8), mode); - mkstate_data(gzf, &result_code, sizeof (Uint8), mode); - mkstate_data(gzf, &neogeo_frame_counter_speed, sizeof (Uint32), mode); - mkstate_data(gzf, &neogeo_frame_counter, sizeof (Uint32), mode); - cpu_68k_mkstate(gzf, mode); -#ifndef ENABLE_940T - mkstate_data(gzf, z80_bank,sizeof(Uint16)*4, mode); - cpu_z80_mkstate(gzf, mode); - ym2610_mkstate(gzf, mode); - timer_mkstate(gzf, mode); -#else -/* TODO */ -#endif - pd4990a_mkstate(gzf, mode); -} - -int save_stateWithName(void *contextPtr, const char *name) { - gzFile gzf; - - if ((gzf = open_state(contextPtr, name, STWRITE)) == NULL) - return false; - - //gzwrite(gzf, state_img->pixels, 304 * 224 * 2); - - neogeo_mkstate(gzf,STWRITE); - - gzclose(gzf); - return true; -} - -int load_stateWithName(void *contextPtr, const char *name) { - gzFile gzf; - /* Save pointers */ - Uint8 *ng_lo = memory.ng_lo; - Uint8 *fix_game_usage=memory.fix_game_usage; - Uint8 *bksw_unscramble = memory.bksw_unscramble; - int *bksw_offset=memory.bksw_offset; -// GAME_ROMS r; -// memcpy(&r,&memory.rom,sizeof(GAME_ROMS)); - - if ((gzf = open_state(contextPtr, name, STREAD))==NULL) - return false; - - //gzread(gzf,state_img_tmp->pixels,304*224*2); - - neogeo_mkstate(gzf,STREAD); - - /* Restore them */ - memory.ng_lo=ng_lo; - memory.fix_game_usage=fix_game_usage; - memory.bksw_unscramble=bksw_unscramble; - memory.bksw_offset=bksw_offset; -// memcpy(&memory.rom,&r,sizeof(GAME_ROMS)); - - cpu_68k_bankswitch(bankaddress); - - if (memory.current_vector==0) - memcpy(memory.rom.cpu_m68k.p, memory.rom.bios_m68k.p, 0x80); - else - memcpy(memory.rom.cpu_m68k.p, memory.game_vector, 0x80); - - if (memory.vid.currentpal) { - current_pal = memory.vid.pal_neo[1]; - current_pc_pal = (Uint32 *) memory.vid.pal_host[1]; - } else { - current_pal = memory.vid.pal_neo[0]; - current_pc_pal = (Uint32 *) memory.vid.pal_host[0]; - } - - if (memory.vid.currentfix) { - current_fix = memory.rom.game_sfix.p; - fix_usage = memory.fix_game_usage; - } else { - current_fix = memory.rom.bios_sfix.p; - fix_usage = memory.fix_board_usage; - } - - gzclose(gzf); - return true; -} -#endif - -#if 0 -/* neogeo state register */ -static Uint8 st_current_pal,st_current_fix; - -static void neogeo_pre_save_state(void) { - - //st_current_pal=(current_pal==memory.pal1?0:1); - //st_current_fix=(current_fix==memory.rom.bios_sfix.p?0:1); - //printf("%d %d\n",st_current_pal,st_current_fix); - -} - -static void neogeo_post_load_state(void) { - int i; - //printf("%d %d\n",st_current_pal,st_current_fix); - //current_pal=(st_current_pal==0?memory.pal1:memory.pal2); - //current_pc_pal=(Uint32 *)(st_current_pal==0?memory.pal_pc1:memory.pal_pc2); - current_fix=(st_current_fix==0?memory.rom.bios_sfix.p:memory.rom.game_sfix.p); - update_all_pal(); - -} - -void clear_state_reg(void) { - int i; - ST_REG *t,*s; - for(i=0;inext; - free(s); - } - st_mod[i].reglist=NULL; - } -} - -void neogeo_init_save_state(void) { - int i; - ST_REG *t,*s; - /*if (!state_img) - state_img=SDL_CreateRGBSurface(SDL_SWSURFACE,304, 224, 16, 0xF800, 0x7E0, 0x1F, 0); - if (!state_img_tmp) - state_img_tmp=SDL_CreateRGBSurface(SDL_SWSURFACE,304, 224, 16, 0xF800, 0x7E0, 0x1F, 0);*/ - -/* - for(i=0;inext; - free(s); - } - st_mod[i].reglist=NULL; - } -*/ - - //create_state_register(ST_NEOGEO,"vptr",1,(void *)&vptr,sizeof(Sint32),REG_INT32); - //create_state_register(ST_NEOGEO,"modulo",1,(void *)&modulo,sizeof(Sint16),REG_INT16); - create_state_register(ST_NEOGEO,"current_pal",1,(void *)&st_current_pal,sizeof(Uint8),REG_UINT8); - create_state_register(ST_NEOGEO,"current_fix",1,(void *)&st_current_fix,sizeof(Uint8),REG_UINT8); - create_state_register(ST_NEOGEO,"sram_lock",1,(void *)&sram_lock,sizeof(Uint8),REG_UINT8); - create_state_register(ST_NEOGEO,"sound_code",1,(void *)&sound_code,sizeof(Uint8),REG_UINT8); - create_state_register(ST_NEOGEO,"pending_command",1,(void *)&pending_command,sizeof(Uint8),REG_UINT8); - create_state_register(ST_NEOGEO,"result_code",1,(void *)&result_code,sizeof(Uint8),REG_UINT8); - //create_state_register(ST_NEOGEO,"sram",1,(void *)memory.sram,0x10000,REG_UINT8); - //create_state_register(ST_NEOGEO,"pal1",1,(void *)memory.pal1,0x2000,REG_UINT8); - //create_state_register(ST_NEOGEO,"pal2",1,(void *)memory.pal2,0x2000,REG_UINT8); - create_state_register(ST_NEOGEO,"video",1,(void *)memory.vid.ram,0x20000,REG_UINT8); -// create_state_register(ST_NEOGEO,"irq2enable",1,(void *)&irq2enable,sizeof(Uint16),REG_UINT16); -// create_state_register(ST_NEOGEO,"irq2start",1,(void *)&irq2start,sizeof(Uint16),REG_UINT16); -// create_state_register(ST_NEOGEO,"irq2repeat",1,(void *)&irq2repeat,sizeof(Uint16),REG_UINT16); -// create_state_register(ST_NEOGEO,"irq2control",1,(void *)&irq2control,sizeof(Uint16),REG_UINT16); -// create_state_register(ST_NEOGEO,"lastirq2line",1,(void *)&lastirq2line,sizeof(Uint16),REG_UINT16); - create_state_register(ST_NEOGEO,"fc_speed",1,(void *)&neogeo_frame_counter_speed,sizeof(Sint32),REG_INT32); - create_state_register(ST_NEOGEO,"fc",1,(void *)&neogeo_frame_counter,sizeof(Sint32),REG_INT32); - - - set_post_load_function(ST_NEOGEO,neogeo_post_load_state); - set_pre_save_function(ST_NEOGEO,neogeo_pre_save_state); - -} -#endif diff --git a/NEO.emu/src/gngeo/state.h b/NEO.emu/src/gngeo/state.h index 529c8484b..3c4623692 100644 --- a/NEO.emu/src/gngeo/state.h +++ b/NEO.emu/src/gngeo/state.h @@ -89,11 +89,12 @@ void create_state_register(ST_MODULE_TYPE module,const char *reg_name,Uint8 num, void set_pre_save_function(ST_MODULE_TYPE module,void (*func)(void)); void set_post_load_function(ST_MODULE_TYPE module,void (*func)(void)); #endif +typedef void Stream; //SDL_Surface *load_state_img(char *game,int slot); int save_stateWithName(void *contextPtr, const char *name); int load_stateWithName(void *contextPtr, const char *name); Uint32 how_many_slot(char *game); -int mkstate_data(gzFile gzf,void *data,int size,int mode); +int mkstate_data(Stream *gzf,void *data,int size,int mode); gzFile gzopenHelper(void *contextPtr, const char *filename, const char *mode); //void neogeo_init_save_state(void); diff --git a/NEO.emu/src/gngeo/timer.c b/NEO.emu/src/gngeo/timer.c index 71dc66441..c11be5d0e 100644 --- a/NEO.emu/src/gngeo/timer.c +++ b/NEO.emu/src/gngeo/timer.c @@ -139,7 +139,7 @@ void init_timer() timers[i].del_it = 1; } -void timer_mkstate(gzFile gzf,int mode) { +void timer_mkstate(Stream *gzf,int mode) { mkstate_data(gzf, &timer_count, sizeof(timer_count), mode); mkstate_data(gzf, timers, sizeof(timers), mode); } diff --git a/NEO.emu/src/gngeo/ym2610/ym2610.c b/NEO.emu/src/gngeo/ym2610/ym2610.c index 09fdad301..162eb8129 100644 --- a/NEO.emu/src/gngeo/ym2610/ym2610.c +++ b/NEO.emu/src/gngeo/ym2610/ym2610.c @@ -3315,7 +3315,7 @@ void YM2610Update_SoundTest(int p) #endif -void ym2610_mkstate(gzFile gzf,int mode) { +void ym2610_mkstate(Stream *gzf,int mode) { //mkstate_data(gzf, &YM2610, sizeof (YM2610), mode); mkstate_data(gzf, &YM2610.regs, 512, mode); diff --git a/NEO.emu/src/main/EmuMenuViews.cc b/NEO.emu/src/main/EmuMenuViews.cc index 9d5fe2e0d..bd9716532 100644 --- a/NEO.emu/src/main/EmuMenuViews.cc +++ b/NEO.emu/src/main/EmuMenuViews.cc @@ -505,16 +505,23 @@ class GameListView : public TableView, public MainAppHelper auto ctx = appContext(); std::string fileList{}; // hold concatenated list of relevant filenames for fast checking fileList.reserve(4095); // avoid initial small re-allocations - ctx.forEachInDirectoryUri(app().contentSearchPath(), - [&](auto &entry) - { - if(entry.type() == FS::file_type::directory) - return true; - if(entry.name().size() > 13) // MAME filenames follow 8.3 convention but names may have 9 characters + try + { + ctx.forEachInDirectoryUri(app().contentSearchPath(), + [&](auto &entry) + { + if(entry.type() == FS::file_type::directory) + return true; + if(entry.name().size() > 13) // MAME filenames follow 8.3 convention but names may have 9 characters + return true; + fileList += entry.name(); return true; - fileList += entry.name(); - return true; - }); + }); + } + catch(...) + { + return; + } for(const auto &entry : romlist) { ROM_DEF *drv = res_load_drv(&ctx, entry.name); @@ -681,7 +688,7 @@ class CustomMainMenuView : public MainMenuView auto gameListMenu = makeView(); if(!gameListMenu->games()) { - app().postMessage(6, true, "No games found, use \"Load Game\" command to browse to a directory with valid games."); + app().postMessage(6, true, "No content found, use \"Open Content\" command to browse to a folder with ROM archives."); return; } pushAndShow(std::move(gameListMenu), e); diff --git a/NEO.emu/src/main/Main.cc b/NEO.emu/src/main/Main.cc index 35fd99588..af938f596 100755 --- a/NEO.emu/src/main/Main.cc +++ b/NEO.emu/src/main/Main.cc @@ -21,6 +21,7 @@ #include #include #include +#include extern "C" { @@ -150,18 +151,81 @@ FS::FileString NeoSystem::stateFilename(int slot, std::string_view name) const return IG::format("{}.0{}.sta", name, saveSlotCharUpper(slot)); } -void NeoSystem::saveState(IG::CStringView path) +size_t NeoSystem::stateSize() { - auto ctx = appContext(); - if(!save_stateWithName(&ctx, path)) - return EmuSystem::throwFileWriteError(); + return saveStateSize; +} + +void NeoSystem::readState(EmuApp &app, std::span buff) +{ + /* Save pointers */ + Uint8 *ng_lo = memory.ng_lo; + Uint8 *fix_game_usage=memory.fix_game_usage; + Uint8 *bksw_unscramble = memory.bksw_unscramble; + int *bksw_offset=memory.bksw_offset; + + DynArray uncompArr; + if(hasGzipHeader(buff)) + { + uncompArr = uncompressGzipState(buff, saveStateSize); + buff = uncompArr; + } + MapIO buffIO{buff}; + if(!openState(buffIO, STREAD)) + throw std::runtime_error("Invalid state data"); + makeState(buffIO, STREAD); + + /* Restore them */ + memory.ng_lo=ng_lo; + memory.fix_game_usage=fix_game_usage; + memory.bksw_unscramble=bksw_unscramble; + memory.bksw_offset=bksw_offset; + + cpu_68k_bankswitch(bankaddress); + if (memory.current_vector==0) + memcpy(memory.rom.cpu_m68k.p, memory.rom.bios_m68k.p, 0x80); + else + memcpy(memory.rom.cpu_m68k.p, memory.game_vector, 0x80); + if (memory.vid.currentpal) + { + current_pal = memory.vid.pal_neo[1]; + current_pc_pal = (Uint32 *) memory.vid.pal_host[1]; + } + else + { + current_pal = memory.vid.pal_neo[0]; + current_pc_pal = (Uint32 *) memory.vid.pal_host[0]; + } + if (memory.vid.currentfix) + { + current_fix = memory.rom.game_sfix.p; + fix_usage = memory.fix_game_usage; + } + else + { + current_fix = memory.rom.bios_sfix.p; + fix_usage = memory.fix_board_usage; + } } -void NeoSystem::loadState(EmuApp &app, IG::CStringView path) +size_t NeoSystem::writeState(std::span buff, SaveStateFlags flags) { - auto ctx = app.appContext(); - if(!load_stateWithName(&ctx, path)) - return EmuSystem::throwFileReadError(); + if(flags.uncompressed) + { + MapIO buffIO{buff}; + openState(buffIO, STWRITE); + makeState(buffIO, STWRITE); + return buffIO.tell(); + } + else + { + assert(saveStateSize); + auto stateArr = DynArray{saveStateSize}; + MapIO buffIO{stateArr}; + openState(buffIO, STWRITE); + makeState(buffIO, STWRITE); + return compressGzip(buff, stateArr, Z_DEFAULT_COMPRESSION); + } } static auto nvramPath(EmuApp &app) @@ -279,6 +343,8 @@ void NeoSystem::loadContent(IO &, EmuSystemCreateParams, OnLoadProgressDelegate FileUtils::readFromUri(ctx, sharedMemcardPath, {memory.memcard, 0x800}); FileUtils::writeToUri(ctx, memcardPath, {memory.memcard, 0x800}); } + static constexpr size_t maxStateSize = 0x60000; + saveStateSize = writeState(std::span{std::make_unique(maxStateSize).get(), maxStateSize}, {.uncompressed = true}); } void NeoSystem::configAudioRate(FrameTime outputFrameTime, int outputRate) diff --git a/NEO.emu/src/main/MainSystem.hh b/NEO.emu/src/main/MainSystem.hh index 1803d7a26..e99a9120a 100644 --- a/NEO.emu/src/main/MainSystem.hh +++ b/NEO.emu/src/main/MainSystem.hh @@ -39,6 +39,7 @@ class NeoSystem final: public EmuSystem public: static constexpr auto pixFmt = IG::PIXEL_FMT_RGB565; static constexpr int FBResX = 352; + size_t saveStateSize{}; FileIO nvramFileIO; FileIO memcardFileIO; GN_Surface sdlSurf{}; @@ -66,8 +67,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".sta"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags = {}); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); @@ -91,6 +93,9 @@ public: using MainSystem = NeoSystem; +bool openState(MapIO &io, int mode); +void makeState(MapIO &io, int mode); + } constexpr EmuEx::EmuSystem::BackupMemoryDirtyFlags SRAM_DIRTY_BIT = IG::bit(0); diff --git a/NEO.emu/src/main/state.cc b/NEO.emu/src/main/state.cc new file mode 100644 index 000000000..daff549c6 --- /dev/null +++ b/NEO.emu/src/main/state.cc @@ -0,0 +1,134 @@ +/* This file is part of NEO.emu. + + NEO.emu 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 3 of the License, or + (at your option) any later version. + + NEO.emu 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 NEO.emu. If not, see */ + +#include +#include +#include + +extern "C" +{ + #include + #include + #include + #include + + void cpu_68k_mkstate(Stream *gzf,int mode); + void cpu_z80_mkstate(Stream *gzf,int mode); + void ym2610_mkstate(Stream *gzf,int mode); + void timer_mkstate(Stream *gzf,int mode); + void pd4990a_mkstate(Stream *gzf,int mode); +} + +#ifdef USE_STARSCREAM +static int m68k_flag=0x1; +#elif USE_GENERATOR68K +static int m68k_flag=0x2; +#elif USE_CYCLONE +static int m68k_flag=0x3; +#elif USE_MUSASHI +static int m68k_flag=0x4; +#endif + +#ifdef USE_RAZE +static int z80_flag=0x4; +#elif USE_MAMEZ80 +static int z80_flag=0x8; +#elif USE_DRZ80 +static int z80_flag=0xC; +#endif + +#ifdef WORDS_BIGENDIAN +static int endian_flag=0x10; +#else +static int endian_flag=0x0; +#endif + +using namespace IG; + +namespace EmuEx +{ + +bool openState(MapIO &io, int mode) +{ + static const char *stateSig = "GNGST3"; + if(mode==STREAD) + { + char string[20]{}; + io.read(string, 6); + + if(strcmp(string, stateSig)) + { + logErr("%s is not a valid gngeo state header", string); + return false; + } + + int flags = io.get(); + + if (flags != (m68k_flag | z80_flag | endian_flag)) + { + logMsg("This save state comes from a different endian architecture.\n" + "This is not currently supported :("); + return false; + } + } + else + { + int flags = m68k_flag | z80_flag | endian_flag; + io.write(stateSig, 6); + io.put(flags); + } + return true; +} + +static int mkstate_data(MapIO &io, void *data, int size, int mode) +{ + if (mode==STREAD) + return io.read(data, size); + return io.write(data, size); +} + +void makeState(MapIO &io, int mode) +{ + GAME_ROMS r; + memcpy(&r,&memory.rom,sizeof(GAME_ROMS)); + mkstate_data(io, &memory, sizeof (memory), mode); + + /* Roms info are needed (at least) for z80 bankswitch, so we need to restore + * it asap */ + if (mode==STREAD) memcpy(&memory.rom,&r,sizeof(GAME_ROMS)); + + + mkstate_data(io, &bankaddress, sizeof (Uint32), mode); + mkstate_data(io, &sram_lock, sizeof (Uint8), mode); + mkstate_data(io, &sound_code, sizeof (Uint8), mode); + mkstate_data(io, &pending_command, sizeof (Uint8), mode); + mkstate_data(io, &result_code, sizeof (Uint8), mode); + mkstate_data(io, &neogeo_frame_counter_speed, sizeof (Uint32), mode); + mkstate_data(io, &neogeo_frame_counter, sizeof (Uint32), mode); + cpu_68k_mkstate(&io, mode); + mkstate_data(io, z80_bank,sizeof(Uint16)*4, mode); + cpu_z80_mkstate(&io, mode); + ym2610_mkstate(&io, mode); + timer_mkstate(&io, mode); + pd4990a_mkstate(&io, mode); +} + +} + +CLINK int mkstate_data(Stream *ptr, void *data, int size, int mode) +{ + return EmuEx::mkstate_data(*static_cast(ptr), data, size, mode); +} + diff --git a/NES.emu/src/main/EmuFileIO.cc b/NES.emu/src/main/EmuFileIO.cc index 9a1668037..3c9810c4d 100644 --- a/NES.emu/src/main/EmuFileIO.cc +++ b/NES.emu/src/main/EmuFileIO.cc @@ -21,7 +21,10 @@ namespace EmuEx { EmuFileIO::EmuFileIO(IG::IO &srcIO): - io{srcIO} + EmuFileIO{IG::MapIO{srcIO}} {} + +EmuFileIO::EmuFileIO(IG::MapIO srcIO): + io{std::move(srcIO)} { if(!io) [[unlikely]] { @@ -31,6 +34,12 @@ EmuFileIO::EmuFileIO(IG::IO &srcIO): int EmuFileIO::fgetc() { return IG::fgetc(io); } +int EmuFileIO::fputc(int c) +{ + io.put(c); + return c; +} + size_t EmuFileIO::_fread(const void *ptr, size_t bytes) { ssize_t ret = io.read((void*)ptr, bytes); @@ -39,6 +48,12 @@ size_t EmuFileIO::_fread(const void *ptr, size_t bytes) return ret; } +void EmuFileIO::fwrite(const void *ptr, size_t bytes) +{ + if(io.write(ptr, bytes) == (ssize_t)bytes) + failbit = true; +} + int EmuFileIO::fseek(long int offset, int origin) { return IG::fseek(io, offset, origin); diff --git a/NES.emu/src/main/EmuFileIO.hh b/NES.emu/src/main/EmuFileIO.hh index cab2b56c3..899be0b27 100644 --- a/NES.emu/src/main/EmuFileIO.hh +++ b/NES.emu/src/main/EmuFileIO.hh @@ -26,27 +26,26 @@ class IO; namespace EmuEx { -class EmuFileIO final : public EMUFILE { -protected: - IG::MapIO io{}; - +class EmuFileIO final : public EMUFILE +{ public: + IG::MapIO io; EmuFileIO(IG::IO &); + EmuFileIO(IG::MapIO); ~EmuFileIO() = default; FILE *get_fp() final { return nullptr; } EMUFILE* memwrap() final { return nullptr; } void truncate(size_t length) final {} int fprintf(const char *format, ...) final { return 0; }; int fgetc() final; - int fputc(int c) final { return 0; } + int fputc(int c) final; size_t _fread(const void *ptr, size_t bytes) final; - void fwrite(const void *ptr, size_t bytes) final { failbit = true; } + void fwrite(const void *ptr, size_t bytes) final; int fseek(long int offset, int origin) final; long int ftell() final; size_t size() final { return io.size(); } void fflush() final {} - }; } diff --git a/NES.emu/src/main/Main.cc b/NES.emu/src/main/Main.cc index 688acf9f2..a7ed48ba8 100755 --- a/NES.emu/src/main/Main.cc +++ b/NES.emu/src/main/Main.cc @@ -129,16 +129,19 @@ FS::FileString NesSystem::stateFilename(int slot, std::string_view name) const return IG::format("{}.fc{}", name, saveSlotCharNES(slot)); } -void NesSystem::saveState(IG::CStringView path) +void NesSystem::readState(EmuApp &app, std::span buff) { - if(!FCEUI_SaveState(path)) - EmuSystem::throwFileWriteError(); + EmuFileIO memFile{buff}; + if(!FCEUSS_LoadFP(&memFile, SSLOADPARAM_NOBACKUP)) + throw std::runtime_error("Invalid state data"); } -void NesSystem::loadState(EmuApp &app, IG::CStringView path) +size_t NesSystem::writeState(std::span buff, SaveStateFlags flags) { - if(!FCEUI_LoadState(path)) - EmuSystem::throwFileReadError(); + assert(buff.size() >= saveStateSize); + EmuFileIO memFile{buff}; + FCEUSS_SaveMS(&memFile, -1); + return memFile.ftell(); } void NesSystem::loadBackupMemory(EmuApp &app) @@ -379,8 +382,10 @@ void NesSystem::loadContent(IO &io, EmuSystemCreateParams, OnLoadProgressDelegat FCEUI_ListCheats(cheatCallback, 0); if(fceuCheats) logMsg("%d total cheats", fceuCheats); - setupNESInputPorts(); + EMUFILE_MEMORY stateMemFile; + FCEUSS_SaveMS(&stateMemFile, 0); + saveStateSize = stateMemFile.get_vec()->size(); } bool NesSystem::onVideoRenderFormatChange(EmuVideo &video, IG::PixelFormat fmt) diff --git a/NES.emu/src/main/MainSystem.hh b/NES.emu/src/main/MainSystem.hh index 7cec18e07..100914a70 100644 --- a/NES.emu/src/main/MainSystem.hh +++ b/NES.emu/src/main/MainSystem.hh @@ -78,6 +78,7 @@ class NesSystem final: public EmuSystem public: using PalArray = std::array; + size_t saveStateSize{}; ESI nesInputPortDev[2]{SI_UNSET, SI_UNSET}; uint32 padData{}; uint32 zapperData[3]{}; @@ -129,8 +130,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".fcs"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize() { return saveStateSize; } + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/NGP.emu/src/main/Main.cc b/NGP.emu/src/main/Main.cc index 290668f0c..4192ed3c5 100755 --- a/NGP.emu/src/main/Main.cc +++ b/NGP.emu/src/main/Main.cc @@ -70,17 +70,9 @@ FS::FileString NgpSystem::stateFilename(int slot, std::string_view name) const return stateFilenameMDFN(*MDFNGameInfo, slot, name, 'a', noMD5InFilenames); } -void NgpSystem::saveState(IG::CStringView path) -{ - if(!MDFNI_SaveState(path, 0, 0, 0, 0)) - throwFileWriteError(); -} - -void NgpSystem::loadState(EmuApp &, IG::CStringView path) -{ - if(!MDFNI_LoadState(path, 0)) - throwFileReadError(); -} +size_t NgpSystem::stateSize() { return stateSizeMDFN(); } +void NgpSystem::readState(EmuApp &app, std::span buff) { readStateMDFN(app, buff); } +size_t NgpSystem::writeState(std::span buff, SaveStateFlags flags) { return writeStateMDFN(buff, flags); } static FS::PathString saveFilename(const EmuApp &app) { diff --git a/NGP.emu/src/main/MainSystem.hh b/NGP.emu/src/main/MainSystem.hh index 97400ee3f..8c9273eaf 100644 --- a/NGP.emu/src/main/MainSystem.hh +++ b/NGP.emu/src/main/MainSystem.hh @@ -54,8 +54,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".mca"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/PCE.emu/src/main/Main.cc b/PCE.emu/src/main/Main.cc index ef8437225..2ac32d021 100755 --- a/PCE.emu/src/main/Main.cc +++ b/PCE.emu/src/main/Main.cc @@ -237,17 +237,9 @@ void PceSystem::reset(EmuApp &, ResetMode mode) mdfnGameInfo.DoSimpleCommand(MDFN_MSC_RESET); } -void PceSystem::saveState(IG::CStringView path) -{ - if(!MDFNI_SaveState(path, 0, 0, 0, 0)) - throwFileWriteError(); -} - -void PceSystem::loadState(EmuApp &, CStringView path) -{ - if(!MDFNI_LoadState(path, 0)) - throwFileReadError(); -} +size_t PceSystem::stateSize() { return stateSizeMDFN(); } +void PceSystem::readState(EmuApp &app, std::span buff) { readStateMDFN(app, buff); } +size_t PceSystem::writeState(std::span buff, SaveStateFlags flags) { return writeStateMDFN(buff, flags); } double PceSystem::videoAspectRatioScale() const { diff --git a/PCE.emu/src/main/MainSystem.hh b/PCE.emu/src/main/MainSystem.hh index 1bf941ecf..5100d5230 100644 --- a/PCE.emu/src/main/MainSystem.hh +++ b/PCE.emu/src/main/MainSystem.hh @@ -142,8 +142,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".mca"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/Saturn.emu/src/main/Main.cc b/Saturn.emu/src/main/Main.cc index bb6e47104..daf9ad158 100755 --- a/Saturn.emu/src/main/Main.cc +++ b/Saturn.emu/src/main/Main.cc @@ -58,9 +58,6 @@ CDInterface *CDCoreList[] = #define SNDCORE_IMAGINE 1 -SaturnApp::SaturnApp(ApplicationInitParams initParams, ApplicationContext &ctx): - EmuApp{initParams, ctx}, saturnSystem{ctx} {} - // EmuFramework is in charge of audio setup & parameters static int SNDImagineInit() { logMsg("called sound core init"); return 0; } static void SNDImagineDeInit() {} @@ -151,6 +148,9 @@ static EmuAudio *emuAudio{}; static EmuVideo *emuVideo{}; PerPad_struct *pad[2]; +SaturnApp::SaturnApp(ApplicationInitParams initParams, ApplicationContext &ctx): + EmuApp{initParams, ctx}, saturnSystem{ctx} {} + static bool hasCDExtension(std::string_view name) { return IG::endsWithAnyCaseless(name, ".cue", ".iso", ".bin"); @@ -258,17 +258,9 @@ FS::FileString SaturnSystem::stateFilename(int slot, std::string_view name) cons return IG::format("{}.0{}.yss", name, saveSlotCharUpper(slot)); } -void SaturnSystem::saveState(IG::CStringView path) -{ - if(YabSaveState(path) != 0) - throwFileWriteError(); -} - -void SaturnSystem::loadState(EmuApp &, IG::CStringView path) -{ - if(YabLoadState(path) != 0) - throwFileReadError(); -} +size_t SaturnSystem::stateSize() { return 0; } +void SaturnSystem::readState(EmuApp &app, std::span buff) {} +size_t SaturnSystem::writeState(std::span buff, SaveStateFlags flags) { return 0; } void SaturnSystem::onFlushBackupMemory(EmuApp &, BackupMemoryDirtyFlags) { diff --git a/Saturn.emu/src/main/MainSystem.hh b/Saturn.emu/src/main/MainSystem.hh index 20c76c0ad..5105694b0 100644 --- a/Saturn.emu/src/main/MainSystem.hh +++ b/Saturn.emu/src/main/MainSystem.hh @@ -41,8 +41,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".yss"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/Snes9x/1.43/build.mk b/Snes9x/1.43/build.mk index b91b9fd2e..d560c8c7e 100644 --- a/Snes9x/1.43/build.mk +++ b/Snes9x/1.43/build.mk @@ -10,7 +10,6 @@ CPPFLAGS += \ -DHAVE_STRINGS_H \ -DVAR_CYCLES \ -DRIGHTSHIFT_IS_SAR \ --DZLIB \ -DUSE_OPENGL \ -DCPU_SHUTDOWN \ -DSPC700_SHUTDOWN \ diff --git a/Snes9x/src/main/Main.cc b/Snes9x/src/main/Main.cc index 56640a391..aa2e72216 100755 --- a/Snes9x/src/main/Main.cc +++ b/Snes9x/src/main/Main.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -108,20 +109,65 @@ static FS::PathString sramFilename(EmuApp &app) return app.contentSaveFilePath(".srm"); } -void Snes9xSystem::saveState(IG::CStringView path) +size_t Snes9xSystem::stateSize() { - if(!S9xFreezeGame(path)) - return throwFileWriteError(); + return saveStateSize; } -void Snes9xSystem::loadState(EmuApp &, IG::CStringView path) +#ifdef SNES9X_VERSION_1_4 +static uint32 S9xFreezeSize() { - if(S9xUnfreezeGame(path)) + DynArray arr{0x100000}; + auto stream = MapIO{arr}.toFileStream("wb"); + S9xFreezeToStream(stream); + return ftell(stream); +} +#endif + +static int unfreezeStateFrom(std::span buff) +{ + #ifndef SNES9X_VERSION_1_4 + return S9xUnfreezeGameMem(buff.data(), buff.size()); + #else + return S9xUnfreezeFromStream(MapIO{buff}.toFileStream("rb")); + #endif +} + +void Snes9xSystem::readState(EmuApp &, std::span buff) +{ + DynArray uncompArr; + if(hasGzipHeader(buff)) + { + uncompArr = uncompressGzipState(buff, saveStateSize); + buff = uncompArr; + } + if(!unfreezeStateFrom(buff)) + throw std::runtime_error("Invalid state data"); + IPPU.RenderThisFrame = TRUE; +} + +static void freezeStateTo(std::span buff) +{ + #ifndef SNES9X_VERSION_1_4 + S9xFreezeGameMem(buff.data(), buff.size()); + #else + S9xFreezeToStream(MapIO{buff}.toFileStream("wb")); + #endif +} + +size_t Snes9xSystem::writeState(std::span buff, SaveStateFlags flags) +{ + if(flags.uncompressed) { - IPPU.RenderThisFrame = TRUE; + freezeStateTo(buff); + return saveStateSize; } else - return throwFileReadError(); + { + auto uncompArr = DynArray(saveStateSize); + freezeStateTo(uncompArr); + return compressGzip(buff, uncompArr, Z_DEFAULT_COMPRESSION); + } } void Snes9xSystem::loadBackupMemory(EmuApp &app) @@ -242,6 +288,7 @@ void Snes9xSystem::loadContent(IO &io, EmuSystemCreateParams, OnLoadProgressDele } } setupSNESInput(EmuApp::get(appContext()).defaultVController()); + saveStateSize = S9xFreezeSize(); IPPU.RenderThisFrame = TRUE; } diff --git a/Snes9x/src/main/MainSystem.hh b/Snes9x/src/main/MainSystem.hh index d3c9325af..8aef7f1eb 100644 --- a/Snes9x/src/main/MainSystem.hh +++ b/Snes9x/src/main/MainSystem.hh @@ -58,6 +58,7 @@ public: std::string satDir{optionUserPathContentToken}; std::string sufamiBiosPath; std::string bsxBiosPath; + size_t saveStateSize{}; #ifndef SNES9X_VERSION_1_4 int snesInputPort = SNES_AUTO_INPUT; int snesActiveInputPort = SNES_JOYPAD; @@ -119,8 +120,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const; - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/Snes9x/src/main/S9XApi.cc b/Snes9x/src/main/S9XApi.cc index 56fb7cb4e..61d42f84c 100755 --- a/Snes9x/src/main/S9XApi.cc +++ b/Snes9x/src/main/S9XApi.cc @@ -10,6 +10,7 @@ #include #include "MainSystem.hh" #include +#include #ifndef SNES9X_VERSION_1_4 #include #include diff --git a/Swan.emu/src/main/Main.cc b/Swan.emu/src/main/Main.cc index 3c7c3b015..e0ecd57c1 100755 --- a/Swan.emu/src/main/Main.cc +++ b/Swan.emu/src/main/Main.cc @@ -67,17 +67,9 @@ FS::FileString WsSystem::stateFilename(int slot, std::string_view name) const return stateFilenameMDFN(*MDFNGameInfo, slot, name, 'a', noMD5InFilenames); } -void WsSystem::saveState(IG::CStringView path) -{ - if(!MDFNI_SaveState(path, 0, 0, 0, 0)) - throwFileWriteError(); -} - -void WsSystem::loadState(EmuApp &, IG::CStringView path) -{ - if(!MDFNI_LoadState(path, 0)) - throwFileReadError(); -} +size_t WsSystem::stateSize() { return stateSizeMDFN(); } +void WsSystem::readState(EmuApp &app, std::span buff) { readStateMDFN(app, buff); } +size_t WsSystem::writeState(std::span buff, SaveStateFlags flags) { return writeStateMDFN(buff, flags); } void WsSystem::loadBackupMemory(EmuApp &app) { diff --git a/Swan.emu/src/main/MainSystem.hh b/Swan.emu/src/main/MainSystem.hh index 867c863a3..08846715e 100644 --- a/Swan.emu/src/main/MainSystem.hh +++ b/Swan.emu/src/main/MainSystem.hh @@ -124,8 +124,9 @@ public: [[gnu::hot]] void runFrame(EmuSystemTaskContext task, EmuVideo *video, EmuAudio *audio); FS::FileString stateFilename(int slot, std::string_view name) const; std::string_view stateFilenameExt() const { return ".mca"; } - void loadState(EmuApp &, CStringView uri); - void saveState(CStringView path); + size_t stateSize(); + void readState(EmuApp &, std::span buff); + size_t writeState(std::span buff, SaveStateFlags); bool readConfig(ConfigType, MapIO &, unsigned key, size_t readSize); void writeConfig(ConfigType, FileIO &); void reset(EmuApp &, ResetMode mode); diff --git a/imagine/include/imagine/fs/ArchiveFS.hh b/imagine/include/imagine/fs/ArchiveFS.hh index f2ba8c673..715a2837b 100644 --- a/imagine/include/imagine/fs/ArchiveFS.hh +++ b/imagine/include/imagine/fs/ArchiveFS.hh @@ -39,6 +39,7 @@ public: using difference_type = ptrdiff_t; using pointer = value_type*; using reference = value_type&; + struct Sentinel {}; constexpr ArchiveIterator() = default; ArchiveIterator(CStringView path); @@ -50,22 +51,23 @@ public: ArchiveEntry& operator*(); ArchiveEntry* operator->(); void operator++(); - bool operator==(ArchiveIterator const &rhs) const; + bool operator==(Sentinel) const { return !hasEntry(); } void rewind(); - bool hasEntry() const { return (bool)impl; } + bool hasEntry() const { return impl.get() && impl->hasEntry(); } + bool hasArchive() const { return impl.get() && impl->hasArchive(); } private: std::shared_ptr impl; }; -static const ArchiveIterator &begin(const ArchiveIterator &iter) +static const auto &begin(const ArchiveIterator &iter) { return iter; } -static ArchiveIterator end(const ArchiveIterator &) +static auto end(const ArchiveIterator &) { - return {}; + return ArchiveIterator::Sentinel{}; } ArchiveIO fileFromArchive(CStringView archivePath, std::string_view filePath); diff --git a/imagine/include/imagine/gui/NavView.hh b/imagine/include/imagine/gui/NavView.hh index 377745bde..ae5b155d6 100644 --- a/imagine/include/imagine/gui/NavView.hh +++ b/imagine/include/imagine/gui/NavView.hh @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -77,8 +78,7 @@ public: void setRotateLeftButton(bool on); protected: - std::unique_ptr gradientStops; - size_t gradientStopsSize{}; + DynArray gradientStops; Gfx::IQuads selectQuad; Gfx::VertexBuffer bgVerts; Gfx::ITexQuads buttonQuads; diff --git a/imagine/include/imagine/io/ArchiveIO.hh b/imagine/include/imagine/io/ArchiveIO.hh index 4bf4e86c9..a74890d8a 100644 --- a/imagine/include/imagine/io/ArchiveIO.hh +++ b/imagine/include/imagine/io/ArchiveIO.hh @@ -53,6 +53,7 @@ public: void reset(ArchiveIO io); bool readNextEntry(); bool hasEntry() const; + bool hasArchive() const { return arch.get(); } void rewind(); struct archive* archive() const { return arch.get(); } diff --git a/imagine/include/imagine/io/IO.hh b/imagine/include/imagine/io/IO.hh index f039fbc2c..21362da4f 100644 --- a/imagine/include/imagine/io/IO.hh +++ b/imagine/include/imagine/io/IO.hh @@ -55,6 +55,8 @@ public: using BufferMode = IOBufferMode; using SeekMode = IOSeekMode; + IO(IOBuffer buff): IOVariant{std::in_place_type, std::move(buff)} {} + // core API ssize_t read(void *buff, size_t bytes, std::optional offset = {}); ssize_t write(const void *buff, size_t bytes, std::optional offset = {}); diff --git a/imagine/src/io/IOUtils.hh b/imagine/include/imagine/io/IOUtils-impl.hh similarity index 89% rename from imagine/src/io/IOUtils.hh rename to imagine/include/imagine/io/IOUtils-impl.hh index d7ffd52a9..3cc90d386 100644 --- a/imagine/src/io/IOUtils.hh +++ b/imagine/include/imagine/io/IOUtils-impl.hh @@ -15,8 +15,7 @@ You should have received a copy of the GNU General Public License along with Imagine. If not, see */ -#include -#include +#include namespace IG { @@ -43,7 +42,7 @@ static IOBuffer makeBufferCopy(auto &io) { auto size = io.size(); auto buff = std::make_unique(size); - if(io.read(buff.get(), size) != (ssize_t)size) + if(io.read(buff.get(), size, 0) != (ssize_t)size) { return {}; } @@ -173,4 +172,15 @@ FILE *IOUtils::toFileStream(const char *opentype) return f; } +inline auto transformOffsetToAbsolute(IOSeekMode mode, auto offset, auto startPos, auto endPos, auto currentPos) +{ + switch(mode) + { + case IOSeekMode::Set: return offset + startPos; + case IOSeekMode::End: return offset + endPos; + case IOSeekMode::Cur: return offset + currentPos; + } + bug_unreachable("IOSeekMode == %d", (int)mode); +} + } diff --git a/imagine/include/imagine/io/MapIO.hh b/imagine/include/imagine/io/MapIO.hh index e1a694f38..e827d8e77 100644 --- a/imagine/include/imagine/io/MapIO.hh +++ b/imagine/include/imagine/io/MapIO.hh @@ -36,6 +36,7 @@ public: constexpr MapIO() = default; MapIO(IOBuffer buff): buff{std::move(buff)} {} + MapIO(std::span buff): MapIO{IOBuffer{buff}} {} explicit MapIO(Readable auto &&io): MapIO{io.buffer(BufferMode::Release)} {} explicit MapIO(Readable auto &io): MapIO{io.buffer(BufferMode::Direct)} {} ssize_t read(void *buff, size_t bytes, std::optional offset = {}); diff --git a/imagine/include/imagine/io/PosixFileIO.hh b/imagine/include/imagine/io/PosixFileIO.hh index fe017fb9a..f72fda1a6 100644 --- a/imagine/include/imagine/io/PosixFileIO.hh +++ b/imagine/include/imagine/io/PosixFileIO.hh @@ -44,6 +44,8 @@ public: PosixFileIO(UniqueFileDescriptor fd, OpenFlags); PosixFileIO(CStringView path, AccessHint access, OpenFlags oFlags = {}); PosixFileIO(CStringView path, OpenFlags oFlags = {}); + PosixFileIO(PosixIO); + PosixFileIO(MapIO); ssize_t read(void *buff, size_t bytes, std::optional offset = {}); ssize_t write(const void *buff, size_t bytes, std::optional offset = {}); std::span map(); diff --git a/imagine/include/imagine/util/bit.hh b/imagine/include/imagine/util/bit.hh index 6d0e33a1f..5a355cc22 100755 --- a/imagine/include/imagine/util/bit.hh +++ b/imagine/include/imagine/util/bit.hh @@ -18,6 +18,8 @@ #include #include #include +#include +#include namespace IG { @@ -73,6 +75,11 @@ constexpr bool isBitMaskSet(BitSet auto x, BitSet auto mask) return (x & mask) == mask; //AND mask, if the result equals mask, all bits match } +constexpr auto addressAsBytes(auto &v) +{ + return std::bit_cast>(&v); +} + constexpr int ctz(unsigned int x) { return __builtin_ctz(x); diff --git a/imagine/include/imagine/util/memory/DynArray.hh b/imagine/include/imagine/util/memory/DynArray.hh new file mode 100644 index 000000000..96b30a482 --- /dev/null +++ b/imagine/include/imagine/util/memory/DynArray.hh @@ -0,0 +1,60 @@ +#pragma once + +/* This file is part of Imagine. + + Imagine 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 3 of the License, or + (at your option) any later version. + + Imagine 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 Imagine. If not, see */ + +#include +#include + +namespace IG +{ + +// A simple wrapper around an array unique_ptr & size with utility functions + +template +class DynArray +{ +public: + constexpr DynArray() = default; + constexpr explicit DynArray(size_t size): + ptr{std::make_unique(size)}, + size_{size} {} + + constexpr T *data() const { return ptr.get(); } + constexpr size_t size() const { return size_; } + constexpr T& operator[] (size_t idx) { return data()[idx]; } + constexpr const T& operator[] (size_t idx) const { return data()[idx]; } + constexpr auto begin() { return data(); } + constexpr auto end() { return data() + size(); } + constexpr auto begin() const { return data(); } + constexpr auto end() const { return data() + size(); } + constexpr std::span span() const { return {data(), size()}; } + constexpr operator std::span() const { return span(); } + auto reset(size_t size) { *this = DynArray{size}; } + auto release() { return ptr.release(); } + + constexpr void trim(size_t smallerSize) + { + if(smallerSize < size_) + size_ = smallerSize; + } + +private: + std::unique_ptr ptr; + size_t size_{}; +}; + + +} diff --git a/imagine/include/imagine/util/span.hh b/imagine/include/imagine/util/span.hh new file mode 100644 index 000000000..aa5756ae2 --- /dev/null +++ b/imagine/include/imagine/util/span.hh @@ -0,0 +1,35 @@ +#pragma once + +/* This file is part of Imagine. + + Imagine 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 3 of the License, or + (at your option) any later version. + + Imagine 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 Imagine. If not, see */ + +#include +#include +#include + +namespace IG +{ + +inline std::span asBytes(auto &v) +{ + return {reinterpret_cast(&v), sizeof(v)}; +} + +inline std::span asWritableBytes(auto &v) +{ + return {reinterpret_cast(&v), sizeof(v)}; +} + +} diff --git a/imagine/include/imagine/util/zlib.hh b/imagine/include/imagine/util/zlib.hh new file mode 100644 index 000000000..37d360173 --- /dev/null +++ b/imagine/include/imagine/util/zlib.hh @@ -0,0 +1,58 @@ +#pragma once + +/* This file is part of Imagine. + + Imagine 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 3 of the License, or + (at your option) any later version. + + Imagine 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 Imagine. If not, see */ + +#include +#include +#include + +namespace IG +{ + +inline size_t compressGzip(std::span dest, std::span src, int level) +{ + z_stream s{}; + s.avail_in = src.size(); + s.next_in = const_cast(src.data()); + s.avail_out = dest.size(); + s.next_out = dest.data(); + deflateInit2(&s, level, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY); + deflate(&s, Z_FINISH); + deflateEnd(&s); + return s.total_out; +} + +inline size_t uncompressGzip(std::span dest, std::span src) +{ + z_stream s{}; + s.avail_in = src.size(); + s.next_in = const_cast(src.data()); + s.avail_out = dest.size(); + s.next_out = dest.data(); + inflateInit2(&s, MAX_WBITS + 16); + auto res = inflate(&s, Z_FINISH); + inflateEnd(&s); + if(res != Z_STREAM_END) + return 0; + return s.total_out; +} + +inline bool hasGzipHeader(std::span buff) +{ + return buff.size() > 10 && buff[0] == 0x1F && buff[1] == 0x8B; +} + +} diff --git a/imagine/src/fs/ArchiveFS.cc b/imagine/src/fs/ArchiveFS.cc index a50462cd7..74c04c29d 100644 --- a/imagine/src/fs/ArchiveFS.cc +++ b/imagine/src/fs/ArchiveFS.cc @@ -58,14 +58,8 @@ ArchiveEntry* ArchiveIterator::operator->() void ArchiveIterator::operator++() { - assumeExpr(impl); // incrementing end-iterator is undefined - if(!impl->readNextEntry()) - impl.reset(); -} - -bool ArchiveIterator::operator==(ArchiveIterator const &rhs) const -{ - return impl == rhs.impl; + assumeExpr(impl->hasEntry()); // incrementing end-iterator is undefined + impl->readNextEntry(); } void ArchiveIterator::rewind() diff --git a/imagine/src/gui/NavView.cc b/imagine/src/gui/NavView.cc index b0b5fe1d1..78f9c1d72 100644 --- a/imagine/src/gui/NavView.cc +++ b/imagine/src/gui/NavView.cc @@ -218,9 +218,8 @@ void BasicNavView::setBackgroundGradient(std::span bgVerts = {}; return; } - gradientStops = std::make_unique(gradStops.size()); - std::ranges::copy(gradStops, gradientStops.get()); - gradientStopsSize = gradStops.size(); + gradientStops.reset(gradStops.size()); + std::ranges::copy(gradStops, gradientStops.begin()); } void BasicNavView::draw(Gfx::RendererCommands &__restrict__ cmds) @@ -303,12 +302,12 @@ void BasicNavView::place() buttonQuads.write(1, {.bounds = scaledRect.as(), .textureSpan = rightTex}); } bool needsTopPadding = viewRect().y > displayRect().y; - auto bgVertsSize = LGradient::vertexSize(gradientStopsSize, needsTopPadding ? LGradientPadMode::top : LGradientPadMode::none); + auto bgVertsSize = LGradient::vertexSize(gradientStops.size(), needsTopPadding ? LGradientPadMode::top : LGradientPadMode::none); bgVerts.reset({.size = bgVertsSize}); auto bgVertsMap = bgVerts.map(); auto rect = displayRect().xRect() + viewRect().yRect(); std::optional topPadding = needsTopPadding ? displayInsetRect(Direction::TOP).y : std::optional{}; - LGradient::write(bgVertsMap, 0, std::span{gradientStops.get(), gradientStopsSize}, rect, topPadding); + LGradient::write(bgVertsMap, 0, gradientStops, rect, topPadding); } void BasicNavView::showLeftBtn(bool show) diff --git a/imagine/src/io/AAssetIO.cc b/imagine/src/io/AAssetIO.cc index 47dcaac88..e5af0b292 100755 --- a/imagine/src/io/AAssetIO.cc +++ b/imagine/src/io/AAssetIO.cc @@ -19,7 +19,7 @@ #include #include #include "utils.hh" -#include "IOUtils.hh" +#include #include #include #include diff --git a/imagine/src/io/ArchiveIO.cc b/imagine/src/io/ArchiveIO.cc index 10a5842fe..ce91ffb48 100755 --- a/imagine/src/io/ArchiveIO.cc +++ b/imagine/src/io/ArchiveIO.cc @@ -20,7 +20,7 @@ #include #include #include "utils.hh" -#include "IOUtils.hh" +#include #include #include #include @@ -214,7 +214,9 @@ bool ArchiveEntry::readNextEntry() { if(!arch) [[unlikely]] return false; - auto ret = archive_read_next_header(arch.get(), &ptr); + ptr = {}; + struct archive_entry *entryPtr{}; + auto ret = archive_read_next_header(arch.get(), &entryPtr); if(ret == ARCHIVE_EOF) { logMsg("reached archive end"); @@ -231,12 +233,13 @@ bool ArchiveEntry::readNextEntry() if(Config::DEBUG_BUILD) logWarn("warning reading archive entry:%s", archive_error_string(arch.get())); } + ptr = entryPtr; return true; } bool ArchiveEntry::hasEntry() const { - return ptr; + return arch && ptr; } void ArchiveEntry::rewind() diff --git a/imagine/src/io/IO.cc b/imagine/src/io/IO.cc index 30236463c..f803ff247 100644 --- a/imagine/src/io/IO.cc +++ b/imagine/src/io/IO.cc @@ -18,7 +18,7 @@ #include #include #include -#include "IOUtils.hh" +#include namespace IG { diff --git a/imagine/src/io/MapIO.cc b/imagine/src/io/MapIO.cc index fbd44c67b..3f6030034 100755 --- a/imagine/src/io/MapIO.cc +++ b/imagine/src/io/MapIO.cc @@ -18,7 +18,7 @@ #include #include #include "utils.hh" -#include "IOUtils.hh" +#include #include #include #if defined __linux__ || defined __APPLE__ diff --git a/imagine/src/io/PosixFileIO.cc b/imagine/src/io/PosixFileIO.cc index ec8f341b1..05323a85d 100644 --- a/imagine/src/io/PosixFileIO.cc +++ b/imagine/src/io/PosixFileIO.cc @@ -15,10 +15,11 @@ #define LOGTAG "PosixFileIO" #include +#include #include #include #include -#include "IOUtils.hh" +#include #include namespace IG @@ -63,6 +64,10 @@ PosixFileIO::PosixFileIO(CStringView path, IOAccessHint access, OpenFlags openFl PosixFileIO::PosixFileIO(CStringView path, OpenFlags openFlags): PosixFileIO{path, IOAccessHint::Normal, openFlags} {} +PosixFileIO::PosixFileIO(PosixIO io): ioImpl{std::move(io)} {} + +PosixFileIO::PosixFileIO(MapIO io): ioImpl{std::move(io)} {} + void PosixFileIO::initMmap(IOAccessHint access, OpenFlags openFlags) { if(!*std::get_if(&ioImpl)) diff --git a/imagine/src/io/PosixIO.cc b/imagine/src/io/PosixIO.cc index 00f2b60d1..b4fc5b6c3 100755 --- a/imagine/src/io/PosixIO.cc +++ b/imagine/src/io/PosixIO.cc @@ -21,7 +21,7 @@ #include #include #include "utils.hh" -#include "IOUtils.hh" +#include #include #include #include diff --git a/imagine/src/io/utils.hh b/imagine/src/io/utils.hh index fc81e02e2..327d18fc0 100644 --- a/imagine/src/io/utils.hh +++ b/imagine/src/io/utils.hh @@ -7,17 +7,6 @@ namespace IG { -inline auto transformOffsetToAbsolute(IOSeekMode mode, auto offset, auto startPos, auto endPos, auto currentPos) -{ - switch(mode) - { - case IOSeekMode::Set: return offset + startPos; - case IOSeekMode::End: return offset + endPos; - case IOSeekMode::Cur: return offset + currentPos; - } - bug_unreachable("IOSeekMode == %d", (int)mode); -} - inline auto asString(IOAccessHint access) { switch(access)