aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNickSiak <nikos.siakas@gmail.com>2021-11-06 14:14:25 +0200
committerNickSiak <nikos.siakas@gmail.com>2022-03-23 20:44:54 +0200
commit99389a5425301120fcf3b06f0d74e579b5a0167e (patch)
tree95ab12a276eabc8b45e280baaec47bbcbfd81f5c
parent0d91930f19527923949d484f9008f87651bd7e86 (diff)
RetroPlayer: Cheevos subsystem
-rw-r--r--addons/resource.language.en_gb/resources/strings.po8
-rw-r--r--addons/skin.estuary/xml/Includes_DialogSelect.xml15
-rw-r--r--cmake/treedata/common/retroplayer.txt1
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayer.cpp16
-rw-r--r--xbmc/cores/RetroPlayer/RetroPlayer.h2
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt6
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp162
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/Cheevos.h89
-rw-r--r--xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h85
-rw-r--r--xbmc/cores/RetroPlayer/messages/savestate.fbs25
-rw-r--r--xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp10
-rw-r--r--xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h3
-rw-r--r--xbmc/cores/RetroPlayer/savestates/ISavestate.h6
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp3
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp31
-rw-r--r--xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h3
-rw-r--r--xbmc/games/dialogs/CMakeLists.txt3
-rw-r--r--xbmc/games/dialogs/DialogGameDefines.h12
-rw-r--r--xbmc/games/dialogs/osd/DialogGameSaves.cpp89
-rw-r--r--xbmc/games/dialogs/osd/DialogGameSaves.h31
-rw-r--r--xbmc/games/dialogs/osd/DialogInGameSaves.cpp3
21 files changed, 569 insertions, 34 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po
index 8d1b9a5fab..3ead73bba1 100644
--- a/addons/resource.language.en_gb/resources/strings.po
+++ b/addons/resource.language.en_gb/resources/strings.po
@@ -8728,7 +8728,13 @@ msgctxt "#15314"
msgid "Save progress"
msgstr ""
-#empty strings from id 15315 to 15999
+#. Caption for "Save progress" button in the in-game savestate manager
+#: xbmc/games/dialogs/osd/DialogInGameSaves.cpp
+msgctxt "#15315"
+msgid "Save progress to new save file"
+msgstr ""
+
+#empty strings from id 15316 to 15999
#: system/settings/settings.xml
msgctxt "#16000"
diff --git a/addons/skin.estuary/xml/Includes_DialogSelect.xml b/addons/skin.estuary/xml/Includes_DialogSelect.xml
index 7d559c7801..85e49bdc55 100644
--- a/addons/skin.estuary/xml/Includes_DialogSelect.xml
+++ b/addons/skin.estuary/xml/Includes_DialogSelect.xml
@@ -160,7 +160,7 @@
<width>1220</width>
<include content="DialogBackgroundCommons">
<param name="width" value="1220" />
- <param name="height" value="750" />
+ <param name="height" value="780" />
<param name="header_label" value="" />
<param name="header_id" value="1" />
</include>
@@ -304,6 +304,18 @@
<param name="label" value="$LOCALIZE[222]" />
</include>
</control>
+ <control type="label" id="12">
+ <description>Caption area</description>
+ <left>14</left>
+ <right>14</right>
+ <bottom>0</bottom>
+ <height>20</height>
+ <font>font12</font>
+ <align>left</align>
+ <shadowcolor>text_shadow</shadowcolor>
+ <autoscroll time="3000" delay="5000" repeat="5000">true</autoscroll>
+ <label></label>
+ </control>
</control>
</include>
<include name="GameDialogSelectFilterLayout">
@@ -596,6 +608,7 @@
<align>justify</align>
<shadowcolor>text_shadow</shadowcolor>
<autoscroll time="3000" delay="5000" repeat="5000">true</autoscroll>
+ <label>$INFO[ListItem.Property(savestate.caption)]</label>
</control>
</control>
</include>
diff --git a/cmake/treedata/common/retroplayer.txt b/cmake/treedata/common/retroplayer.txt
index 97fc849a02..fd2c7f5c43 100644
--- a/cmake/treedata/common/retroplayer.txt
+++ b/cmake/treedata/common/retroplayer.txt
@@ -2,6 +2,7 @@ xbmc/cores/RetroPlayer cores/RetroPlaye
xbmc/cores/RetroPlayer/audio cores/RetroPlayer/audio
xbmc/cores/RetroPlayer/buffers cores/RetroPlayer/buffers
xbmc/cores/RetroPlayer/buffers/video cores/RetroPlayer/buffers/video
+xbmc/cores/RetroPlayer/cheevos cores/RetroPlayer/cheevos
xbmc/cores/RetroPlayer/guibridge cores/RetroPlayer/guibridge
xbmc/cores/RetroPlayer/guicontrols cores/RetroPlayer/guicontrols
xbmc/cores/RetroPlayer/guiplayback cores/RetroPlayer/guiplayback
diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp
index ef382124d3..7b7b260892 100644
--- a/xbmc/cores/RetroPlayer/RetroPlayer.cpp
+++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp
@@ -16,6 +16,7 @@
#include "addons/AddonManager.h"
#include "cores/DataCacheCore.h"
#include "cores/IPlayerCallback.h"
+#include "cores/RetroPlayer/cheevos/Cheevos.h"
#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
#include "cores/RetroPlayer/guiplayback/GUIPlaybackControl.h"
#include "cores/RetroPlayer/playback/IPlayback.h"
@@ -180,6 +181,12 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options
// Switch to fullscreen
CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN);
+ m_cheevos = std::make_shared<CCheevos>(m_gameClient.get(),
+ m_gameServices.GameSettings().GetRAUsername(),
+ m_gameServices.GameSettings().GetRAToken());
+
+ m_cheevos->EnableRichPresence();
+
// Initialize gameplay
CreatePlayback(m_gameServices.GameSettings().AutosaveEnabled(), savestatePath);
RegisterWindowCallbacks();
@@ -233,6 +240,8 @@ bool CRetroPlayer::CloseFile(bool reopen /* = false */)
m_input.reset();
m_streamManager.reset();
+ m_cheevos.reset();
+
if (m_gameClient)
m_gameClient->Unload();
m_gameClient.reset();
@@ -408,6 +417,7 @@ bool CRetroPlayer::OnAction(const CAction& action)
m_playback->SetSpeed(0.0);
CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Sending reset command via ACTION_PLAYER_RESET");
+ m_cheevos->ResetRuntime();
m_gameClient->Input().HardwareReset();
// If rewinding or paused, begin playback
@@ -583,9 +593,9 @@ void CRetroPlayer::CreatePlayback(bool bRestoreState, const std::string& savesta
if (m_gameClient->RequiresGameLoop())
{
m_playback->Deinitialize();
- m_playback.reset(new CReversiblePlayback(m_gameClient.get(), *m_renderManager,
- m_gameClient->GetFrameRate(),
- m_gameClient->GetSerializeSize()));
+ m_playback = std::make_unique<CReversiblePlayback>(
+ m_gameClient.get(), *m_renderManager, m_cheevos.get(), m_gameClient->GetFrameRate(),
+ m_gameClient->GetSerializeSize());
}
else
ResetPlayback();
diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.h b/xbmc/cores/RetroPlayer/RetroPlayer.h
index ddb9196208..7c2581754b 100644
--- a/xbmc/cores/RetroPlayer/RetroPlayer.h
+++ b/xbmc/cores/RetroPlayer/RetroPlayer.h
@@ -27,6 +27,7 @@ class CGameServices;
namespace RETRO
{
+class CCheevos;
class CRetroPlayerInput;
class CRPProcessInfo;
class CRPRenderManager;
@@ -128,6 +129,7 @@ private:
std::unique_ptr<IPlayback> m_playback;
std::unique_ptr<IPlaybackControl> m_playbackControl;
std::unique_ptr<CRetroPlayerAutoSave> m_autoSave;
+ std::shared_ptr<CCheevos> m_cheevos;
// Game parameters
GAME::GameClientPtr m_gameClient;
diff --git a/xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt b/xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt
new file mode 100644
index 0000000000..0e1bf23ccc
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES Cheevos.cpp)
+
+set(HEADERS Cheevos.h
+ RConsoleIDs.h)
+
+core_add_library(retroplayer_cheevos)
diff --git a/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp b/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp
new file mode 100644
index 0000000000..7e22257534
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020-2021 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 "Cheevos.h"
+
+#include "URL.h"
+#include "filesystem/CurlFile.h"
+#include "filesystem/File.h"
+#include "games/addons/GameClient.h"
+#include "games/addons/cheevos/GameClientCheevos.h"
+#include "utils/JSONVariantParser.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "vector"
+
+using namespace KODI;
+using namespace RETRO;
+
+namespace
+{
+// API JSON Field names
+constexpr auto SUCCESS = "Success";
+constexpr auto GAME_ID = "GameID";
+constexpr auto PATCH_DATA = "PatchData";
+constexpr auto RICH_PRESENCE = "RichPresencePatch";
+
+constexpr int RESPORNSE_SIZE = 64;
+} // namespace
+
+CCheevos::CCheevos(GAME::CGameClient* gameClient,
+ const std::string& userName,
+ const std::string& loginToken)
+ : m_gameClient(gameClient), m_userName(userName), m_loginToken(loginToken)
+{
+}
+
+void CCheevos::ResetRuntime()
+{
+ m_gameClient->Cheevos().RCResetRuntime();
+}
+
+bool CCheevos::LoadData()
+{
+ if (m_userName.empty() || m_loginToken.empty())
+ return false;
+
+ if (m_romHash.empty())
+ {
+ m_consoleID = ConsoleID();
+ if (m_consoleID == RConsoleID::RC_INVALID_ID)
+ return false;
+
+ std::string hash;
+ if (!m_gameClient->Cheevos().RCGenerateHashFromFile(hash, m_consoleID,
+ m_gameClient->GetGamePath().c_str()))
+ {
+ return false;
+ }
+
+ m_romHash = hash;
+ }
+
+ std::string requestURL;
+
+ if (!m_gameClient->Cheevos().RCGetGameIDUrl(requestURL, m_romHash))
+ return false;
+
+ XFILE::CFile response;
+ response.CURLCreate(requestURL);
+ response.CURLOpen(0);
+
+ char responseStr[RESPORNSE_SIZE];
+ response.ReadString(responseStr, RESPORNSE_SIZE);
+
+ response.Close();
+
+ CVariant data(CVariant::VariantTypeObject);
+ CJSONVariantParser::Parse(responseStr, data);
+
+ if (!data[SUCCESS].asBoolean())
+ return false;
+
+ m_gameID = data[GAME_ID].asUnsignedInteger32();
+
+ // For some reason RetroAchievements returns Success = true when the hash isn't found
+ if (m_gameID == 0)
+ return false;
+
+ if (!m_gameClient->Cheevos().RCGetPatchFileUrl(requestURL, m_userName, m_loginToken, m_gameID))
+ return false;
+
+ CURL curl(requestURL);
+ std::vector<uint8_t> patchData;
+ response.LoadFile(curl, patchData);
+
+ std::string strResponse(patchData.begin(), patchData.end());
+ CJSONVariantParser::Parse(strResponse, data);
+
+ if (!data[SUCCESS].asBoolean())
+ return false;
+
+ m_richPresenceScript = data[PATCH_DATA][RICH_PRESENCE].asString();
+ m_richPresenceLoaded = true;
+
+ return true;
+}
+
+void CCheevos::EnableRichPresence()
+{
+ if (!m_richPresenceLoaded)
+ {
+ if (!LoadData())
+ {
+ CLog::Log(LOGERROR, "Cheevos: Couldn't load patch file");
+ return;
+ }
+ }
+
+ m_gameClient->Cheevos().RCEnableRichPresence(m_richPresenceScript);
+ m_richPresenceScript.clear();
+}
+
+std::string CCheevos::GetRichPresenceEvaluation()
+{
+ if (!m_richPresenceLoaded)
+ {
+ CLog::Log(LOGERROR, "Cheevos: Rich Presence script was not found");
+ return "";
+ }
+
+ std::string evaluation;
+ m_gameClient->Cheevos().RCGetRichPresenceEvaluation(evaluation, m_consoleID);
+
+ std::string url;
+ std::string postData;
+ if (m_gameClient->Cheevos().RCPostRichPresenceUrl(url, postData, m_userName, m_loginToken,
+ m_gameID, evaluation))
+ {
+ XFILE::CCurlFile curl;
+ std::string res;
+ curl.Post(url, postData, res);
+ }
+
+ return evaluation;
+}
+
+RConsoleID CCheevos::ConsoleID()
+{
+ const std::string extension = URIUtils::GetExtension(m_gameClient->GetGamePath());
+ auto it = m_extensionToConsole.find(extension);
+
+ if (it == m_extensionToConsole.end())
+ return RConsoleID::RC_INVALID_ID;
+
+ return it->second;
+}
diff --git a/xbmc/cores/RetroPlayer/cheevos/Cheevos.h b/xbmc/cores/RetroPlayer/cheevos/Cheevos.h
new file mode 100644
index 0000000000..71c5ed5896
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/Cheevos.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020-2021 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 "RConsoleIDs.h"
+
+#include <map>
+#include <string>
+
+namespace KODI
+{
+namespace GAME
+{
+class CGameClient;
+}
+
+namespace RETRO
+{
+class CCheevos
+{
+public:
+ CCheevos(GAME::CGameClient* gameClient,
+ const std::string& userName,
+ const std::string& loginToken);
+ void ResetRuntime();
+ void EnableRichPresence();
+ std::string GetRichPresenceEvaluation();
+
+private:
+ bool LoadData();
+ RConsoleID ConsoleID();
+
+ GAME::CGameClient* const m_gameClient;
+ std::string m_userName;
+ std::string m_loginToken;
+ std::string m_romHash;
+ std::string m_richPresenceScript;
+ uint32_t m_gameID{};
+ RConsoleID m_consoleID = RConsoleID::RC_INVALID_ID;
+ bool m_richPresenceLoaded{};
+
+ const std::map<std::string, RConsoleID> m_extensionToConsole = {
+ {".a26", RConsoleID::RC_CONSOLE_ATARI_2600},
+ {".a78", RConsoleID::RC_CONSOLE_ATARI_7800},
+ {".agb", RConsoleID::RC_CONSOLE_GAMEBOY_ADVANCE},
+ {".cdi", RConsoleID::RC_CONSOLE_DREAMCAST},
+ {".cdt", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".cgb", RConsoleID::RC_CONSOLE_GAMEBOY_COLOR},
+ {".chd", RConsoleID::RC_CONSOLE_DREAMCAST},
+ {".cpr", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".d64", RConsoleID::RC_CONSOLE_COMMODORE_64},
+ {".gb", RConsoleID::RC_CONSOLE_GAMEBOY},
+ {".gba", RConsoleID::RC_CONSOLE_GAMEBOY_ADVANCE},
+ {".gbc", RConsoleID::RC_CONSOLE_GAMEBOY_COLOR},
+ {".gdi", RConsoleID::RC_CONSOLE_DREAMCAST},
+ {".j64", RConsoleID::RC_CONSOLE_ATARI_JAGUAR},
+ {".jag", RConsoleID::RC_CONSOLE_ATARI_JAGUAR},
+ {".lnx", RConsoleID::RC_CONSOLE_ATARI_LYNX},
+ {".mds", RConsoleID::RC_CONSOLE_SATURN},
+ {".min", RConsoleID::RC_CONSOLE_POKEMON_MINI},
+ {".mx1", RConsoleID::RC_CONSOLE_MSX},
+ {".mx2", RConsoleID::RC_CONSOLE_MSX},
+ {".n64", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".ndd", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".nds", RConsoleID::RC_CONSOLE_NINTENDO_DS},
+ {".nes", RConsoleID::RC_CONSOLE_NINTENDO},
+ {".o", RConsoleID::RC_CONSOLE_ATARI_LYNX},
+ {".pce", RConsoleID::RC_CONSOLE_PC_ENGINE},
+ {".sfc", RConsoleID::RC_CONSOLE_SUPER_NINTENDO},
+ {".sgx", RConsoleID::RC_CONSOLE_PC_ENGINE},
+ {".smc", RConsoleID::RC_CONSOLE_SUPER_NINTENDO},
+ {".sna", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".tap", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".u1", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".v64", RConsoleID::RC_CONSOLE_NINTENDO_64},
+ {".vb", RConsoleID::RC_CONSOLE_VIRTUAL_BOY},
+ {".vboy", RConsoleID::RC_CONSOLE_VIRTUAL_BOY},
+ {".vec", RConsoleID::RC_CONSOLE_VECTREX},
+ {".voc", RConsoleID::RC_CONSOLE_AMSTRAD_PC},
+ {".z64", RConsoleID::RC_CONSOLE_NINTENDO_64}};
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h b/xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h
new file mode 100644
index 0000000000..c6c9d5af82
--- /dev/null
+++ b/xbmc/cores/RetroPlayer/cheevos/RConsoleIDs.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020-2021 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
+{
+enum class RConsoleID
+{
+ RC_INVALID_ID = -1,
+ RC_CONSOLE_MEGA_DRIVE = 1,
+ RC_CONSOLE_NINTENDO_64 = 2,
+ RC_CONSOLE_SUPER_NINTENDO = 3,
+ RC_CONSOLE_GAMEBOY = 4,
+ RC_CONSOLE_GAMEBOY_ADVANCE = 5,
+ RC_CONSOLE_GAMEBOY_COLOR = 6,
+ RC_CONSOLE_NINTENDO = 7,
+ RC_CONSOLE_PC_ENGINE = 8,
+ RC_CONSOLE_SEGA_CD = 9,
+ RC_CONSOLE_SEGA_32X = 10,
+ RC_CONSOLE_MASTER_SYSTEM = 11,
+ RC_CONSOLE_PLAYSTATION = 12,
+ RC_CONSOLE_ATARI_LYNX = 13,
+ RC_CONSOLE_NEOGEO_POCKET = 14,
+ RC_CONSOLE_GAME_GEAR = 15,
+ RC_CONSOLE_GAMECUBE = 16,
+ RC_CONSOLE_ATARI_JAGUAR = 17,
+ RC_CONSOLE_NINTENDO_DS = 18,
+ RC_CONSOLE_WII = 19,
+ RC_CONSOLE_WII_U = 20,
+ RC_CONSOLE_PLAYSTATION_2 = 21,
+ RC_CONSOLE_XBOX = 22,
+ RC_CONSOLE_MAGNAVOX_ODYSSEY = 23,
+ RC_CONSOLE_POKEMON_MINI = 24,
+ RC_CONSOLE_ATARI_2600 = 25,
+ RC_CONSOLE_MS_DOS = 26,
+ RC_CONSOLE_ARCADE = 27,
+ RC_CONSOLE_VIRTUAL_BOY = 28,
+ RC_CONSOLE_MSX = 29,
+ RC_CONSOLE_COMMODORE_64 = 30,
+ RC_CONSOLE_ZX81 = 31,
+ RC_CONSOLE_ORIC = 32,
+ RC_CONSOLE_SG1000 = 33,
+ RC_CONSOLE_VIC20 = 34,
+ RC_CONSOLE_AMIGA = 35,
+ RC_CONSOLE_AMIGA_ST = 36,
+ RC_CONSOLE_AMSTRAD_PC = 37,
+ RC_CONSOLE_APPLE_II = 38,
+ RC_CONSOLE_SATURN = 39,
+ RC_CONSOLE_DREAMCAST = 40,
+ RC_CONSOLE_PSP = 41,
+ RC_CONSOLE_CDI = 42,
+ RC_CONSOLE_3DO = 43,
+ RC_CONSOLE_COLECOVISION = 44,
+ RC_CONSOLE_INTELLIVISION = 45,
+ RC_CONSOLE_VECTREX = 46,
+ RC_CONSOLE_PC8800 = 47,
+ RC_CONSOLE_PC9800 = 48,
+ RC_CONSOLE_PCFX = 49,
+ RC_CONSOLE_ATARI_5200 = 50,
+ RC_CONSOLE_ATARI_7800 = 51,
+ RC_CONSOLE_X68K = 52,
+ RC_CONSOLE_WONDERSWAN = 53,
+ RC_CONSOLE_CASSETTEVISION = 54,
+ RC_CONSOLE_SUPER_CASSETTEVISION = 55,
+ RC_CONSOLE_NEO_GEO_CD = 56,
+ RC_CONSOLE_FAIRCHILD_CHANNEL_F = 57,
+ RC_CONSOLE_FM_TOWNS = 58,
+ RC_CONSOLE_ZX_SPECTRUM = 59,
+ RC_CONSOLE_GAME_AND_WATCH = 60,
+ RC_CONSOLE_NOKIA_NGAGE = 61,
+ RC_CONSOLE_NINTENDO_3DS = 62,
+
+ RC_CONSOLE_HUBS = 100,
+ RC_CONSOLE_EVENTS = 101
+};
+} // namespace RETRO
+} // namespace KODI
diff --git a/xbmc/cores/RetroPlayer/messages/savestate.fbs b/xbmc/cores/RetroPlayer/messages/savestate.fbs
index 6a01ea29da..3a1c5c855b 100644
--- a/xbmc/cores/RetroPlayer/messages/savestate.fbs
+++ b/xbmc/cores/RetroPlayer/messages/savestate.fbs
@@ -9,7 +9,7 @@
namespace KODI.RETRO;
// Savestate schema
-// Version 1
+// Version 2
file_identifier "SAV_";
@@ -21,27 +21,28 @@ enum SaveType : uint8 {
table Savestate {
// Schema version
- version:uint8;
+ version:uint8 (id: 0);
// Savestate properties
- type:SaveType;
- slot:uint8;
- label:string;
- created:string; // W3C date time [ISO 8601 : 1988 (E)] with timezone info
+ type:SaveType (id: 1);
+ slot:uint8 (id: 2);
+ label:string (id: 3);
+ caption:string (id: 11);
+ created:string (id: 4); // W3C date time [ISO 8601 : 1988 (E)] with timezone info
// Game properties
- game_file_name:string;
+ game_file_name:string (id: 5);
// Environment properties
- timestamp_frames:uint64;
- timestamp_wall_clock_ns:uint64;
+ timestamp_frames:uint64 (id: 6);
+ timestamp_wall_clock_ns:uint64 (id: 7);
// Emulator properties
- emulator_addon_id:string;
- emulator_version:string; // Semantic version
+ emulator_addon_id:string (id: 8);
+ emulator_version:string (id: 9); // Semantic version
// Memory properties
- memory_data:[uint8];
+ memory_data:[uint8] (id: 10);
}
root_type Savestate;
diff --git a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp
index 00da072093..1bfa1b485e 100644
--- a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp
+++ b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp
@@ -9,6 +9,7 @@
#include "ReversiblePlayback.h"
#include "ServiceBroker.h"
+#include "cores/RetroPlayer/cheevos/Cheevos.h"
#include "cores/RetroPlayer/rendering/RPRenderManager.h"
#include "cores/RetroPlayer/savestates/ISavestate.h"
#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
@@ -29,10 +30,12 @@ using namespace RETRO;
CReversiblePlayback::CReversiblePlayback(GAME::CGameClient* gameClient,
CRPRenderManager& renderManager,
+ CCheevos* cheevos,
double fps,
size_t serializeSize)
: m_gameClient(gameClient),
m_renderManager(renderManager),
+ m_cheevos(cheevos),
m_gameLoop(this, fps),
m_savestateDatabase(new CSavestateDatabase),
m_totalFrameCount(0),
@@ -126,12 +129,16 @@ std::string CReversiblePlayback::CreateSavestate(bool autosave)
}
std::string label = "";
+
+ std::string caption = m_cheevos->GetRichPresenceEvaluation();
+
if (!m_autosavePath.empty())
{
std::unique_ptr<ISavestate> loadedSavestate = CSavestateDatabase::AllocateSavestate();
if (m_savestateDatabase->GetSavestate(m_autosavePath, *loadedSavestate))
label = loadedSavestate->Label();
}
+
const CDateTime nowUTC = CDateTime::GetUTCDateTime();
const std::string gameFileName = URIUtils::GetFileName(m_gameClient->GetGamePath());
const uint64_t timestampFrames = m_totalFrameCount;
@@ -145,6 +152,7 @@ std::string CReversiblePlayback::CreateSavestate(bool autosave)
savestate->SetType(autosave ? SAVE_TYPE::AUTO : SAVE_TYPE::MANUAL);
savestate->SetLabel(label);
+ savestate->SetCaption(caption);
savestate->SetCreated(nowUTC);
savestate->SetGameFileName(gameFileName);
savestate->SetTimestampFrames(timestampFrames);
@@ -219,6 +227,8 @@ bool CReversiblePlayback::LoadSavestate(const std::string& path)
}
}
+ m_cheevos->ResetRuntime();
+
return bSuccess;
}
diff --git a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h
index fac238bf92..e3113ce913 100644
--- a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h
+++ b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.h
@@ -26,6 +26,7 @@ class CGameClient;
namespace RETRO
{
+class CCheevos;
class CSavestateDatabase;
class IMemoryStream;
class CRPRenderManager;
@@ -35,6 +36,7 @@ class CReversiblePlayback : public IPlayback, public IGameLoopCallback, public O
public:
CReversiblePlayback(GAME::CGameClient* gameClient,
CRPRenderManager& renderManager,
+ CCheevos* cheevos,
double fps,
size_t serializeSize);
@@ -72,6 +74,7 @@ private:
// Construction parameter
GAME::CGameClient* const m_gameClient;
CRPRenderManager& m_renderManager;
+ CCheevos* const m_cheevos;
// Gameplay functionality
CGameLoop m_gameLoop;
diff --git a/xbmc/cores/RetroPlayer/savestates/ISavestate.h b/xbmc/cores/RetroPlayer/savestates/ISavestate.h
index 368cef93e2..236dccdb48 100644
--- a/xbmc/cores/RetroPlayer/savestates/ISavestate.h
+++ b/xbmc/cores/RetroPlayer/savestates/ISavestate.h
@@ -55,6 +55,11 @@ public:
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;
@@ -111,6 +116,7 @@ public:
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;
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp
index b0bf354739..03e91755fb 100644
--- a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp
@@ -15,6 +15,7 @@
#include "filesystem/Directory.h"
#include "filesystem/File.h"
#include "filesystem/IFileTypes.h"
+#include "games/dialogs/DialogGameDefines.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
@@ -157,6 +158,7 @@ bool CSavestateDatabase::GetSavestatesNav(CFileItemList& items,
}
items[i]->SetArt("icon", MakeThumbnailPath(items[i]->GetPath()));
+ items[i]->SetProperty(SAVESTATE_CAPTION, savestate->Caption());
items[i]->m_dateTime = dateUTC;
}
@@ -172,6 +174,7 @@ bool CSavestateDatabase::RenameSavestate(const std::string& savestatePath, const
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());
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp
index 5bf08d1766..a1265dc52b 100644
--- a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp
@@ -16,7 +16,8 @@ using namespace RETRO;
namespace
{
-const uint8_t SCHEMA_VERSION = 1;
+const uint8_t SCHEMA_VERSION = 2;
+const uint8_t SCHEMA_MIN_VERSION = 1;
/*!
* \brief The initial size of the FlatBuffer's memory buffer
@@ -135,6 +136,21 @@ 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;
@@ -262,6 +278,12 @@ void CSavestateFlatBuffer::Finalize()
m_labelOffset.reset();
}
+ if (m_captionOffset)
+ {
+ savestateBuilder.add_caption(*m_captionOffset);
+ m_captionOffset.reset();
+ }
+
if (m_createdOffset)
{
savestateBuilder.add_created(*m_createdOffset);
@@ -311,10 +333,11 @@ bool CSavestateFlatBuffer::Deserialize(std::vector<uint8_t> data)
{
const Savestate* savestate = GetSavestate(data.data());
- if (savestate->version() != SCHEMA_VERSION)
+ if (savestate->version() < SCHEMA_MIN_VERSION)
{
- CLog::Log(LOGERROR, "RetroPlayer[SAVE): Schema version {} not supported, must be version {}",
- savestate->version(), SCHEMA_VERSION);
+ CLog::Log(LOGERROR,
+ "RetroPlayer[SAVE): Schema version {} not supported, must be at least version {}",
+ savestate->version(), SCHEMA_MIN_VERSION);
}
else
{
diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h
index ec2caf1048..d7c4b1abf2 100644
--- a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h
+++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h
@@ -38,6 +38,7 @@ public:
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;
@@ -49,6 +50,7 @@ public:
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;
@@ -86,6 +88,7 @@ private:
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;
diff --git a/xbmc/games/dialogs/CMakeLists.txt b/xbmc/games/dialogs/CMakeLists.txt
index 108d3f85de..d6c9a16c6a 100644
--- a/xbmc/games/dialogs/CMakeLists.txt
+++ b/xbmc/games/dialogs/CMakeLists.txt
@@ -2,7 +2,8 @@ set(SOURCES GUIDialogSelectGameClient.cpp
GUIDialogSelectSavestate.cpp
)
-set(HEADERS GUIDialogSelectGameClient.h
+set(HEADERS DialogGameDefines.h
+ GUIDialogSelectGameClient.h
GUIDialogSelectSavestate.h
)
diff --git a/xbmc/games/dialogs/DialogGameDefines.h b/xbmc/games/dialogs/DialogGameDefines.h
new file mode 100644
index 0000000000..386976d753
--- /dev/null
+++ b/xbmc/games/dialogs/DialogGameDefines.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) 2020-2021 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
+
+// Name of list item property for savestate captions
+constexpr auto SAVESTATE_CAPTION = "savestate.caption";
diff --git a/xbmc/games/dialogs/osd/DialogGameSaves.cpp b/xbmc/games/dialogs/osd/DialogGameSaves.cpp
index 5011cb2594..d4c2136e25 100644
--- a/xbmc/games/dialogs/osd/DialogGameSaves.cpp
+++ b/xbmc/games/dialogs/osd/DialogGameSaves.cpp
@@ -9,20 +9,31 @@
#include "DialogGameSaves.h"
#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "cores/RetroPlayer/savestates/ISavestate.h"
#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
#include "dialogs/GUIDialogContextMenu.h"
#include "dialogs/GUIDialogYesNo.h"
+#include "games/dialogs/DialogGameDefines.h"
+#include "guilib/GUIBaseContainer.h"
+#include "guilib/GUIComponent.h"
#include "guilib/GUIKeyboardFactory.h"
#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
#include "guilib/WindowIDs.h"
#include "input/Key.h"
#include "utils/FileUtils.h"
+#include "utils/Variant.h"
using namespace KODI;
using namespace GAME;
-#define CONTROL_SIMPLE_LIST 3
+namespace
+{
+constexpr int CONTROL_DETAILED_LIST = 6;
+constexpr int CONTROL_DESCRIPTION = 12;
+} // namespace
CDialogGameSaves::CDialogGameSaves() : CGUIDialogSelect(WINDOW_DIALOG_GAME_SAVES)
{
@@ -34,7 +45,7 @@ bool CDialogGameSaves::OnMessage(CGUIMessage& message)
{
case GUI_MSG_CLICKED:
{
- if (m_viewControl.HasControl(CONTROL_SIMPLE_LIST))
+ if (m_viewControl.HasControl(CONTROL_DETAILED_LIST))
{
int action = message.GetParam1();
if (action == ACTION_CONTEXT_MENU || action == ACTION_MOUSE_RIGHT_CLICK)
@@ -42,20 +53,67 @@ bool CDialogGameSaves::OnMessage(CGUIMessage& message)
int selectedItem = m_viewControl.GetSelectedItem();
if (selectedItem >= 0 && selectedItem < m_vecList->Size())
{
- OnPopupMenu(selectedItem);
+ CFileItemPtr item = m_vecList->Get(selectedItem);
+ OnPopupMenu(std::move(item));
return true;
}
}
}
+ break;
}
+ default:
+ break;
}
return CGUIDialogSelect::OnMessage(message);
}
-void CDialogGameSaves::OnPopupMenu(int itemIndex)
+void CDialogGameSaves::FrameMove()
{
- const std::string savePath = m_vecList->Get(itemIndex)->GetPath();
+ CGUIControl* itemContainer = GetControl(CONTROL_DETAILED_LIST);
+ if (itemContainer != nullptr)
+ {
+ if (itemContainer->HasFocus())
+ {
+ int selectedItem = m_viewControl.GetSelectedItem();
+ if (selectedItem >= 0 && selectedItem < m_vecList->Size())
+ {
+ CFileItemPtr item = m_vecList->Get(selectedItem);
+ OnFocus(std::move(item));
+ }
+ }
+ else
+ {
+ OnFocusLost();
+ }
+ }
+
+ CGUIDialogSelect::FrameMove();
+}
+
+std::string CDialogGameSaves::GetSelectedItemPath()
+{
+ if (m_selectedItem != nullptr)
+ return m_selectedItem->GetPath();
+
+ return "";
+}
+
+void CDialogGameSaves::OnFocus(CFileItemPtr item)
+{
+ const std::string caption = item->GetProperty(SAVESTATE_CAPTION).asString();
+
+ HandleCaption(caption);
+}
+
+void CDialogGameSaves::OnFocusLost()
+{
+ HandleCaption("");
+}
+
+void CDialogGameSaves::OnPopupMenu(CFileItemPtr item)
+{
+ const std::string& savePath = item->GetPath();
CContextButtons buttons;
@@ -73,15 +131,16 @@ void CDialogGameSaves::OnPopupMenu(int itemIndex)
{
if (db.RenameSavestate(savePath, label))
{
- CFileItemPtr item = m_vecList->Get(itemIndex);
-
std::unique_ptr<RETRO::ISavestate> savestate =
RETRO::CSavestateDatabase::AllocateSavestate();
db.GetSavestate(savePath, *savestate);
item->SetLabel(label);
+
CDateTime date = CDateTime::FromUTCDateTime(savestate->Created());
item->SetLabel2(date.GetAsLocalizedDateTime(false, false));
+
+ item->SetProperty(SAVESTATE_CAPTION, savestate->Caption());
}
}
}
@@ -90,14 +149,22 @@ void CDialogGameSaves::OnPopupMenu(int itemIndex)
if (CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, CVariant{125}))
{
if (db.DeleteSavestate(savePath))
- m_vecList->Remove(itemIndex);
+ m_vecList->Remove(item.get());
}
}
m_viewControl.SetItems(*m_vecList);
}
-std::string CDialogGameSaves::GetSelectedItemPath()
+void CDialogGameSaves::HandleCaption(const std::string& caption)
{
- return m_selectedItem->GetPath();
-} \ No newline at end of file
+ if (caption != m_currentCaption)
+ {
+ m_currentCaption = caption;
+
+ // Update the GUI label
+ CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_DESCRIPTION);
+ msg.SetLabel(m_currentCaption);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID());
+ }
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameSaves.h b/xbmc/games/dialogs/osd/DialogGameSaves.h
index 0a9c2081c6..bfd1a84485 100644
--- a/xbmc/games/dialogs/osd/DialogGameSaves.h
+++ b/xbmc/games/dialogs/osd/DialogGameSaves.h
@@ -21,11 +21,40 @@ class CDialogGameSaves : public CGUIDialogSelect
public:
CDialogGameSaves();
~CDialogGameSaves() override = default;
+
+ // implementation of CGUIControl via CGUIDialog
bool OnMessage(CGUIMessage& message) override;
+
+ // implementation of CGUIWindow via CGUIDialog
+ void FrameMove() override;
+
std::string GetSelectedItemPath();
private:
- void OnPopupMenu(int itemIndex);
+ using CGUIControl::OnFocus;
+
+ /*!
+ * \brief Called every frame with the item being focused
+ */
+ void OnFocus(CFileItemPtr item);
+
+ /*!
+ * \brief Called every frame if no item is focused
+ */
+ void OnFocusLost();
+
+ /*!
+ * \brief Called when a popup menu is opened for an item
+ */
+ void OnPopupMenu(CFileItemPtr item);
+
+ /*!
+ * \brief Called every frame with the caption to set
+ */
+ void HandleCaption(const std::string& caption);
+
+ // State parameters
+ std::string m_currentCaption;
};
} // namespace GAME
} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogInGameSaves.cpp b/xbmc/games/dialogs/osd/DialogInGameSaves.cpp
index 8788c4fe71..d705d03822 100644
--- a/xbmc/games/dialogs/osd/DialogInGameSaves.cpp
+++ b/xbmc/games/dialogs/osd/DialogInGameSaves.cpp
@@ -14,6 +14,7 @@
#include "cores/RetroPlayer/playback/IPlayback.h"
#include "cores/RetroPlayer/savestates/ISavestate.h"
#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
+#include "games/dialogs/DialogGameDefines.h"
#include "guilib/LocalizeStrings.h"
#include "guilib/WindowIDs.h"
#include "settings/GameSettings.h"
@@ -42,6 +43,8 @@ void CDialogInGameSaves::PreInit()
CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(15314)); // "Save progress"
item->SetArt("icon", "DefaultAddSource.png");
item->SetPath("");
+ item->SetProperty(SAVESTATE_CAPTION,
+ g_localizeStrings.Get(15315)); // "Save progress to new save file"
m_items.AddFront(std::move(item), 0);
}