diff options
Diffstat (limited to 'xbmc/cores/RetroPlayer/savestates')
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/CMakeLists.txt | 19 | ||||
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/ISavestate.h | 214 | ||||
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp | 272 | ||||
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h | 61 | ||||
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp | 637 | ||||
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h | 127 | ||||
-rw-r--r-- | xbmc/cores/RetroPlayer/savestates/SavestateTypes.h | 28 |
7 files changed, 1358 insertions, 0 deletions
diff --git a/xbmc/cores/RetroPlayer/savestates/CMakeLists.txt b/xbmc/cores/RetroPlayer/savestates/CMakeLists.txt new file mode 100644 index 0000000..956776d --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/CMakeLists.txt @@ -0,0 +1,19 @@ +set(SOURCES SavestateDatabase.cpp + SavestateFlatBuffer.cpp +) + +set(HEADERS ISavestate.h + SavestateDatabase.h + SavestateFlatBuffer.h + SavestateTypes.h +) + +core_add_library(retroplayer_savestates) + +set(DEPENDS retroplayer_messages) + +if(ENABLE_STATIC_LIBS) + add_dependencies(retroplayer_savestates ${DEPENDS}) +else() + add_dependencies(lib${APP_NAME_LC} ${DEPENDS}) +endif() diff --git a/xbmc/cores/RetroPlayer/savestates/ISavestate.h b/xbmc/cores/RetroPlayer/savestates/ISavestate.h new file mode 100644 index 0000000..c12d837 --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/ISavestate.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SavestateTypes.h" + +#include <stdint.h> +#include <string> +#include <vector> + +extern "C" +{ +#include <libavutil/pixfmt.h> +} + +class CDateTime; + +namespace KODI +{ +namespace RETRO +{ +class ISavestate +{ +public: + virtual ~ISavestate() = default; + + /*! + * \brief Reset to the initial state + */ + virtual void Reset() = 0; + + /*! + * Access the data representation of this savestate + */ + virtual bool Serialize(const uint8_t*& data, size_t& size) const = 0; + + /// @name Savestate properties + ///{ + /*! + * \brief The type of save action that created this savestate, either + * manual or automatic + */ + virtual SAVE_TYPE Type() const = 0; + + /*! + * \brief The slot this savestate was saved into, or 0 for no slot + * + * This allows for keyboard access of saved games using the number keys 1-9. + */ + virtual uint8_t Slot() const = 0; + + /*! + * \brief The label shown in the GUI for this savestate + */ + virtual std::string Label() const = 0; + + /*! + * \brief A caption that describes the state of the game for this savestate + */ + virtual std::string Caption() const = 0; + + /*! + * \brief The timestamp of this savestate's creation + */ + virtual CDateTime Created() const = 0; + ///} + + /// @name Game properties + ///{ + /*! + * \brief The name of the file belonging to this savestate's game + */ + virtual std::string GameFileName() const = 0; + ///} + + /// @name Environment properties + ///{ + /*! + * \brief The number of frames in the entire gameplay history + */ + virtual uint64_t TimestampFrames() const = 0; + + /*! + * \brief The duration of the entire gameplay history as seen by a wall clock + */ + virtual double TimestampWallClock() const = 0; + ///} + + /// @name Game client properties + ///{ + /*! + * \brief The game client add-on ID that created this savestate + */ + virtual std::string GameClientID() const = 0; + + /*! + * \brief The semantic version of the game client + */ + virtual std::string GameClientVersion() const = 0; + ///} + + /// @name Video stream properties + ///{ + /*! + * \brief The pixel format of the video stream + */ + virtual AVPixelFormat GetPixelFormat() const = 0; + + /*! + * \brief The nominal width of the video stream, a good guess for subsequent frames + */ + virtual unsigned int GetNominalWidth() const = 0; + + /*! + * \brief The nominal height of the video stream, a good guess for subsequent frames + */ + virtual unsigned int GetNominalHeight() const = 0; + + /*! + * \brief The maximum width of the video stream, in pixels + */ + virtual unsigned int GetMaxWidth() const = 0; + + /*! + * \brief The maximum height of the video stream, in pixels + */ + virtual unsigned int GetMaxHeight() const = 0; + + /*! + * \brief The pixel aspect ratio of the video stream + */ + virtual float GetPixelAspectRatio() const = 0; + ///} + + /// @name Video frame properties + ///{ + /*! + * \brief A pointer to the frame's video data (pixels) + */ + virtual const uint8_t* GetVideoData() const = 0; + + /*! + * \brief The size of the frame's video data, in bytes + */ + virtual size_t GetVideoSize() const = 0; + + /*! + * \brief The width of the video frame, in pixels + */ + virtual unsigned int GetVideoWidth() const = 0; + + /*! + * \brief The height of the video frame, in pixels + */ + virtual unsigned int GetVideoHeight() const = 0; + + /*! + * \brief The rotation of the video frame, in degrees counter-clockwise + */ + virtual unsigned int GetRotationDegCCW() const = 0; + ///} + + /// @name Memory properties + ///{ + /*! + * \brief A pointer to the internal memory (SRAM) of the frame + */ + virtual const uint8_t* GetMemoryData() const = 0; + + /*! + * \brief The size of the memory region returned by GetMemoryData() + */ + virtual size_t GetMemorySize() const = 0; + ///} + + /// @name Builders for setting individual fields + ///{ + virtual void SetType(SAVE_TYPE type) = 0; + virtual void SetSlot(uint8_t slot) = 0; + virtual void SetLabel(const std::string& label) = 0; + virtual void SetCaption(const std::string& caption) = 0; + virtual void SetCreated(const CDateTime& createdUTC) = 0; + virtual void SetGameFileName(const std::string& gameFileName) = 0; + virtual void SetTimestampFrames(uint64_t timestampFrames) = 0; + virtual void SetTimestampWallClock(double timestampWallClock) = 0; + virtual void SetGameClientID(const std::string& gameClient) = 0; + virtual void SetGameClientVersion(const std::string& gameClient) = 0; + virtual void SetPixelFormat(AVPixelFormat pixelFormat) = 0; + virtual void SetNominalWidth(unsigned int nominalWidth) = 0; + virtual void SetNominalHeight(unsigned int nominalHeight) = 0; + virtual void SetMaxWidth(unsigned int maxWidth) = 0; + virtual void SetMaxHeight(unsigned int maxHeight) = 0; + virtual void SetPixelAspectRatio(float pixelAspectRatio) = 0; + virtual uint8_t* GetVideoBuffer(size_t size) = 0; + virtual void SetVideoWidth(unsigned int videoWidth) = 0; + virtual void SetVideoHeight(unsigned int videoHeight) = 0; + virtual void SetRotationDegCCW(unsigned int rotationCCW) = 0; + virtual uint8_t* GetMemoryBuffer(size_t size) = 0; + virtual void Finalize() = 0; + ///} + + /*! + * \brief Take ownership and initialize the flatbuffer with the given vector + */ + virtual bool Deserialize(std::vector<uint8_t> data) = 0; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp new file mode 100644 index 0000000..2611f25 --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SavestateDatabase.h" + +#include "FileItem.h" +#include "SavestateFlatBuffer.h" +#include "URL.h" +#include "XBDateTime.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/IFileTypes.h" +#include "games/dialogs/DialogGameDefines.h" +#include "guilib/LocalizeStrings.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +namespace +{ +constexpr auto SAVESTATE_EXTENSION = ".sav"; +constexpr auto SAVESTATE_BASE_FOLDER = "special://home/saves/"; +} // namespace + +using namespace KODI; +using namespace RETRO; + +CSavestateDatabase::CSavestateDatabase() = default; + +std::unique_ptr<ISavestate> CSavestateDatabase::AllocateSavestate() +{ + std::unique_ptr<ISavestate> savestate; + + savestate.reset(new CSavestateFlatBuffer); + + return savestate; +} + +bool CSavestateDatabase::AddSavestate(const std::string& savestatePath, + const std::string& gamePath, + const ISavestate& save) +{ + bool bSuccess = false; + std::string path; + + if (savestatePath.empty()) + path = MakeSavestatePath(gamePath, save.Created()); + else + path = savestatePath; + + CLog::Log(LOGDEBUG, "Saving savestate to {}", CURL::GetRedacted(path)); + + const uint8_t* data = nullptr; + size_t size = 0; + if (save.Serialize(data, size)) + { + XFILE::CFile file; + if (file.OpenForWrite(path, true)) + { + const ssize_t written = file.Write(data, size); + if (written == static_cast<ssize_t>(size)) + { + CLog::Log(LOGDEBUG, "Wrote savestate of {} bytes", size); + bSuccess = true; + } + } + else + CLog::Log(LOGERROR, "Failed to open savestate for writing"); + } + + return bSuccess; +} + +bool CSavestateDatabase::GetSavestate(const std::string& savestatePath, ISavestate& save) +{ + bool bSuccess = false; + + CLog::Log(LOGDEBUG, "Loading savestate from {}", CURL::GetRedacted(savestatePath)); + + std::vector<uint8_t> savestateData; + + XFILE::CFile savestateFile; + if (savestateFile.Open(savestatePath, XFILE::READ_TRUNCATED)) + { + int64_t size = savestateFile.GetLength(); + if (size > 0) + { + savestateData.resize(static_cast<size_t>(size)); + + const ssize_t readLength = savestateFile.Read(savestateData.data(), savestateData.size()); + if (readLength != static_cast<ssize_t>(savestateData.size())) + { + CLog::Log(LOGERROR, "Failed to read savestate {} of size {} bytes", + CURL::GetRedacted(savestatePath), size); + savestateData.clear(); + } + } + else + CLog::Log(LOGERROR, "Failed to get savestate length: {}", CURL::GetRedacted(savestatePath)); + } + else + CLog::Log(LOGERROR, "Failed to open savestate file {}", CURL::GetRedacted(savestatePath)); + + if (!savestateData.empty()) + bSuccess = save.Deserialize(std::move(savestateData)); + + return bSuccess; +} + +bool CSavestateDatabase::GetSavestatesNav(CFileItemList& items, + const std::string& gamePath, + const std::string& gameClient /* = "" */) +{ + const std::string savesFolder = MakePath(gamePath); + + XFILE::CDirectory::CHints hints; + hints.mask = SAVESTATE_EXTENSION; + + if (!XFILE::CDirectory::GetDirectory(savesFolder, items, hints)) + return false; + + if (!gameClient.empty()) + { + for (int i = items.Size() - 1; i >= 0; i--) + { + std::unique_ptr<ISavestate> save = AllocateSavestate(); + GetSavestate(items[i]->GetPath(), *save); + if (save->GameClientID() != gameClient) + items.Remove(i); + } + } + + for (int i = 0; i < items.Size(); i++) + { + std::unique_ptr<ISavestate> savestate = AllocateSavestate(); + GetSavestate(items[i]->GetPath(), *savestate); + + GetSavestateItem(*savestate, items[i]->GetPath(), *items[i]); + } + + return true; +} + +void CSavestateDatabase::GetSavestateItem(const ISavestate& savestate, + const std::string& savestatePath, + CFileItem& item) +{ + CDateTime dateUTC = CDateTime::FromUTCDateTime(savestate.Created()); + + std::string label; + std::string label2; + + // Date has the lowest priority of being shown + label = dateUTC.GetAsLocalizedDateTime(false, false); + + // Label has the next priority + if (!savestate.Label().empty()) + { + label2 = std::move(label); + label = savestate.Label(); + } + + // "Autosave" has the highest priority + if (savestate.Type() == SAVE_TYPE::AUTO) + { + label2 = std::move(label); + label = g_localizeStrings.Get(15316); // "Autosave" + } + + item.SetLabel(label); + item.SetLabel2(label2); + item.SetPath(savestatePath); + item.SetArt("screenshot", MakeThumbnailPath(savestatePath)); + item.SetProperty(SAVESTATE_LABEL, savestate.Label()); + item.SetProperty(SAVESTATE_CAPTION, savestate.Caption()); + item.SetProperty(SAVESTATE_GAME_CLIENT, savestate.GameClientID()); + item.m_dateTime = dateUTC; +} + +std::unique_ptr<ISavestate> CSavestateDatabase::RenameSavestate(const std::string& savestatePath, + const std::string& label) +{ + std::unique_ptr<ISavestate> savestate = AllocateSavestate(); + if (!GetSavestate(savestatePath, *savestate)) + return {}; + + std::unique_ptr<ISavestate> newSavestate = AllocateSavestate(); + + newSavestate->SetLabel(label); + newSavestate->SetCaption(savestate->Caption()); + newSavestate->SetType(savestate->Type()); + newSavestate->SetCreated(savestate->Created()); + newSavestate->SetGameFileName(savestate->GameFileName()); + newSavestate->SetTimestampFrames(savestate->TimestampFrames()); + newSavestate->SetTimestampWallClock(savestate->TimestampWallClock()); + newSavestate->SetGameClientID(savestate->GameClientID()); + newSavestate->SetGameClientVersion(savestate->GameClientVersion()); + + size_t memorySize = savestate->GetMemorySize(); + std::memcpy(newSavestate->GetMemoryBuffer(memorySize), savestate->GetMemoryData(), memorySize); + + newSavestate->Finalize(); + + std::string path = savestatePath; + if (!AddSavestate(path, "", *newSavestate)) + return {}; + + return newSavestate; +} + +bool CSavestateDatabase::DeleteSavestate(const std::string& savestatePath) +{ + if (!XFILE::CFile::Delete(savestatePath)) + { + CLog::Log(LOGERROR, "Failed to delete savestate file {}", CURL::GetRedacted(savestatePath)); + return false; + } + + XFILE::CFile::Delete(MakeThumbnailPath(savestatePath)); + return true; +} + +bool CSavestateDatabase::ClearSavestatesOfGame(const std::string& gamePath, + const std::string& gameClient /* = "" */) +{ + //! @todo + return false; +} + +std::string CSavestateDatabase::MakeSavestatePath(const std::string& gamePath, + const CDateTime& creationTime) +{ + std::string path = MakePath(gamePath); + return URIUtils::AddFileToFolder(path, creationTime.GetAsSaveString() + SAVESTATE_EXTENSION); +} + +std::string CSavestateDatabase::MakeThumbnailPath(const std::string& savestatePath) +{ + return URIUtils::ReplaceExtension(savestatePath, ".jpg"); +} + +std::string CSavestateDatabase::MakePath(const std::string& gamePath) +{ + if (!CreateFolderIfNotExists(SAVESTATE_BASE_FOLDER)) + return ""; + + std::string gameName = URIUtils::GetFileName(gamePath); + std::string folderPath = URIUtils::AddFileToFolder(SAVESTATE_BASE_FOLDER, gameName); + + if (!CreateFolderIfNotExists(folderPath)) + return ""; + + return folderPath; +} + +bool CSavestateDatabase::CreateFolderIfNotExists(const std::string& path) +{ + if (!XFILE::CDirectory::Exists(path)) + { + if (!XFILE::CDirectory::Create(path)) + { + CLog::Log(LOGERROR, "Failed to create folder: {}", path); + return false; + } + } + + return true; +} diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h new file mode 100644 index 0000000..b084771 --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class CDateTime; +class CFileItem; +class CFileItemList; + +namespace KODI +{ +namespace RETRO +{ +class ISavestate; + +class CSavestateDatabase +{ +public: + CSavestateDatabase(); + virtual ~CSavestateDatabase() = default; + + static std::unique_ptr<ISavestate> AllocateSavestate(); + + bool AddSavestate(const std::string& savestatePath, + const std::string& gamePath, + const ISavestate& save); + + bool GetSavestate(const std::string& savestatePath, ISavestate& save); + + bool GetSavestatesNav(CFileItemList& items, + const std::string& gamePath, + const std::string& gameClient = ""); + + static void GetSavestateItem(const ISavestate& savestate, + const std::string& savestatePath, + CFileItem& item); + + std::unique_ptr<ISavestate> RenameSavestate(const std::string& savestatePath, + const std::string& label); + + bool DeleteSavestate(const std::string& savestatePath); + + bool ClearSavestatesOfGame(const std::string& gamePath, const std::string& gameClient = ""); + + static std::string MakeSavestatePath(const std::string& gamePath, const CDateTime& creationTime); + static std::string MakeThumbnailPath(const std::string& savestatePath); + +private: + static std::string MakePath(const std::string& gamePath); + static bool CreateFolderIfNotExists(const std::string& path); +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp new file mode 100644 index 0000000..9b98bde --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SavestateFlatBuffer.h" + +#include "XBDateTime.h" +#include "savestate_generated.h" +#include "utils/log.h" +#include "video_generated.h" + +using namespace KODI; +using namespace RETRO; + +namespace +{ +const uint8_t SCHEMA_VERSION = 3; +const uint8_t SCHEMA_MIN_VERSION = 1; + +/*! + * \brief The initial size of the FlatBuffer's memory buffer + * + * 1024 is the default size in the FlatBuffers header. We might as well use + * this until our size requirements are more known. + */ +const size_t INITIAL_FLATBUFFER_SIZE = 1024; + +/*! + * \brief Translate the save type (RetroPlayer to FlatBuffers) + */ +SaveType TranslateType(SAVE_TYPE type) +{ + switch (type) + { + case SAVE_TYPE::AUTO: + return SaveType_Auto; + case SAVE_TYPE::MANUAL: + return SaveType_Manual; + default: + break; + } + + return SaveType_Unknown; +} + +/*! + * \brief Translate the save type (FlatBuffers to RetroPlayer) + */ +SAVE_TYPE TranslateType(SaveType type) +{ + switch (type) + { + case SaveType_Auto: + return SAVE_TYPE::AUTO; + case SaveType_Manual: + return SAVE_TYPE::MANUAL; + default: + break; + } + + return SAVE_TYPE::UNKNOWN; +} + +/*! + * \brief Translate the video pixel format (RetroPlayer to FlatBuffers) + */ +PixelFormat TranslatePixelFormat(AVPixelFormat pixelFormat) +{ + switch (pixelFormat) + { + case AV_PIX_FMT_RGBA: + return PixelFormat_RGBA_8888; + + case AV_PIX_FMT_0RGB32: +#if defined(__BIG_ENDIAN__) + return PixelFormat_XRGB_8888; +#else + return PixelFormat_BGRX_8888; +#endif + + case AV_PIX_FMT_RGB565: +#if defined(__BIG_ENDIAN__) + return PixelFormat_RGB_565_BE; +#else + return PixelFormat_RGB_565_LE; +#endif + + case AV_PIX_FMT_RGB555: +#if defined(__BIG_ENDIAN__) + return PixelFormat_RGB_555_BE; +#else + return PixelFormat_RGB_555_LE; +#endif + + default: + break; + } + + return PixelFormat_Unknown; +} + +/*! + * \brief Translate the video pixel format (FlatBuffers to RetroPlayer) + */ +AVPixelFormat TranslatePixelFormat(PixelFormat pixelFormat) +{ + switch (pixelFormat) + { + case PixelFormat_RGBA_8888: + return AV_PIX_FMT_RGBA; + + case PixelFormat_XRGB_8888: + return AV_PIX_FMT_0RGB; + + case PixelFormat_BGRX_8888: + return AV_PIX_FMT_BGR0; + + case PixelFormat_RGB_565_BE: + return AV_PIX_FMT_RGB565BE; + + case PixelFormat_RGB_565_LE: + return AV_PIX_FMT_RGB565LE; + + case PixelFormat_RGB_555_BE: + return AV_PIX_FMT_RGB555BE; + + case PixelFormat_RGB_555_LE: + return AV_PIX_FMT_RGB555LE; + + default: + break; + } + + return AV_PIX_FMT_NONE; +} + +/*! + * \brief Translate the video rotation (RetroPlayer to FlatBuffers) + */ +VideoRotation TranslateRotation(unsigned int rotationCCW) +{ + switch (rotationCCW) + { + case 0: + return VideoRotation_CCW_0; + case 90: + return VideoRotation_CCW_90; + case 180: + return VideoRotation_CCW_180; + case 270: + return VideoRotation_CCW_270; + default: + break; + } + + return VideoRotation_CCW_0; +} + +/*! + * \brief Translate the video rotation (RetroPlayer to FlatBuffers) + */ +unsigned int TranslateRotation(VideoRotation rotationCCW) +{ + switch (rotationCCW) + { + case VideoRotation_CCW_0: + return 0; + case VideoRotation_CCW_90: + return 90; + case VideoRotation_CCW_180: + return 180; + case VideoRotation_CCW_270: + return 270; + default: + break; + } + + return 0; +} +} // namespace + +CSavestateFlatBuffer::CSavestateFlatBuffer() +{ + Reset(); +} + +CSavestateFlatBuffer::~CSavestateFlatBuffer() = default; + +void CSavestateFlatBuffer::Reset() +{ + m_builder.reset(new flatbuffers::FlatBufferBuilder(INITIAL_FLATBUFFER_SIZE)); + m_data.clear(); + m_savestate = nullptr; +} + +bool CSavestateFlatBuffer::Serialize(const uint8_t*& data, size_t& size) const +{ + // Check if savestate was deserialized from vector or built with FlatBuffers + if (!m_data.empty()) + { + data = m_data.data(); + size = m_data.size(); + } + else + { + data = m_builder->GetBufferPointer(); + size = m_builder->GetSize(); + } + + return true; +} + +SAVE_TYPE CSavestateFlatBuffer::Type() const +{ + if (m_savestate != nullptr) + return TranslateType(m_savestate->type()); + + return SAVE_TYPE::UNKNOWN; +} + +void CSavestateFlatBuffer::SetType(SAVE_TYPE type) +{ + m_type = type; +} + +uint8_t CSavestateFlatBuffer::Slot() const +{ + if (m_savestate != nullptr) + return m_savestate->slot(); + + return 0; +} + +void CSavestateFlatBuffer::SetSlot(uint8_t slot) +{ + m_slot = slot; +} + +std::string CSavestateFlatBuffer::Label() const +{ + std::string label; + + if (m_savestate != nullptr && m_savestate->label()) + label = m_savestate->label()->c_str(); + + return label; +} + +void CSavestateFlatBuffer::SetLabel(const std::string& label) +{ + m_labelOffset.reset(new StringOffset{m_builder->CreateString(label)}); +} + +std::string CSavestateFlatBuffer::Caption() const +{ + std::string caption; + + if (m_savestate != nullptr && m_savestate->caption()) + caption = m_savestate->caption()->str(); + + return caption; +} + +void CSavestateFlatBuffer::SetCaption(const std::string& caption) +{ + m_captionOffset = std::make_unique<StringOffset>(m_builder->CreateString(caption)); +} + +CDateTime CSavestateFlatBuffer::Created() const +{ + CDateTime createdUTC; + + if (m_savestate != nullptr && m_savestate->created()) + createdUTC.SetFromW3CDateTime(m_savestate->created()->c_str(), false); + + return createdUTC; +} + +void CSavestateFlatBuffer::SetCreated(const CDateTime& createdUTC) +{ + m_createdOffset = + std::make_unique<StringOffset>(m_builder->CreateString(createdUTC.GetAsW3CDateTime(true))); +} + +std::string CSavestateFlatBuffer::GameFileName() const +{ + std::string gameFileName; + + if (m_savestate != nullptr && m_savestate->game_file_name()) + gameFileName = m_savestate->game_file_name()->c_str(); + + return gameFileName; +} + +void CSavestateFlatBuffer::SetGameFileName(const std::string& gameFileName) +{ + m_gameFileNameOffset.reset(new StringOffset{m_builder->CreateString(gameFileName)}); +} + +uint64_t CSavestateFlatBuffer::TimestampFrames() const +{ + return m_savestate->timestamp_frames(); +} + +void CSavestateFlatBuffer::SetTimestampFrames(uint64_t timestampFrames) +{ + m_timestampFrames = timestampFrames; +} + +double CSavestateFlatBuffer::TimestampWallClock() const +{ + if (m_savestate != nullptr) + return static_cast<double>(m_savestate->timestamp_wall_clock_ns()) / 1000.0 / 1000.0 / 1000.0; + + return 0.0; +} + +void CSavestateFlatBuffer::SetTimestampWallClock(double timestampWallClock) +{ + m_timestampWallClock = timestampWallClock; +} + +std::string CSavestateFlatBuffer::GameClientID() const +{ + std::string gameClientId; + + if (m_savestate != nullptr && m_savestate->emulator_addon_id()) + gameClientId = m_savestate->emulator_addon_id()->c_str(); + + return gameClientId; +} + +void CSavestateFlatBuffer::SetGameClientID(const std::string& gameClientId) +{ + m_emulatorAddonIdOffset.reset(new StringOffset{m_builder->CreateString(gameClientId)}); +} + +std::string CSavestateFlatBuffer::GameClientVersion() const +{ + std::string gameClientVersion; + + if (m_savestate != nullptr && m_savestate->emulator_version()) + gameClientVersion = m_savestate->emulator_version()->c_str(); + + return gameClientVersion; +} + +void CSavestateFlatBuffer::SetGameClientVersion(const std::string& gameClientVersion) +{ + m_emulatorVersionOffset.reset(new StringOffset{m_builder->CreateString(gameClientVersion)}); +} + +AVPixelFormat CSavestateFlatBuffer::GetPixelFormat() const +{ + if (m_savestate != nullptr) + return TranslatePixelFormat(m_savestate->pixel_format()); + + return AV_PIX_FMT_NONE; +} + +void CSavestateFlatBuffer::SetPixelFormat(AVPixelFormat pixelFormat) +{ + m_pixelFormat = pixelFormat; +} + +unsigned int CSavestateFlatBuffer::GetNominalWidth() const +{ + if (m_savestate != nullptr) + return m_savestate->nominal_width(); + + return 0; +} + +void CSavestateFlatBuffer::SetNominalWidth(unsigned int nominalWidth) +{ + m_nominalWidth = nominalWidth; +} + +unsigned int CSavestateFlatBuffer::GetNominalHeight() const +{ + if (m_savestate != nullptr) + return m_savestate->nominal_height(); + + return 0; +} + +void CSavestateFlatBuffer::SetNominalHeight(unsigned int nominalHeight) +{ + m_nominalHeight = nominalHeight; +} + +unsigned int CSavestateFlatBuffer::GetMaxWidth() const +{ + if (m_savestate != nullptr) + return m_savestate->max_width(); + + return 0; +} + +void CSavestateFlatBuffer::SetMaxWidth(unsigned int maxWidth) +{ + m_maxWidth = maxWidth; +} + +unsigned int CSavestateFlatBuffer::GetMaxHeight() const +{ + if (m_savestate != nullptr) + return m_savestate->max_height(); + + return 0; +} + +void CSavestateFlatBuffer::SetMaxHeight(unsigned int maxHeight) +{ + m_maxHeight = maxHeight; +} + +float CSavestateFlatBuffer::GetPixelAspectRatio() const +{ + if (m_savestate != nullptr) + return m_savestate->pixel_aspect_ratio(); + + return 0.0f; +} + +void CSavestateFlatBuffer::SetPixelAspectRatio(float pixelAspectRatio) +{ + m_pixelAspectRatio = pixelAspectRatio; +} + +const uint8_t* CSavestateFlatBuffer::GetVideoData() const +{ + if (m_savestate != nullptr && m_savestate->video_data()) + return m_savestate->video_data()->data(); + + return nullptr; +} + +size_t CSavestateFlatBuffer::GetVideoSize() const +{ + if (m_savestate != nullptr && m_savestate->video_data()) + return m_savestate->video_data()->size(); + + return 0; +} + +uint8_t* CSavestateFlatBuffer::GetVideoBuffer(size_t size) +{ + uint8_t* videoBuffer = nullptr; + + m_videoDataOffset = + std::make_unique<VectorOffset>(m_builder->CreateUninitializedVector(size, &videoBuffer)); + + return videoBuffer; +} + +unsigned int CSavestateFlatBuffer::GetVideoWidth() const +{ + if (m_savestate != nullptr) + return m_savestate->video_width(); + + return 0; +} + +void CSavestateFlatBuffer::SetVideoWidth(unsigned int videoWidth) +{ + m_videoWidth = videoWidth; +} + +unsigned int CSavestateFlatBuffer::GetVideoHeight() const +{ + if (m_savestate != nullptr) + return m_savestate->video_height(); + + return 0; +} + +void CSavestateFlatBuffer::SetVideoHeight(unsigned int videoHeight) +{ + m_videoHeight = videoHeight; +} + +unsigned int CSavestateFlatBuffer::GetRotationDegCCW() const +{ + if (m_savestate != nullptr) + return TranslateRotation(m_savestate->rotation_ccw()); + + return 0; +} + +void CSavestateFlatBuffer::SetRotationDegCCW(unsigned int rotationCCW) +{ + m_rotationCCW = rotationCCW; +} + +const uint8_t* CSavestateFlatBuffer::GetMemoryData() const +{ + if (m_savestate != nullptr && m_savestate->memory_data()) + return m_savestate->memory_data()->data(); + + return nullptr; +} + +size_t CSavestateFlatBuffer::GetMemorySize() const +{ + if (m_savestate != nullptr && m_savestate->memory_data()) + return m_savestate->memory_data()->size(); + + return 0; +} + +uint8_t* CSavestateFlatBuffer::GetMemoryBuffer(size_t size) +{ + uint8_t* memoryBuffer = nullptr; + + m_memoryDataOffset.reset( + new VectorOffset{m_builder->CreateUninitializedVector(size, &memoryBuffer)}); + + return memoryBuffer; +} + +void CSavestateFlatBuffer::Finalize() +{ + // Helper class to build the nested Savestate table + SavestateBuilder savestateBuilder(*m_builder); + + savestateBuilder.add_version(SCHEMA_VERSION); + + savestateBuilder.add_type(TranslateType(m_type)); + + savestateBuilder.add_slot(m_slot); + + if (m_labelOffset) + { + savestateBuilder.add_label(*m_labelOffset); + m_labelOffset.reset(); + } + + if (m_captionOffset) + { + savestateBuilder.add_caption(*m_captionOffset); + m_captionOffset.reset(); + } + + if (m_createdOffset) + { + savestateBuilder.add_created(*m_createdOffset); + m_createdOffset.reset(); + } + + if (m_gameFileNameOffset) + { + savestateBuilder.add_game_file_name(*m_gameFileNameOffset); + m_gameFileNameOffset.reset(); + } + + savestateBuilder.add_timestamp_frames(m_timestampFrames); + + const uint64_t wallClockNs = + static_cast<uint64_t>(m_timestampWallClock * 1000.0 * 1000.0 * 1000.0); + savestateBuilder.add_timestamp_wall_clock_ns(wallClockNs); + + if (m_emulatorAddonIdOffset) + { + savestateBuilder.add_emulator_addon_id(*m_emulatorAddonIdOffset); + m_emulatorAddonIdOffset.reset(); + } + + if (m_emulatorVersionOffset) + { + savestateBuilder.add_emulator_version(*m_emulatorVersionOffset); + m_emulatorVersionOffset.reset(); + } + + savestateBuilder.add_pixel_format(TranslatePixelFormat(m_pixelFormat)); + + savestateBuilder.add_nominal_width(m_nominalWidth); + + savestateBuilder.add_nominal_height(m_nominalHeight); + + savestateBuilder.add_max_width(m_maxWidth); + + savestateBuilder.add_max_height(m_maxHeight); + + savestateBuilder.add_pixel_aspect_ratio(m_pixelAspectRatio); + + if (m_videoDataOffset) + { + savestateBuilder.add_video_data(*m_videoDataOffset); + m_videoDataOffset.reset(); + } + + savestateBuilder.add_video_width(m_videoWidth); + + savestateBuilder.add_video_height(m_videoHeight); + + savestateBuilder.add_rotation_ccw(TranslateRotation(m_rotationCCW)); + + if (m_memoryDataOffset) + { + savestateBuilder.add_memory_data(*m_memoryDataOffset); + m_memoryDataOffset.reset(); + } + + auto savestate = savestateBuilder.Finish(); + FinishSavestateBuffer(*m_builder, savestate); + + m_savestate = GetSavestate(m_builder->GetBufferPointer()); +} + +bool CSavestateFlatBuffer::Deserialize(std::vector<uint8_t> data) +{ + flatbuffers::Verifier verifier(data.data(), data.size()); + if (VerifySavestateBuffer(verifier)) + { + const Savestate* savestate = GetSavestate(data.data()); + + if (savestate->version() < SCHEMA_MIN_VERSION) + { + CLog::Log(LOGERROR, + "RetroPlayer[SAVE): Schema version {} not supported, must be at least version {}", + savestate->version(), SCHEMA_MIN_VERSION); + } + else + { + m_data = std::move(data); + m_savestate = GetSavestate(m_data.data()); + return true; + } + } + + return false; +} diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h new file mode 100644 index 0000000..fa42a9b --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISavestate.h" + +#include <memory> + +#include <flatbuffers/flatbuffers.h> + +namespace KODI +{ +namespace RETRO +{ +struct Savestate; +struct SavestateBuilder; + +class CSavestateFlatBuffer : public ISavestate +{ +public: + CSavestateFlatBuffer(); + ~CSavestateFlatBuffer() override; + + // Implementation of ISavestate + void Reset() override; + bool Serialize(const uint8_t*& data, size_t& size) const override; + SAVE_TYPE Type() const override; + uint8_t Slot() const override; + std::string Label() const override; + std::string Caption() const override; + CDateTime Created() const override; + std::string GameFileName() const override; + uint64_t TimestampFrames() const override; + double TimestampWallClock() const override; + std::string GameClientID() const override; + std::string GameClientVersion() const override; + AVPixelFormat GetPixelFormat() const override; + unsigned int GetNominalWidth() const override; + unsigned int GetNominalHeight() const override; + unsigned int GetMaxWidth() const override; + unsigned int GetMaxHeight() const override; + float GetPixelAspectRatio() const override; + const uint8_t* GetVideoData() const override; + size_t GetVideoSize() const override; + unsigned int GetVideoWidth() const override; + unsigned int GetVideoHeight() const override; + unsigned int GetRotationDegCCW() const override; + const uint8_t* GetMemoryData() const override; + size_t GetMemorySize() const override; + void SetType(SAVE_TYPE type) override; + void SetSlot(uint8_t slot) override; + void SetLabel(const std::string& label) override; + void SetCaption(const std::string& caption) override; + void SetCreated(const CDateTime& createdUTC) override; + void SetGameFileName(const std::string& gameFileName) override; + void SetTimestampFrames(uint64_t timestampFrames) override; + void SetTimestampWallClock(double timestampWallClock) override; + void SetGameClientID(const std::string& gameClient) override; + void SetGameClientVersion(const std::string& gameClient) override; + void SetPixelFormat(AVPixelFormat pixelFormat) override; + void SetNominalWidth(unsigned int nominalWidth) override; + void SetNominalHeight(unsigned int nominalHeight) override; + void SetMaxWidth(unsigned int maxWidth) override; + void SetMaxHeight(unsigned int maxHeight) override; + void SetPixelAspectRatio(float pixelAspectRatio) override; + uint8_t* GetVideoBuffer(size_t size) override; + void SetVideoWidth(unsigned int videoWidth) override; + void SetVideoHeight(unsigned int videoHeight) override; + void SetRotationDegCCW(unsigned int rotationCCW) override; + uint8_t* GetMemoryBuffer(size_t size) override; + void Finalize() override; + bool Deserialize(std::vector<uint8_t> data) override; + +private: + /*! + * \brief Helper class to hold data needed in creation of a FlatBuffer + * + * The builder is used when deserializing from individual fields. + */ + std::unique_ptr<flatbuffers::FlatBufferBuilder> m_builder; + + /*! + * \brief System memory storage (for deserializing savestates) + * + * This memory is used when deserializing from a vector. + */ + std::vector<uint8_t> m_data; + + /*! + * \brief FlatBuffer struct used for accessing data + */ + const Savestate* m_savestate = nullptr; + + using StringOffset = flatbuffers::Offset<flatbuffers::String>; + using VectorOffset = flatbuffers::Offset<flatbuffers::Vector<uint8_t>>; + + // Temporary deserialization variables + SAVE_TYPE m_type = SAVE_TYPE::UNKNOWN; + uint8_t m_slot = 0; + std::unique_ptr<StringOffset> m_labelOffset; + std::unique_ptr<StringOffset> m_captionOffset; + std::unique_ptr<StringOffset> m_createdOffset; + std::unique_ptr<StringOffset> m_gameFileNameOffset; + uint64_t m_timestampFrames = 0; + double m_timestampWallClock = 0.0; + std::unique_ptr<StringOffset> m_emulatorAddonIdOffset; + std::unique_ptr<StringOffset> m_emulatorVersionOffset; + AVPixelFormat m_pixelFormat{AV_PIX_FMT_NONE}; + unsigned int m_nominalWidth{0}; + unsigned int m_nominalHeight{0}; + unsigned int m_maxWidth{0}; + unsigned int m_maxHeight{0}; + float m_pixelAspectRatio{0.0f}; + std::unique_ptr<VectorOffset> m_videoDataOffset; + unsigned int m_videoWidth{0}; + unsigned int m_videoHeight{0}; + unsigned int m_rotationCCW{0}; + std::unique_ptr<VectorOffset> m_memoryDataOffset; +}; +} // namespace RETRO +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateTypes.h b/xbmc/cores/RetroPlayer/savestates/SavestateTypes.h new file mode 100644 index 0000000..e4bbc9b --- /dev/null +++ b/xbmc/cores/RetroPlayer/savestates/SavestateTypes.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace KODI +{ +namespace RETRO +{ +/*! + * \brief Type of save action, either: + * + * - automatic (saving was not prompted by the user) + * - manual (user manually prompted the save) + */ +enum class SAVE_TYPE +{ + UNKNOWN, + AUTO, + MANUAL, +}; +} // namespace RETRO +} // namespace KODI |