summaryrefslogtreecommitdiffstats
path: root/xbmc/cores/RetroPlayer/savestates
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/cores/RetroPlayer/savestates')
-rw-r--r--xbmc/cores/RetroPlayer/savestates/CMakeLists.txt19
-rw-r--r--xbmc/cores/RetroPlayer/savestates/ISavestate.h214
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp272
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateDatabase.h61
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp637
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h127
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateTypes.h28
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