diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a7e9930..0bfc2542 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,6 +254,7 @@ set(Impacto_Src src/renderer/3d/animation.cpp src/renderer/3d/modelanimator.cpp + src/io/filemeta.cpp src/io/vfs.cpp src/io/assetpath.cpp src/io/memorystream.cpp diff --git a/src/data/savesystem.cpp b/src/data/savesystem.cpp index c6636768..879ae13d 100644 --- a/src/data/savesystem.cpp +++ b/src/data/savesystem.cpp @@ -46,8 +46,9 @@ void LoadMemoryNew(LoadProcess load) { if (Implementation) Implementation->LoadMemoryNew(load); } -void FlushWorkingSaveEntry(SaveType type, int id) { - if (Implementation) Implementation->FlushWorkingSaveEntry(type, id); +void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) { + if (Implementation) + Implementation->FlushWorkingSaveEntry(type, id, autoSaveType); } void WriteSaveFile() { @@ -172,11 +173,11 @@ void SetCheckpointId(int id) { if (Implementation) Implementation->SetCheckpointId(id); } -int GetQuickSaveCount() { - if (Implementation) return Implementation->GetQuickSaveCount(); +int GetQuickSaveOpenSlot() { + if (Implementation) return Implementation->GetQuickSaveOpenSlot(); ImpLog(LL_Warning, LC_VMStub, - "%s: save system not implemented, returning 0\n", __func__); - return 0; + "%s: save system not implemented, returning -1\n", __func__); + return -1; } Sprite const& GetSaveThumbnail(SaveType type, int id) { diff --git a/src/data/savesystem.h b/src/data/savesystem.h index efb10543..1d1b1950 100644 --- a/src/data/savesystem.h +++ b/src/data/savesystem.h @@ -12,6 +12,8 @@ namespace SaveSystem { BETTER_ENUM(SaveDataType, int, None, CHLCC, CCLCC, MO6TW) +enum SaveFlagsMode { WriteProtect = 1 }; + enum SaveError { SaveOK = 0, SaveNotFound = 2, @@ -72,7 +74,8 @@ class SaveSystemBase { virtual void SaveMemory() = 0; virtual void LoadEntry(SaveType type, int id) = 0; virtual void LoadMemoryNew(LoadProcess){}; - virtual void FlushWorkingSaveEntry(SaveType type, int id) = 0; + virtual void FlushWorkingSaveEntry(SaveType type, int id, + int autoSaveType) = 0; virtual void WriteSaveFile() = 0; virtual uint32_t GetSavePlayTime(SaveType type, int id) = 0; virtual uint8_t GetSaveFlags(SaveType type, int id) = 0; @@ -93,7 +96,15 @@ class SaveSystemBase { virtual bool GetBgmFlag(int id) = 0; virtual void SetCheckpointId(int id) = 0; virtual Sprite const& GetSaveThumbnail(SaveType type, int id) = 0; - int GetQuickSaveCount() { return QuickSaveCount; } + int GetQuickSaveOpenSlot() { + for (int i = 0; i < MaxSaveEntries; i++) { + if (QuickSaveEntries[i]->Status == 0) return i; + } + for (int i = 0; i < MaxSaveEntries; i++) { + if (!(GetSaveFlags(SaveQuick, i) & WriteProtect)) return i; + } + return -1; + } Sprite const& GetWorkingSaveThumbnail() { return WorkingSaveThumbnail; } protected: @@ -113,7 +124,7 @@ SaveError MountSaveFile(); void SaveMemory(); void LoadEntry(SaveType type, int id); void LoadMemoryNew(LoadProcess process); -void FlushWorkingSaveEntry(SaveType type, int id); +void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType = 0); void WriteSaveFile(); uint32_t GetSavePlayTime(SaveType type, int id); uint8_t GetSaveFlags(SaveType type, int id); @@ -130,7 +141,7 @@ void GetEVStatus(int evId, int* totalVariations, int* viewedVariations); bool GetEVVariationIsUnlocked(int evId, int variationIdx); bool GetBgmFlag(int id); void SetCheckpointId(int id); -int GetQuickSaveCount(); +int GetQuickSaveOpenSlot(); Sprite const& GetSaveThumbnail(SaveType type, int id); Sprite const& GetWorkingSaveThumbnail(); diff --git a/src/games/cclcc/savemenu.cpp b/src/games/cclcc/savemenu.cpp index e9e7c124..984728b0 100644 --- a/src/games/cclcc/savemenu.cpp +++ b/src/games/cclcc/savemenu.cpp @@ -55,6 +55,37 @@ void SaveMenu::Show() { State = Showing; FadeAnimation.StartIn(); int id = 0; + Impacto::SaveSystem::SaveType saveType = + ScrWork[SW_SAVEMENUMODE] == SaveMenuPageType::QuickLoad + ? SaveSystem::SaveType::SaveQuick + : SaveSystem::SaveType::SaveFull; + + std::array saveEntryIds; + for (int i = 0; i < SaveSystem::MaxSaveEntries; i++) { + saveEntryIds[i] = i; + } + if (saveType == SaveSystem::SaveType::SaveQuick) { + // quick saves are sorted by time and status + std::sort(saveEntryIds.begin(), saveEntryIds.end(), + [saveType](int a, int b) { + int statusA = SaveSystem::GetSaveStatus(saveType, a); + int statusB = SaveSystem::GetSaveStatus(saveType, b); + if (statusA == statusB) { + std::tm ta = SaveSystem::GetSaveDate(saveType, a); + std::tm tb = SaveSystem::GetSaveDate(saveType, b); + std::time_t th = std::mktime(&ta); + std::time_t tl = std::mktime(&tb); + if (th == -1 || tl == -1) { + ImpLog(LL_Error, LC_General, + "Failed to convert time to time_t\n"); + return statusA > statusB; + } + return difftime(th, tl) > 0; + } + return statusA > statusB; + }); + } + for (int p = 0; p < Pages; ++p) { MainItems[p] = new Widgets::Group(this); MainItems[p]->WrapFocus = false; @@ -62,16 +93,13 @@ void SaveMenu::Show() { for (int i = 0; i < RowsPerPage; i++) { // Start on right col for (int j = EntriesPerRow - 1; j >= 0; j--) { - Impacto::SaveSystem::SaveType saveType = - ScrWork[SW_SAVEMENUMODE] == SaveMenuPageType::QuickLoad - ? SaveSystem::SaveType::SaveQuick - : SaveSystem::SaveType::SaveFull; glm::vec2 buttonPos = (j == 0) ? glm::vec2{EntryStartXL, EntryStartYL + (i * EntryYPadding)} : glm::vec2{EntryStartXR, EntryStartYR + (i * EntryYPadding)}; SaveEntryButton* saveEntryButton = new SaveEntryButton( - id, EntryHighlightedBoxSprite[ScrWork[SW_SAVEMENUMODE]], + saveEntryIds[id], id, + EntryHighlightedBoxSprite[ScrWork[SW_SAVEMENUMODE]], EntryHighlightedTextSprite[ScrWork[SW_SAVEMENUMODE]], p, buttonPos, SlotLockedSprite[ScrWork[SW_SAVEMENUMODE]], saveType, NoDataSprite[ScrWork[SW_SAVEMENUMODE]], diff --git a/src/games/cclcc/savesystem.cpp b/src/games/cclcc/savesystem.cpp index 9b55a9c2..fbe132b4 100644 --- a/src/games/cclcc/savesystem.cpp +++ b/src/games/cclcc/savesystem.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include namespace Impacto { namespace CCLCC { @@ -25,28 +25,49 @@ using namespace Impacto::Profile::ScriptVars; using namespace Impacto::Profile::Vm; SaveError SaveSystem::CheckSaveFile() { - std::filesystem::path savePath(SaveFilePath); - if (!std::filesystem::exists(savePath)) { + std::error_code ec; + IoError existsState = Io::PathExists(SaveFilePath); + if (existsState == IoError_NotFound) { return SaveNotFound; + } else if (existsState == IoError_Fail) { + ImpLog(LL_Error, LC_IO, + "Failed to check if save file exists, error: \"%s\"\n", + ec.message().c_str()); + return SaveFailed; } - if (std::filesystem::file_size(savePath) != SaveFileSize) { + auto saveFileSize = Io::GetFileSize(SaveFilePath); + if (saveFileSize == IoError_Fail) { + ImpLog(LL_Error, LC_IO, "Failed to get save file size, error: \"%s\"\n", + ec.message().c_str()); + return SaveFailed; + } else if (saveFileSize != SaveFileSize) { return SaveCorrupted; } - if (auto perms = std::filesystem::status(savePath).permissions(); - to_underlying(perms) & - (to_underlying(std::filesystem::perms::owner_read) | - to_underlying(std::filesystem::perms::owner_write)) == 0 && - to_underlying(perms) & - (to_underlying(std::filesystem::perms::group_read) | - to_underlying(std::filesystem::perms::group_write)) == 0) { + auto checkPermsBit = [](Io::FilePermissionsFlags perms, + Io::FilePermissionsFlags flag) { + return to_underlying(perms) & to_underlying(flag); + }; + + Io::FilePermissionsFlags perms; + IoError permsState = Io::GetFilePermissions(SaveFilePath, perms); + if (permsState == IoError_Fail) { + ImpLog(LL_Error, LC_IO, + "Failed to get save file permissions, error: \"%s\"\n", + ec.message().c_str()); + return SaveFailed; + } else if ((!checkPermsBit(perms, Io::FilePermissionsFlags::owner_read) || + !checkPermsBit(perms, Io::FilePermissionsFlags::owner_write))) { return SaveWrongUser; } return SaveOK; } SaveError SaveSystem::CreateSaveFile() { + using CF = Io::PhysicalFileStream::CreateFlagsMode; Io::Stream* stream; - IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &stream, false); + IoError err = Io::PhysicalFileStream::Create( + SaveFilePath, &stream, + CF::CREATE_IF_NOT_EXISTS | CF::CREATE_DIRS | CF::WRITE); if (err != IoError_OK) { return SaveFailed; } @@ -232,20 +253,24 @@ SaveError SaveSystem::MountSaveFile() { // return 0; //} -void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id) { +void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id, + int autoSaveType) { SaveFileEntry* entry = 0; switch (type) { case SaveQuick: - entry = (SaveFileEntry*)QuickSaveEntries[QuickSaveCount++]; + entry = (SaveFileEntry*)QuickSaveEntries[id]; break; case SaveFull: entry = (SaveFileEntry*)FullSaveEntries[id]; break; } - if (entry != 0) { + if (entry != 0 && !(GetSaveFlags(type, id) & WriteProtect)) { Renderer->FreeTexture(entry->SaveThumbnail.Sheet.Texture); *entry = *WorkingSaveEntry; + if (type == SaveQuick) { + entry->SaveType = autoSaveType; + } time_t rawtime; time(&rawtime); entry->SaveDate = *localtime(&rawtime); @@ -271,8 +296,10 @@ void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id) { } void SaveSystem::WriteSaveFile() { + using CF = Io::PhysicalFileStream::CreateFlagsMode; Io::Stream* stream; - IoError err = Io::PhysicalFileStream::Create(SaveFilePath, &stream); + IoError err = + Io::PhysicalFileStream::Create(SaveFilePath, &stream, CF::WRITE); if (err != IoError_OK) { ImpLog(LL_Error, LC_IO, "Failed to open save file for writing\n"); return; diff --git a/src/games/cclcc/savesystem.h b/src/games/cclcc/savesystem.h index f0a86545..098068d9 100644 --- a/src/games/cclcc/savesystem.h +++ b/src/games/cclcc/savesystem.h @@ -32,7 +32,7 @@ class SaveSystem : public SaveSystemBase { void SaveMemory() override; void LoadEntry(SaveType type, int id) override; void LoadMemoryNew(LoadProcess load) override; - void FlushWorkingSaveEntry(SaveType type, int id) override; + void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override; void WriteSaveFile() override; uint32_t GetSavePlayTime(SaveType type, int id) override; uint8_t GetSaveFlags(SaveType type, int id) override; diff --git a/src/games/chlcc/savesystem.cpp b/src/games/chlcc/savesystem.cpp index 8e79cc8c..fe904d92 100644 --- a/src/games/chlcc/savesystem.cpp +++ b/src/games/chlcc/savesystem.cpp @@ -132,7 +132,8 @@ SaveError SaveSystem::MountSaveFile() { // return 0; //} -void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id) { +void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id, + int autoSaveType) { SaveFileEntry* entry = 0; switch (type) { case SaveQuick: @@ -144,8 +145,11 @@ void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id) { } if (WorkingSaveEntry != 0) { - if (entry != 0) { + if (entry != 0 && !(GetSaveFlags(type, id) & WriteProtect)) { *entry = *WorkingSaveEntry; + if (type == SaveQuick) { + entry->SaveType = autoSaveType; + } time_t rawtime; time(&rawtime); entry->SaveDate = *localtime(&rawtime); diff --git a/src/games/chlcc/savesystem.h b/src/games/chlcc/savesystem.h index 6917c26e..75977cd8 100644 --- a/src/games/chlcc/savesystem.h +++ b/src/games/chlcc/savesystem.h @@ -22,7 +22,7 @@ class SaveSystem : public SaveSystemBase { SaveError MountSaveFile() override; void SaveMemory() override; void LoadEntry(SaveType type, int id) override; - void FlushWorkingSaveEntry(SaveType type, int id) override; + void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override; void WriteSaveFile() override; uint32_t GetSavePlayTime(SaveType type, int id) override; uint8_t GetSaveFlags(SaveType type, int id) override; diff --git a/src/games/mo6tw/savesystem.cpp b/src/games/mo6tw/savesystem.cpp index f4c025a6..ef8b7d6b 100644 --- a/src/games/mo6tw/savesystem.cpp +++ b/src/games/mo6tw/savesystem.cpp @@ -190,7 +190,8 @@ SaveError SaveSystem::MountSaveFile() { // return 0; //} -void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id) { +void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id, + int autoSaveType) { SaveFileEntry* entry = 0; switch (type) { case SaveQuick: @@ -202,7 +203,11 @@ void SaveSystem::FlushWorkingSaveEntry(SaveType type, int id) { } if (WorkingSaveEntry != 0) { - if (entry != 0) { + if (entry != 0 && !(GetSaveFlags(type, id) & WriteProtect)) { + Renderer->FreeTexture(entry->SaveThumbnail.Sheet.Texture); + if (type == SaveQuick) { + entry->SaveType = autoSaveType; + } entry->Status = 1; std::time_t t = std::time(0); diff --git a/src/games/mo6tw/savesystem.h b/src/games/mo6tw/savesystem.h index 54c3c8ea..ddb376bb 100644 --- a/src/games/mo6tw/savesystem.h +++ b/src/games/mo6tw/savesystem.h @@ -22,7 +22,7 @@ class SaveSystem : public SaveSystemBase { SaveError MountSaveFile() override; void SaveMemory() override; void LoadEntry(SaveType type, int id) override; - void FlushWorkingSaveEntry(SaveType type, int id) override; + void FlushWorkingSaveEntry(SaveType type, int id, int autoSaveType) override; void WriteSaveFile() override; uint32_t GetSavePlayTime(SaveType type, int id) override; uint8_t GetSaveFlags(SaveType type, int id) override; diff --git a/src/io/filemeta.cpp b/src/io/filemeta.cpp new file mode 100644 index 00000000..501d2f44 --- /dev/null +++ b/src/io/filemeta.cpp @@ -0,0 +1,59 @@ +#include "filemeta.h" +#include "../log.h" +#include + +namespace Impacto { +namespace Io { + +int64_t GetFileSize(std::string const& path) { + std::error_code ec; + uintmax_t result = std::filesystem::file_size(path, ec); + if (ec) { + ImpLog(LL_Error, LC_IO, + "Error getting file size of file \"%s\", error: \"%s\"\n", + path.c_str(), ec.message().c_str()); + return IoError_Fail; + } + // Hopefully no one has a file of size between int64_t max and uint64_t max + return static_cast(result); +} + +IoError PathExists(std::string const& path) { + std::error_code ec; + bool result = std::filesystem::exists(path, ec); + if (ec) { + ImpLog(LL_Error, LC_IO, + "Error checking for file existence for file \"%s\", error: \"%s\"\n", + path.c_str(), ec.message().c_str()); + return IoError_Fail; + } + return result == false ? IoError_NotFound : IoError_OK; +} + +int8_t CreateDirectories(std::string const& path) { + std::error_code ec; + bool result = std::filesystem::create_directories(path, ec); + if (ec) { + ImpLog(LL_Error, LC_IO, + "Error creating directories for file \"%s\", error: \"%s\"\n", + path.c_str(), ec.message().c_str()); + return IoError_Fail; + } + return result; +} + +IoError GetFilePermissions(std::string const& path, + FilePermissionsFlags& flags) { + std::error_code ec; + flags = std::filesystem::status(path, ec).permissions(); + if (ec) { + ImpLog(LL_Error, LC_IO, + "Error retrieving permissions for file \"%s\", error: \"%s\"\n", + path.c_str(), ec.message().c_str()); + return IoError_Fail; + } + return IoError_OK; +} + +} // namespace Io +} // namespace Impacto \ No newline at end of file diff --git a/src/io/filemeta.h b/src/io/filemeta.h index 6245d416..c02f40ab 100644 --- a/src/io/filemeta.h +++ b/src/io/filemeta.h @@ -2,7 +2,8 @@ #include #include - +#include +#include "io.h" namespace Impacto { namespace Io { @@ -14,5 +15,14 @@ struct FileMeta { int64_t Size = 0; }; +// TODO: use our own perms enum +using FilePermissionsFlags = std::filesystem::perms; + +int64_t GetFileSize(std::string const& path); +IoError PathExists(std::string const& path); +int8_t CreateDirectories(std::string const& path); +IoError GetFilePermissions(std::string const& path, + FilePermissionsFlags& flags); + } // namespace Io } // namespace Impacto \ No newline at end of file diff --git a/src/io/physicalfilestream.cpp b/src/io/physicalfilestream.cpp index 41648673..823bc8c1 100644 --- a/src/io/physicalfilestream.cpp +++ b/src/io/physicalfilestream.cpp @@ -3,32 +3,86 @@ #include #include +#include namespace Impacto { namespace Io { +std::ios_base::openmode PhysicalFileStream::PrepareFileOpenMode( + CreateFlags flags) { + std::ios_base::openmode mode = std::ios::binary; + IoError fileExists = PathExists(SourceFileName); + if (fileExists == IoError_Fail) { + ErrorCode = IoError_Fail; + return {}; + } -IoError PhysicalFileStream::Create(std::string const& fileName, Stream** out, - bool exists) { - std::error_code ec; - if (exists && !std::filesystem::exists(fileName, ec)) { - if (ec) { - ImpLog(LL_Error, LC_IO, "Error checking file existence: %s\n", - ec.message().c_str()); - return IoError_Fail; + bool writeNoExistNoCreate = (flags & WRITE) && + (fileExists == IoError_NotFound) && + !(flags & CREATE_IF_NOT_EXISTS); + bool readNoExist = + !(flags & WRITE) && (flags & READ) && (fileExists == IoError_NotFound); + if (writeNoExistNoCreate || readNoExist) { + ErrorCode = IoError_NotFound; + std::string errMsg = + std::make_error_code(std::errc::no_such_file_or_directory).message(); + ImpLog(LL_Error, LC_IO, "Failed to open stream \"%s\", error: \"%s\"\n", + SourceFileName.c_str(), errMsg.c_str()); + return {}; + } + + bool writeExistNoOverwrite = + (flags & WRITE) && fileExists && !(flags & TRUNCATE) && !(flags & APPEND); + if (writeExistNoOverwrite) { + // avoid truncating when in write only mode without truncate flag to + // preserve file size + // I think this is more intuitive than making it an error + // or letting the truncate happen + flags |= CreateFlagsMode::READ; + } + + // require write for create/overwrite flags + assert((flags & WRITE) || !(flags & CREATE_DIRS)); + assert((flags & WRITE) || !(flags & CREATE_IF_NOT_EXISTS)); + + // truncate is only needed when creating nonexistent if also reading + bool truncFlag = (flags & TRUNCATE) || + ((flags & READ) && !fileExists && + (flags & CREATE_IF_NOT_EXISTS) && !(flags & APPEND)); + + // trunc and append are mutually exclusive + assert(!truncFlag || !(flags & APPEND)); + + if (flags & READ) mode |= std::ios::in; + if (flags & WRITE) mode |= std::ios::out; + if (flags & APPEND) mode |= std::ios::app; + if (truncFlag) mode |= std::ios::trunc; + if (flags & CREATE_DIRS) { + auto result = Io::CreateDirectories(SourceFileName); + if (result == IoError_Fail) { + ErrorCode = IoError_Fail; + return {}; } - return IoError_NotFound; } - PhysicalFileStream* result = new PhysicalFileStream(fileName, !exists); + ErrorCode = IoError_OK; + return mode; +} + +IoError PhysicalFileStream::Create(std::string const& fileName, Stream** out, + CreateFlags flags) { + PhysicalFileStream* result = new PhysicalFileStream(fileName, flags); + if (result->ErrorCode != IoError_OK) { + ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\"\n", fileName.c_str()); + delete result; + return result->ErrorCode; + } if (!result->FileStream) { ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\", error: \"%s\"\n", - fileName.c_str(), std::strerror(errno)); + fileName.c_str(), std::generic_category().message(errno).c_str()); delete result; return IoError_Fail; } - result->Meta.Size = std::filesystem::file_size(result->SourceFileName, ec); - if (ec) { - ImpLog(LL_Error, LC_IO, "Error getting file size: %s\n", - ec.message().c_str()); + result->Meta.Size = GetFileSize(result->SourceFileName); + if (result->Meta.Size == IoError_Fail) { delete result; return IoError_Fail; } @@ -37,13 +91,18 @@ IoError PhysicalFileStream::Create(std::string const& fileName, Stream** out, } int64_t PhysicalFileStream::Read(void* buffer, int64_t sz) { - if (sz < 0) return IoError_Fail; - if (Position == Meta.Size) return IoError_Eof; + if (sz < 0 || !(Flags & READ)) return IoError_Fail; + if (Position >= Meta.Size || FileStream.eof()) { + FileStream.clear(FileStream.rdstate() & ~std::ios::failbit & + ~std::ios::eofbit); // Clear only failbit and eofbit + return IoError_Eof; + }; int bytesToRead = std::min(sz, Meta.Size - Position); FileStream.read((char*)buffer, bytesToRead); if (!FileStream) { ImpLog(LL_Error, LC_IO, "Read failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.c_str(), + std::generic_category().message(errno).c_str()); FileStream.clear(FileStream.rdstate() & ~std::ios::failbit & ~std::ios::eofbit); // Clear only failbit and eofbit return IoError_Fail; @@ -54,6 +113,10 @@ int64_t PhysicalFileStream::Read(void* buffer, int64_t sz) { } int64_t PhysicalFileStream::Seek(int64_t offset, int origin) { + if (!(Flags & READ) && (Flags & APPEND)) { + // seeking is useless in readless append mode + return IoError_Fail; + } int64_t absPos; switch (origin) { case RW_SEEK_SET: @@ -66,11 +129,15 @@ int64_t PhysicalFileStream::Seek(int64_t offset, int origin) { absPos = Meta.Size - offset; break; } - if (absPos < 0 || absPos > Meta.Size) return IoError_Fail; + + // seeking past EOF is a legal operation, after write past EOF, the gap + // between prev file size and write pos is zero padded + if (absPos < 0) return IoError_Fail; FileStream.seekg(absPos, std::ios::beg); if (!FileStream && !FileStream.eof()) { ImpLog(LL_Error, LC_IO, "Seek failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.c_str(), + std::generic_category().message(errno).c_str()); FileStream.clear(FileStream.rdstate() & ~std::ios::failbit & ~std::ios::eofbit); // Clear only failbit and eofbit return IoError_Fail; @@ -82,22 +149,28 @@ int64_t PhysicalFileStream::Seek(int64_t offset, int origin) { IoError PhysicalFileStream::Duplicate(Stream** outStream) { PhysicalFileStream* result = new PhysicalFileStream(*this); std::error_code ec; + if (result->ErrorCode != IoError_OK) { + ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\"\n", + SourceFileName.c_str()); + delete result; + return result->ErrorCode; + } if (!result->FileStream) { ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\", error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.c_str(), + std::generic_category().message(errno).c_str()); delete result; return IoError_Fail; } - result->Meta.Size = std::filesystem::file_size(SourceFileName, ec); - if (ec) { - ImpLog(LL_Error, LC_IO, "Error getting file size: %s\n", - ec.message().c_str()); + result->Meta.Size = GetFileSize(result->SourceFileName); + if (result->Meta.Size == IoError_Fail) { delete result; return IoError_Fail; } if (result->Seek(Position, RW_SEEK_SET) < 0) { ImpLog(LL_Error, LC_IO, "Seek failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.c_str(), + std::generic_category().message(errno).c_str()); delete result; return IoError_Fail; } @@ -106,10 +179,14 @@ IoError PhysicalFileStream::Duplicate(Stream** outStream) { } int64_t PhysicalFileStream::Write(void* buffer, int64_t sz, int cnt) { + if (!(Flags & WRITE)) { + return IoError_Fail; + } FileStream.write((char*)buffer, sz * cnt); if (!FileStream) { ImpLog(LL_Error, LC_IO, "Write failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.c_str(), + std::generic_category().message(errno).c_str()); FileStream.clear(FileStream.rdstate() & ~std::ios::failbit & ~std::ios::eofbit); // Clear only failbit and eofbit return IoError_Fail; diff --git a/src/io/physicalfilestream.h b/src/io/physicalfilestream.h index e140d095..898905d3 100644 --- a/src/io/physicalfilestream.h +++ b/src/io/physicalfilestream.h @@ -2,42 +2,50 @@ #include "stream.h" #include -#include -#include "buffering.h" namespace Impacto { namespace Io { class PhysicalFileStream : public Stream { public: + enum CreateFlagsMode { + READ = 1, + WRITE = READ << 1, + CREATE_IF_NOT_EXISTS = WRITE << 1, + TRUNCATE = CREATE_IF_NOT_EXISTS << 1, + APPEND = TRUNCATE << 1, + CREATE_DIRS = CREATE_IF_NOT_EXISTS << 1 + }; + using CreateFlags = int; static IoError Create(std::string const& fileName, Stream** out, - bool exists = true); + CreateFlags flags = CreateFlagsMode::READ); int64_t Read(void* buffer, int64_t sz) override; int64_t Seek(int64_t offset, int origin) override; IoError Duplicate(Stream** outStream) override; int64_t Write(void* buffer, int64_t sz, int cnt = 1) override; protected: - std::ios_base::openmode GetFileMode(bool truncate) { - // trunc will clear file if it exists, and allows creation of new file - return (truncate) ? std::ios::in | std::ios::out | std::ios::trunc | - std::ios::binary - : std::ios::in | std::ios::out | std::ios::binary; - } - PhysicalFileStream(std::filesystem::path filePath, bool truncate = false) - : Truncate(truncate), + std::ios_base::openmode PrepareFileOpenMode(CreateFlags flags); + + PhysicalFileStream(std::string filePath, CreateFlags flags) + : Flags(flags), SourceFileName(std::move(filePath)), - FileStream(SourceFileName, GetFileMode(truncate)) { - Meta.FileName = SourceFileName.string(); + FileStream(SourceFileName, PrepareFileOpenMode(flags)) { + Meta.FileName = SourceFileName; } + PhysicalFileStream(std::string filePath) + : PhysicalFileStream(std::move(filePath), CreateFlagsMode::READ) {} + PhysicalFileStream(PhysicalFileStream const& other) - : SourceFileName(other.SourceFileName), - FileStream(other.SourceFileName, GetFileMode(other.Truncate)) { - Meta.FileName = SourceFileName.string(); + : Flags(other.Flags), + SourceFileName(other.SourceFileName), + FileStream(other.SourceFileName, PrepareFileOpenMode(Flags)) { + Meta.FileName = SourceFileName; } - bool Truncate; - std::filesystem::path SourceFileName; + IoError ErrorCode = IoError_OK; + CreateFlags Flags; + std::string SourceFileName; std::fstream FileStream; }; diff --git a/src/ui/widgets/cclcc/saveentrybutton.cpp b/src/ui/widgets/cclcc/saveentrybutton.cpp index 7c6ec238..557e7e0d 100644 --- a/src/ui/widgets/cclcc/saveentrybutton.cpp +++ b/src/ui/widgets/cclcc/saveentrybutton.cpp @@ -20,19 +20,17 @@ using namespace Impacto::SaveSystem; glm::vec4 SaveEntryButton::FocusedAlpha = glm::vec4(1.0f); Animation SaveEntryButton::FocusedAlphaFade; -SaveEntryButton::SaveEntryButton(int id, Sprite const& focusedBox, +SaveEntryButton::SaveEntryButton(int id, int index, Sprite const& focusedBox, Sprite const& focusedText, int page, glm::vec2 pos, Sprite lockedSymbol, SaveSystem::SaveType saveType, Sprite NoDataSprite, Sprite BrokenDataSprite) : Widgets::Button( - (saveType == SaveSystem::SaveType::SaveFull - ? id - : SaveSystem::GetQuickSaveCount() - id - 1), + id, Sprite(SpriteSheet(), focusedBox.Bounds.X, focusedBox.Bounds.Y, focusedBox.Bounds.Width, focusedBox.Bounds.Height), Sprite(SpriteSheet(), 0, 0, 0, 0), focusedBox, pos), - Index(id), + Index(index), Page(page), FocusedSpriteLabel(focusedText, glm::vec2{pos.x, pos.y - 34}), LockedSymbol(lockedSymbol, @@ -157,7 +155,7 @@ void SaveEntryButton::RefreshSaveDateText() { // TODO: Make this only refresh when saved void SaveEntryButton::Update(float dt) { SaveStatus = SaveSystem::GetSaveStatus(Type, Id); - IsLocked = SaveSystem::GetSaveFlags(Type, Id) == 1; + IsLocked = SaveSystem::GetSaveFlags(Type, Id) & WriteProtect; if (SaveStatus == 1) { auto strIndex = (SaveSystem::GetSaveTitle(Type, Id) * 2); if (strIndex > 40) { diff --git a/src/ui/widgets/cclcc/saveentrybutton.h b/src/ui/widgets/cclcc/saveentrybutton.h index fe74c3a8..8091776b 100644 --- a/src/ui/widgets/cclcc/saveentrybutton.h +++ b/src/ui/widgets/cclcc/saveentrybutton.h @@ -14,10 +14,10 @@ namespace CCLCC { class SaveEntryButton : public Widgets::Button { public: - SaveEntryButton(int id, Sprite const& focusedBox, Sprite const& focusedText, - int page, glm::vec2 pos, Sprite lockedSymbol, - SaveSystem::SaveType saveType, Sprite noDataSprite, - Sprite brokenDataSprite); + SaveEntryButton(int id, int index, Sprite const& focusedBox, + Sprite const& focusedText, int page, glm::vec2 pos, + Sprite lockedSymbol, SaveSystem::SaveType saveType, + Sprite noDataSprite, Sprite brokenDataSprite); void Render() override; int GetPage() const; void AddNormalSpriteLabel(Sprite norm, glm::vec2 pos); diff --git a/src/vm/inst_dialogue.cpp b/src/vm/inst_dialogue.cpp index 4ae2200b..f4b3afa6 100644 --- a/src/vm/inst_dialogue.cpp +++ b/src/vm/inst_dialogue.cpp @@ -374,7 +374,32 @@ VmInstruction(InstSelect) { switch (type) { case 0: { UI::SelectionMenuPtr->Show(); - SaveIconDisplay::ShowFor(2.4f); + bool flag = GetFlag(1282); + int unk = ScrWork[2108]; + if (unk < 0x100) { + ScrWork[2108] = unk + 0x10; + SetFlag(1286, 1); + ResetInstruction; + BlockThread; + } + SetFlag(thread->DialoguePageId + 1213, 0); + if (ScrWork[2112] == 2) { + thread->Ip += 12; + return; + } else { + SaveSystem::SaveMemory(); + int quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot(); + if (!flag && quicksaveEntries != -1) { + SaveIconDisplay::ShowFor(2.4f); + SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::SaveQuick, + quicksaveEntries, 2); + } + ScrWork[2112] = 0; + if (quicksaveEntries == -1) { + thread->Ip += 12; + } + } + SetFlag(1286, 0); } break; case 1: { if (!UI::SelectionMenuPtr->ChoiceMade) { diff --git a/src/vm/inst_system.cpp b/src/vm/inst_system.cpp index 495e99cb..aa778eda 100644 --- a/src/vm/inst_system.cpp +++ b/src/vm/inst_system.cpp @@ -223,6 +223,9 @@ VmInstruction(InstSave) { switch (type) { // TODO: Types 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // 16, 20, 21, 72, 30, 31, 32, 33, 34, 35, 41, 50, 51, 66, // 67, 70, 71, 74, 76 + case 0: { + // TODO, System Save only + } break; case 3: break; case 4: { @@ -255,6 +258,9 @@ VmInstruction(InstSave) { break; case 51: break; + case 60: + SaveSystem::WriteSaveFile(); + break; case 70: if (Profile::Vm::GameInstructionSet == +InstructionSet::CC) { LoadSaveFile(); @@ -713,38 +719,49 @@ VmInstruction(InstDebugData) { } VmInstruction(InstAutoSave) { StartInstruction; + auto quickSave = [&](int autosaveRestartCheck, int saveType) { + SaveSystem::SaveMemory(); + if (ScrWork[2112] != autosaveRestartCheck) { + int quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot(); + if (quicksaveEntries != -1) { + SaveIconDisplay::ShowFor(2.4f); + SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::SaveQuick, + quicksaveEntries, saveType); + } + } + SetFlag(1285, 1); + ScrWork[2112] = 0; + }; + PopUint8(type); switch (type) { case 0: // QuickSave - SaveIconDisplay::ShowFor(2.4f); + case 20: if (ScrWork[SW_TITLE] == 0xffff) break; - SaveSystem::SaveMemory(); - if (ScrWork[2112] != 1) { - // TODO: Quicksave(1) - } - SetFlag(1285, 1); - ScrWork[2112] = 0; + quickSave(1, 1); ImpLogSlow(LL_Warning, LC_VMStub, "STUB instruction AutoSave(type: QuickSave)\n"); break; case 1: // AutoSaveRestart (?) + case 21: if (ScrWork[SW_TITLE] == 0xffff) break; - SaveSystem::SaveMemory(); - if ((ScrWork[2112] != 3)) { - // TODO: Quicksave(3) - } - SetFlag(1285, 1); - ScrWork[2112] = 0; + quickSave(3, 3); ImpLogSlow(LL_Warning, LC_VMStub, "STUB instruction AutoSave(type: %i)\n", type); break; - case 3: // DisableAutoSave - // Check quicksave, quicksave(0) + case 3: { // DisableAutoSave + int quicksaveEntries = SaveSystem::GetQuickSaveOpenSlot(); + if (quicksaveEntries != -1) { + SaveIconDisplay::ShowFor(2.4f); + SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::SaveQuick, + quicksaveEntries, 0); + } + SetFlag(1285, 0); ImpLogSlow(LL_Warning, LC_VMStub, "STUB instruction AutoSave(type: %i)\n", type); - break; - case 5: // EnableAutoSave + } break; + case 5: { // EnableAutoSave ImpLogSlow(LL_Warning, LC_VMStub, "STUB instruction AutoSave(type: %i)\n", type); if (ScrWork[SW_TITLE] != 0xffff) { @@ -752,7 +769,7 @@ VmInstruction(InstAutoSave) { SetFlag(1285, 1); ScrWork[2112] = 0; } - break; + } break; case 10: { // SetCheckpointId if (Profile::Vm::UseReturnIds) { PopUint16(checkpointId); @@ -774,6 +791,9 @@ VmInstruction(InstAutoSave) { ImpLogSlow(LL_Warning, LC_VMStub, "STUB instruction AutoSave(arg1: %i)\n", type); break; + case 0xff: { + BlockThread; + } break; default: // More quicksave cases here break;