diff options
author | NickSiak <nikos.siakas@gmail.com> | 2021-11-06 14:14:25 +0200 |
---|---|---|
committer | NickSiak <nikos.siakas@gmail.com> | 2022-03-23 20:44:54 +0200 |
commit | 99389a5425301120fcf3b06f0d74e579b5a0167e (patch) | |
tree | 95ab12a276eabc8b45e280baaec47bbcbfd81f5c | |
parent | 0d91930f19527923949d484f9008f87651bd7e86 (diff) |
RetroPlayer: Cheevos subsystem
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); } |