From 2450ffa101f52fe56eeac2591043c3c4fc67e2e1 Mon Sep 17 00:00:00 2001 From: Christian Fetzer Date: Wed, 4 Jan 2017 17:14:32 +0100 Subject: [PATCH] [GameClient] Implement in-game saves --- xbmc/games/addons/CMakeLists.txt | 2 + xbmc/games/addons/GameClient.cpp | 7 + xbmc/games/addons/GameClient.h | 4 + xbmc/games/addons/GameClientInGameSaves.cpp | 163 ++++++++++++++++++++ xbmc/games/addons/GameClientInGameSaves.h | 73 +++++++++ xbmc/games/addons/GameClientTranslator.cpp | 19 +++ xbmc/games/addons/GameClientTranslator.h | 7 + 7 files changed, 275 insertions(+) create mode 100644 xbmc/games/addons/GameClientInGameSaves.cpp create mode 100644 xbmc/games/addons/GameClientInGameSaves.h diff --git a/xbmc/games/addons/CMakeLists.txt b/xbmc/games/addons/CMakeLists.txt index 8a89ec63c6b88..b6d98ab0e4fe5 100644 --- a/xbmc/games/addons/CMakeLists.txt +++ b/xbmc/games/addons/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES GameClient.cpp + GameClientInGameSaves.cpp GameClientInput.cpp GameClientKeyboard.cpp GameClientMouse.cpp @@ -8,6 +9,7 @@ set(SOURCES GameClient.cpp set(HEADERS GameClient.h GameClientCallbacks.h + GameClientInGameSaves.h GameClientInput.h GameClientKeyboard.h GameClientMouse.h diff --git a/xbmc/games/addons/GameClient.cpp b/xbmc/games/addons/GameClient.cpp index 0c6f04e42d231..3199abe376b72 100644 --- a/xbmc/games/addons/GameClient.cpp +++ b/xbmc/games/addons/GameClient.cpp @@ -20,6 +20,7 @@ #include "GameClient.h" #include "GameClientCallbacks.h" +#include "GameClientInGameSaves.h" #include "GameClientInput.h" #include "GameClientKeyboard.h" #include "GameClientMouse.h" @@ -276,6 +277,9 @@ bool CGameClient::OpenFile(const CFileItem& file, IGameAudioCallback* audio, IGa if (!InitializeGameplay(file.GetPath(), audio, video)) return false; + m_inGameSaves.reset(new CGameClientInGameSaves(this, m_pStruct)); + m_inGameSaves->Load(); + return true; } @@ -486,6 +490,9 @@ void CGameClient::CloseFile() if (m_bIsPlaying) { + m_inGameSaves->Save(); + m_inGameSaves.reset(); + try { LogError(m_pStruct->UnloadGame(), "UnloadGame()"); } catch (...) { LogException("UnloadGame()"); } } diff --git a/xbmc/games/addons/GameClient.h b/xbmc/games/addons/GameClient.h index a88b64403011c..43db19c91c207 100644 --- a/xbmc/games/addons/GameClient.h +++ b/xbmc/games/addons/GameClient.h @@ -40,6 +40,7 @@ class CFileItem; namespace GAME { +class CGameClientInGameSaves; class CGameClientInput; class CGameClientKeyboard; class CGameClientMouse; @@ -162,6 +163,9 @@ class CGameClient : public ADDON::CAddonDll m_playback; // Interface to control playback GAME_REGION m_region; // Region of the loaded game + // In-game saves + std::unique_ptr m_inGameSaves; + // Input std::map> m_ports; std::unique_ptr m_keyboard; diff --git a/xbmc/games/addons/GameClientInGameSaves.cpp b/xbmc/games/addons/GameClientInGameSaves.cpp new file mode 100644 index 0000000000000..bca54f20358c4 --- /dev/null +++ b/xbmc/games/addons/GameClientInGameSaves.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016-2017 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * . + * + */ + +#include "GameClientInGameSaves.h" + +#include "GameClient.h" +#include "GameClientTranslator.h" +#include "filesystem/File.h" +#include "profiles/ProfilesManager.h" +#include "utils/URIUtils.h" + +#include + +using namespace GAME; + +#define INGAME_SAVES_DIRECTORY "InGameSaves" +#define INGAME_SAVES_EXTENSION_SAVE_RAM ".sav" +#define INGAME_SAVES_EXTENSION_RTC ".rtc" + +CGameClientInGameSaves::CGameClientInGameSaves(CGameClient* addon, const GameClient* dllStruct) : + m_gameClient(addon), + m_dllStruct(dllStruct) +{ + assert(m_gameClient != nullptr); + assert(m_dllStruct != nullptr); +} + +void CGameClientInGameSaves::Load() +{ + Load(GAME_MEMORY_SAVE_RAM); + Load(GAME_MEMORY_RTC); +} + +void CGameClientInGameSaves::Save() +{ + Save(GAME_MEMORY_SAVE_RAM); + Save(GAME_MEMORY_RTC); +} + +std::string CGameClientInGameSaves::GetPath(GAME_MEMORY memoryType) +{ + std::string path = URIUtils::AddFileToFolder(CProfilesManager::GetInstance().GetSavestatesFolder(), INGAME_SAVES_DIRECTORY); + if (!XFILE::CDirectory::Exists(path)) + XFILE::CDirectory::Create(path); + + // Append save game filename + std::string gamePath = URIUtils::GetFileName(m_gameClient->GetGamePath()); + path = URIUtils::AddFileToFolder(path, gamePath.empty() ? m_gameClient->ID() : gamePath); + + // Append file extension + switch (memoryType) + { + case GAME_MEMORY_SAVE_RAM: return path + INGAME_SAVES_EXTENSION_SAVE_RAM; + case GAME_MEMORY_RTC: return path + INGAME_SAVES_EXTENSION_RTC; + default: + break; + } + return std::string(); +} + +void CGameClientInGameSaves::Load(GAME_MEMORY memoryType) +{ + uint8_t *gameMemory = nullptr; + size_t size = 0; + + try + { + m_dllStruct->GetMemory(memoryType, &gameMemory, &size); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: Exception caught in GetMemory()", m_gameClient->ID().c_str()); + } + + const std::string path = GetPath(memoryType); + if (size > 0 && XFILE::CFile::Exists(path)) + { + XFILE::CFile file; + if (file.Open(path)) + { + ssize_t read = file.Read(gameMemory, size); + if (read == static_cast(size)) + { + CLog::Log(LOGINFO, "GAME: In-game saves (%s) loaded from %s", CGameClientTranslator::ToString(memoryType), path.c_str()); + } + else + { + CLog::Log(LOGERROR, "GAME: Failed to read in-game saves (%s): %ld/%ld bytes read", CGameClientTranslator::ToString(memoryType), read, size); + } + } + else + { + CLog::Log(LOGERROR, "GAME: Unable to open in-game saves (%s) from file %s", CGameClientTranslator::ToString(memoryType), path.c_str()); + } + } + else + { + CLog::Log(LOGDEBUG, "GAME: No in-game saves (%s) to load", CGameClientTranslator::ToString(memoryType)); + } +} + +void CGameClientInGameSaves::Save(GAME_MEMORY memoryType) +{ + uint8_t *gameMemory = nullptr; + size_t size = 0; + + try + { + m_dllStruct->GetMemory(memoryType, &gameMemory, &size); + } + catch (...) + { + CLog::Log(LOGERROR, "GAME: %s: Exception caught in GetMemory()", m_gameClient->ID().c_str()); + } + + if (size > 0) + { + const std::string path = GetPath(memoryType); + + + + XFILE::CFile file; + if (file.OpenForWrite(path, true)) + { + ssize_t written = 0; + written = file.Write(gameMemory, size); + file.Close(); + if (written == static_cast(size)) + { + CLog::Log(LOGINFO, "GAME: In-game saves (%s) written to %s", CGameClientTranslator::ToString(memoryType), path.c_str()); + } + else + { + CLog::Log(LOGERROR, "GAME: Failed to write in-game saves (%s): %ld/%ld bytes written", CGameClientTranslator::ToString(memoryType), written, size); + } + } + else + { + CLog::Log(LOGERROR, "GAME: Unable to open in-game saves (%s) from file %s", CGameClientTranslator::ToString(memoryType), path.c_str()); + } + } + else + { + CLog::Log(LOGDEBUG, "GAME: No in-game saves (%s) to save", CGameClientTranslator::ToString(memoryType)); + } +} diff --git a/xbmc/games/addons/GameClientInGameSaves.h b/xbmc/games/addons/GameClientInGameSaves.h new file mode 100644 index 0000000000000..1b124e87d7e51 --- /dev/null +++ b/xbmc/games/addons/GameClientInGameSaves.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016-2017 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this Program; see the file COPYING. If not, see + * . + * + */ +#pragma once + +#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h" + +#include + +struct GameClient; + +namespace GAME +{ + class CGameClient; + + /*! + * \brief This class implements in-game saves. + * + * Some games do not implement state persistance on their own, but rely on the frontend for saving their current + * memory state to disk. This is mostly the case for emulators for SRAM (battery backed up ram on cadridges) or + * memory cards. + * + * Differences to save states: + * - Works only for supported games (e.g. emulated games with SRAM support) + * - Often works emulator independent (and can be used to start a game with one emulator and continue with another) + * - Visible in-game (e.g. in-game save game selection menus) + */ + class CGameClientInGameSaves + { + public: + /*! + * \brief Constructor. + * \param addon The game client implementation. + * \param dllStruct The emulator or game for which the in-game saves are processed. + */ + CGameClientInGameSaves(CGameClient* addon, const GameClient* dllStruct); + + /*! + * \brief Load in-game data. + */ + void Load(); + + /*! + * \brief Save in-game data. + */ + void Save(); + + private: + std::string GetPath(GAME_MEMORY memoryType); + + void Load(GAME_MEMORY memoryType); + void Save(GAME_MEMORY memoryType); + + const CGameClient* const m_gameClient; + const GameClient* const m_dllStruct; + }; +} diff --git a/xbmc/games/addons/GameClientTranslator.cpp b/xbmc/games/addons/GameClientTranslator.cpp index dda424fc03623..afa03ca755879 100644 --- a/xbmc/games/addons/GameClientTranslator.cpp +++ b/xbmc/games/addons/GameClientTranslator.cpp @@ -39,6 +39,25 @@ const char* CGameClientTranslator::ToString(GAME_ERROR error) return "unknown error"; } +const char* CGameClientTranslator::ToString(GAME_MEMORY memory) +{ + switch (memory) + { + case GAME_MEMORY_SAVE_RAM: return "save ram"; + case GAME_MEMORY_RTC: return "rtc"; + case GAME_MEMORY_SYSTEM_RAM: return "system ram"; + case GAME_MEMORY_VIDEO_RAM: return "video ram"; + case GAME_MEMORY_SNES_BSX_RAM: return "snes bsx ram"; + case GAME_MEMORY_SNES_SUFAMI_TURBO_A_RAM: return "snes sufami turbo a ram"; + case GAME_MEMORY_SNES_SUFAMI_TURBO_B_RAM: return "snes sufami turbo b ram"; + case GAME_MEMORY_SNES_GAME_BOY_RAM: return "snes game boy ram"; + case GAME_MEMORY_SNES_GAME_BOY_RTC: return "snes game boy rtc"; + default: + break; + } + return "unknown memory"; +} + AVPixelFormat CGameClientTranslator::TranslatePixelFormat(GAME_PIXEL_FORMAT format) { switch (format) diff --git a/xbmc/games/addons/GameClientTranslator.h b/xbmc/games/addons/GameClientTranslator.h index 55a4c948934bf..a9f4e504db86d 100644 --- a/xbmc/games/addons/GameClientTranslator.h +++ b/xbmc/games/addons/GameClientTranslator.h @@ -46,6 +46,13 @@ namespace GAME */ static const char* ToString(GAME_ERROR error); + /*! + * \brief Translates game memory types to string representation (e.g. for logging). + * \param memory The memory type to translate. + * \return Translated memory type. + */ + static const char* ToString(GAME_MEMORY error); + /*! * \brief Translate pixel format (Game API to FFMPEG). * \param format The pixel format to translate.