From 6a5096e546bfca6e847737eb4fc6c5d0a9b59bf8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Jan 2016 12:53:03 -0800 Subject: [PATCH 1/3] Save the game title in savestates, if available. So that the UI can show them. Old savestates still load fine, just don't have the title. --- Common/ChunkFile.cpp | 116 ++++++++++++++++++++++++++++--------------- Common/ChunkFile.h | 29 +++++++---- Core/SaveState.cpp | 4 +- Core/SaveState.h | 2 - 4 files changed, 97 insertions(+), 54 deletions(-) diff --git a/Common/ChunkFile.cpp b/Common/ChunkFile.cpp index a315a98692..985cfe9424 100644 --- a/Common/ChunkFile.cpp +++ b/Common/ChunkFile.cpp @@ -161,53 +161,81 @@ PointerWrapSection::~PointerWrapSection() { } } -CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string& _rFilename, int _Revision, const char *_VersionString, u8 *&_buffer, size_t &sz, std::string *_failureReason) { - if (!File::Exists(_rFilename)) { - *_failureReason = "LoadStateDoesntExist"; - ERROR_LOG(COMMON, "ChunkReader: File doesn't exist"); - return ERROR_BAD_FILE; - } - - // Check file size - const u64 fileSize = File::GetFileSize(_rFilename); - static const u64 headerSize = sizeof(SChunkHeader); - if (fileSize < headerSize) - { - ERROR_LOG(COMMON, "ChunkReader: File too small"); - return ERROR_BAD_FILE; - } - - File::IOFile pFile(_rFilename, "rb"); - if (!pFile) - { +CChunkFileReader::Error CChunkFileReader::LoadFileHeader(File::IOFile &pFile, SChunkHeader &header, std::string *title) { + if (!pFile) { ERROR_LOG(COMMON, "ChunkReader: Can't open file for reading"); return ERROR_BAD_FILE; } - // read the header - SChunkHeader header; - if (!pFile.ReadArray(&header, 1)) - { + const u64 fileSize = pFile.GetSize(); + u64 headerSize = sizeof(SChunkHeader); + if (fileSize < headerSize) { + ERROR_LOG(COMMON, "ChunkReader: File too small"); + return ERROR_BAD_FILE; + } + + if (!pFile.ReadArray(&header, 1)) { ERROR_LOG(COMMON, "ChunkReader: Bad header size"); return ERROR_BAD_FILE; } - // Check revision - if (header.Revision != _Revision) - { - ERROR_LOG(COMMON, "ChunkReader: Wrong file revision, got %d expected %d", header.Revision, _Revision); + if (header.Revision < REVISION_MIN) { + ERROR_LOG(COMMON, "ChunkReader: Wrong file revision, got %d expected >= %d", header.Revision, REVISION_MIN); return ERROR_BAD_FILE; } - // get size - sz = (int)(fileSize - headerSize); - if (header.ExpectedSize != sz) - { - ERROR_LOG(COMMON, "ChunkReader: Bad file size, got %u expected %u", (u32)sz, header.ExpectedSize); + if (header.Revision >= REVISION_TITLE) { + char titleFixed[128]; + if (!pFile.ReadArray(titleFixed, sizeof(titleFixed))) { + ERROR_LOG(COMMON, "ChunkReader: Unable to read title"); + return ERROR_BAD_FILE; + } + + if (title) { + *title = titleFixed; + } + + headerSize += 128; + } else if (title) { + title->clear(); + } + + u32 sz = (u32)(fileSize - headerSize); + if (header.ExpectedSize != sz) { + ERROR_LOG(COMMON, "ChunkReader: Bad file size, got %u expected %u", sz, header.ExpectedSize); return ERROR_BAD_FILE; } + return ERROR_NONE; +} + +CChunkFileReader::Error CChunkFileReader::GetFileTitle(const std::string &filename, std::string *title) { + if (!File::Exists(filename)) { + ERROR_LOG(COMMON, "ChunkReader: File doesn't exist"); + return ERROR_BAD_FILE; + } + + File::IOFile pFile(filename, "rb"); + SChunkHeader header; + return LoadFileHeader(pFile, header, title); +} + +CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, const char *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) { + if (!File::Exists(filename)) { + *failureReason = "LoadStateDoesntExist"; + ERROR_LOG(COMMON, "ChunkReader: File doesn't exist"); + return ERROR_BAD_FILE; + } + + File::IOFile pFile(filename, "rb"); + SChunkHeader header; + Error err = LoadFileHeader(pFile, header, nullptr); + if (err != ERROR_NONE) { + return err; + } + // read the state + sz = header.ExpectedSize; u8 *buffer = new u8[sz]; if (!pFile.ReadBytes(buffer, sz)) { @@ -235,10 +263,10 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string& _rFilename } // Takes ownership of buffer. -CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string& _rFilename, int _Revision, const char *_VersionString, u8 *buffer, size_t sz) { - INFO_LOG(COMMON, "ChunkReader: Writing %s" , _rFilename.c_str()); +CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string &filename, const std::string &title, const char *gitVersion, u8 *buffer, size_t sz) { + INFO_LOG(COMMON, "ChunkReader: Writing %s", filename.c_str()); - File::IOFile pFile(_rFilename, "wb"); + File::IOFile pFile(filename, "wb"); if (!pFile) { ERROR_LOG(COMMON, "ChunkReader: Error opening file for write"); @@ -251,12 +279,17 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string& _rFilename // Create header SChunkHeader header; header.Compress = compress ? 1 : 0; - header.Revision = _Revision; + header.Revision = REVISION_CURRENT; header.ExpectedSize = (u32)sz; header.UncompressedSize = (u32)sz; - strncpy(header.GitVersion, _VersionString, 32); + strncpy(header.GitVersion, gitVersion, 32); header.GitVersion[31] = '\0'; + // Setup the fixed-length title. + char titleFixed[128]; + strncpy(titleFixed, title.c_str(), sizeof(titleFixed)); + titleFixed[sizeof(titleFixed) - 1] = '\0'; + // Write to file if (compress) { size_t comp_len = snappy_max_compressed_length(sz); @@ -264,11 +297,14 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string& _rFilename snappy_compress((const char *)buffer, sz, (char *)compressed_buffer, &comp_len); delete [] buffer; header.ExpectedSize = (u32)comp_len; - if (!pFile.WriteArray(&header, 1)) - { + if (!pFile.WriteArray(&header, 1)) { ERROR_LOG(COMMON, "ChunkReader: Failed writing header"); return ERROR_BAD_FILE; } + if (!pFile.WriteArray(titleFixed, sizeof(titleFixed))) { + ERROR_LOG(COMMON, "ChunkReader: Failed writing title"); + return ERROR_BAD_FILE; + } if (!pFile.WriteBytes(&compressed_buffer[0], comp_len)) { ERROR_LOG(COMMON, "ChunkReader: Failed writing compressed data"); return ERROR_BAD_FILE; @@ -292,6 +328,6 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string& _rFilename delete [] buffer; } - INFO_LOG(COMMON, "ChunkReader: Done writing %s", _rFilename.c_str()); + INFO_LOG(COMMON, "ChunkReader: Done writing %s", filename.c_str()); return ERROR_NONE; } diff --git a/Common/ChunkFile.h b/Common/ChunkFile.h index 56d7d9076b..c3880e9f0d 100644 --- a/Common/ChunkFile.h +++ b/Common/ChunkFile.h @@ -632,28 +632,28 @@ public: // Load file template template - static Error Load(const std::string& _rFilename, int _Revision, const char *_VersionString, T& _class, std::string* _failureReason) + static Error Load(const std::string &filename, const char *gitVersion, T& _class, std::string *failureReason) { - *_failureReason = "LoadStateWrongVersion"; + *failureReason = "LoadStateWrongVersion"; u8 *ptr = nullptr; size_t sz; - Error error = LoadFile(_rFilename, _Revision, _VersionString, ptr, sz, _failureReason); + Error error = LoadFile(filename, gitVersion, ptr, sz, failureReason); if (error == ERROR_NONE) { error = LoadPtr(ptr, _class); delete [] ptr; } - INFO_LOG(COMMON, "ChunkReader: Done loading %s" , _rFilename.c_str()); + INFO_LOG(COMMON, "ChunkReader: Done loading %s", filename.c_str()); if (error == ERROR_NONE) { - _failureReason->clear(); + failureReason->clear(); } return error; } // Save file template template - static Error Save(const std::string& _rFilename, int _Revision, const char *_VersionString, T& _class) + static Error Save(const std::string &filename, const std::string &title, const char *gitVersion, T& _class) { // Get data size_t const sz = MeasurePtr(_class); @@ -662,7 +662,7 @@ public: // SaveFile takes ownership of buffer if (error == ERROR_NONE) - error = SaveFile(_rFilename, _Revision, _VersionString, buffer, sz); + error = SaveFile(filename, title, gitVersion, buffer, sz); return error; } @@ -690,10 +690,9 @@ public: return ERROR_NONE; } -private: - static CChunkFileReader::Error LoadFile(const std::string& _rFilename, int _Revision, const char *_VersionString, u8 *&buffer, size_t &sz, std::string *_failureReason); - static CChunkFileReader::Error SaveFile(const std::string& _rFilename, int _Revision, const char *_VersionString, u8 *buffer, size_t sz); + static Error GetFileTitle(const std::string &filename, std::string *title); +private: struct SChunkHeader { int Revision; @@ -702,4 +701,14 @@ private: u32 UncompressedSize; char GitVersion[32]; }; + + enum { + REVISION_MIN = 4, + REVISION_TITLE = 5, + REVISION_CURRENT = REVISION_TITLE, + }; + + static Error LoadFile(const std::string &filename, const char *gitVersion, u8 *&buffer, size_t &sz, std::string *failureReason); + static Error SaveFile(const std::string &filename, const std::string &title, const char *gitVersion, u8 *buffer, size_t sz); + static Error LoadFileHeader(File::IOFile &pFile, SChunkHeader &header, std::string *title); }; diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 4317996c17..a407c253a5 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -541,7 +541,7 @@ namespace SaveState { case SAVESTATE_LOAD: INFO_LOG(COMMON, "Loading state from %s", op.filename.c_str()); - result = CChunkFileReader::Load(op.filename, REVISION, PPSSPP_GIT_VERSION, state, &reason); + result = CChunkFileReader::Load(op.filename, PPSSPP_GIT_VERSION, state, &reason); if (result == CChunkFileReader::ERROR_NONE) { osm.Show(sc->T("Loaded State"), 2.0); callbackResult = true; @@ -559,7 +559,7 @@ namespace SaveState case SAVESTATE_SAVE: INFO_LOG(COMMON, "Saving state to %s", op.filename.c_str()); - result = CChunkFileReader::Save(op.filename, REVISION, PPSSPP_GIT_VERSION, state); + result = CChunkFileReader::Save(op.filename, g_paramSFO.GetValueString("TITLE"), PPSSPP_GIT_VERSION, state); if (result == CChunkFileReader::ERROR_NONE) { osm.Show(sc->T("Saved State"), 2.0); diff --git a/Core/SaveState.h b/Core/SaveState.h index 99eca4cbaf..e23c53278e 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -25,8 +25,6 @@ namespace SaveState { typedef std::function Callback; - // TODO: Better place for this? - const int REVISION = 4; const int SAVESTATESLOTS = 5; void Init(); From 9ff8d719de1d5bf1c7493df315b861a7aea14ce6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Jan 2016 13:06:30 -0800 Subject: [PATCH 2/3] Add function to determine ppst title. --- Core/SaveState.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ Core/SaveState.h | 2 ++ 2 files changed, 44 insertions(+) diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index a407c253a5..f0e0e3aaf8 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -293,6 +293,48 @@ namespace SaveState static const char *SCREENSHOT_EXTENSION = "jpg"; // Slot utilities + std::string AppendSlotTitle(const std::string &filename, const std::string &title) { + if (!endsWith(filename, std::string(".") + STATE_EXTENSION)) { + return title + " (" + filename + ")"; + } + + // Usually these are slots, let's check the slot # after the last '_'. + size_t slotNumPos = filename.find_last_of('_'); + if (slotNumPos == filename.npos) { + return title + " (" + filename + ")"; + } + + const size_t extLength = strlen(STATE_EXTENSION) + 1; + // If we take out the extension, '_', etc. we should be left with only a single digit. + if (slotNumPos + 1 + extLength != filename.length() - 1) { + return title + " (" + filename + ")"; + } + + std::string slot = filename.substr(slotNumPos + 1, 1); + if (slot[0] < '0' || slot[0] > '8') { + return title + " (" + filename + ")"; + } + + // Change from zero indexed to human friendly. + slot[0]++; + return title + " (" + slot + ")"; + } + + std::string GetTitle(const std::string &filename) { + std::string title; + if (CChunkFileReader::GetFileTitle(filename, &title) == CChunkFileReader::ERROR_NONE) { + if (title.empty()) { + return File::GetFilename(filename); + } + + return AppendSlotTitle(filename, title); + } + + // The file can't be loaded - let's note that. + I18NCategory *sy = GetI18NCategory("System"); + return File::GetFilename(filename) + " " + sy->T("(broken)"); + } + std::string GenerateSaveSlotFilename(const std::string &gameFilename, int slot, const char *extension) { std::string discId = g_paramSFO.GetValueString("DISC_ID"); diff --git a/Core/SaveState.h b/Core/SaveState.h index e23c53278e..6b09c566d1 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -45,6 +45,8 @@ namespace SaveState std::string GetSlotDateAsString(const std::string &gameFilename, int slot); std::string GenerateSaveSlotFilename(const std::string &gameFilename, int slot, const char *extension); + std::string GetTitle(const std::string &filename); + // Load the specified file into the current state (async.) // Warning: callback will be called on a different thread. void Load(const std::string &filename, Callback callback = Callback(), void *cbUserData = 0); From a4a8e8f569c4ddc4849ba4c03da6bbfcea95871a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 23 Jan 2016 13:06:58 -0800 Subject: [PATCH 3/3] UI: Show savestate titles in manager. Note: this will also show broken savestates with (broken). --- UI/GameInfoCache.cpp | 11 ++++++++++- UI/GameInfoCache.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 36b428cd4a..0e98c0cf5c 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -35,6 +35,7 @@ #include "Core/FileSystems/DirectoryFileSystem.h" #include "Core/FileSystems/VirtualDiscFileSystem.h" #include "Core/ELF/PBPReader.h" +#include "Core/SaveState.h" #include "Core/System.h" #include "Core/Util/GameManager.h" #include "Core/Config.h" @@ -287,6 +288,11 @@ std::string GameInfo::GetTitle() { return title; } +void GameInfo::SetTitle(const std::string &newTitle) { + lock_guard guard(lock); + title = newTitle; +} + static bool ReadFileToString(IFileSystem *fs, const char *filename, std::string *contents, recursive_mutex *mtx) { PSPFileInfo info = fs->GetFileInfo(filename); if (!info.exists) { @@ -443,9 +449,12 @@ handleELF: case FILETYPE_PPSSPP_SAVESTATE: { + info_->SetTitle(SaveState::GetTitle(gamePath_)); + + lock_guard guard(info_->lock); + // Let's use the screenshot as an icon, too. std::string screenshotPath = ReplaceAll(gamePath_, ".ppst", ".jpg"); - lock_guard guard(info_->lock); if (File::Exists(screenshotPath)) { if (readFileToString(false, screenshotPath.c_str(), info_->iconTextureData)) { info_->iconDataLoaded = true; diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 74d2823a1a..9fa3c36a39 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -117,6 +117,7 @@ public: std::vector GetSaveDataDirectories(); std::string GetTitle(); + void SetTitle(const std::string &newTitle); // Hold this when reading or writing from the GameInfo. // Don't need to hold it when just passing around the pointer,