Skip to content

Commit

Permalink
Merge pull request #11380 from fetzerch/sram-master
Browse files Browse the repository at this point in the history
[games] Implement in-game saves
  • Loading branch information
garbear committed Feb 5, 2017
2 parents 0e7867f + 9de6c02 commit 6b29488
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 6 deletions.
4 changes: 2 additions & 2 deletions addons/kodi.game/addon.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon id="kodi.game" version="1.0.28" provider-name="Team-Kodi">
<backwards-compatibility abi="1.0.28"/>
<addon id="kodi.game" version="1.0.29" provider-name="Team-Kodi">
<backwards-compatibility abi="1.0.29"/>
<requires>
<import addon="xbmc.core" version="0.1.0"/>
</requires>
Expand Down
Expand Up @@ -233,7 +233,7 @@ GAME_ERROR CheatReset(void);
*
* \return the error, or GAME_ERROR_NO_ERROR if data was set to a valid buffer
*/
GAME_ERROR GetMemory(GAME_MEMORY type, const uint8_t** data, size_t* size);
GAME_ERROR GetMemory(GAME_MEMORY type, uint8_t** data, size_t* size);

/*!
* \brief Set a cheat code
Expand Down
6 changes: 3 additions & 3 deletions xbmc/addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h
Expand Up @@ -21,10 +21,10 @@
#define KODI_GAME_TYPES_H_

/* current game API version */
#define GAME_API_VERSION "1.0.28"
#define GAME_API_VERSION "1.0.29"

/* min. game API version */
#define GAME_MIN_API_VERSION "1.0.28"
#define GAME_MIN_API_VERSION "1.0.29"

#include <stddef.h>
#include <stdint.h>
Expand Down Expand Up @@ -476,7 +476,7 @@ typedef struct KodiToAddonFuncTable_Game
GAME_ERROR (__cdecl* Serialize)(uint8_t*, size_t);
GAME_ERROR (__cdecl* Deserialize)(const uint8_t*, size_t);
GAME_ERROR (__cdecl* CheatReset)(void);
GAME_ERROR (__cdecl* GetMemory)(GAME_MEMORY, const uint8_t**, size_t*);
GAME_ERROR (__cdecl* GetMemory)(GAME_MEMORY, uint8_t**, size_t*);
GAME_ERROR (__cdecl* SetCheat)(unsigned int, bool, const char*);
} KodiToAddonFuncTable_Game;

Expand Down
2 changes: 2 additions & 0 deletions xbmc/games/addons/CMakeLists.txt
@@ -1,4 +1,5 @@
set(SOURCES GameClient.cpp
GameClientInGameSaves.cpp
GameClientInput.cpp
GameClientKeyboard.cpp
GameClientMouse.cpp
Expand All @@ -8,6 +9,7 @@ set(SOURCES GameClient.cpp

set(HEADERS GameClient.h
GameClientCallbacks.h
GameClientInGameSaves.h
GameClientInput.h
GameClientKeyboard.h
GameClientMouse.h
Expand Down
7 changes: 7 additions & 0 deletions xbmc/games/addons/GameClient.cpp
Expand Up @@ -20,6 +20,7 @@

#include "GameClient.h"
#include "GameClientCallbacks.h"
#include "GameClientInGameSaves.h"
#include "GameClientInput.h"
#include "GameClientKeyboard.h"
#include "GameClientMouse.h"
Expand Down Expand Up @@ -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_struct));
m_inGameSaves->Load();

return true;
}

Expand Down Expand Up @@ -486,6 +490,9 @@ void CGameClient::CloseFile()

if (m_bIsPlaying)
{
m_inGameSaves->Save();
m_inGameSaves.reset();

try { LogError(m_struct.UnloadGame(), "UnloadGame()"); }
catch (...) { LogException("UnloadGame()"); }
}
Expand Down
4 changes: 4 additions & 0 deletions xbmc/games/addons/GameClient.h
Expand Up @@ -39,6 +39,7 @@ class CFileItem;
namespace GAME
{

class CGameClientInGameSaves;
class CGameClientInput;
class CGameClientKeyboard;
class CGameClientMouse;
Expand Down Expand Up @@ -161,6 +162,9 @@ class CGameClient : public ADDON::CAddonDll
std::unique_ptr<IGameClientPlayback> m_playback; // Interface to control playback
GAME_REGION m_region; // Region of the loaded game

// In-game saves
std::unique_ptr<CGameClientInGameSaves> m_inGameSaves;

// Input
std::map<int, std::unique_ptr<CGameClientInput>> m_ports;
std::unique_ptr<CGameClientKeyboard> m_keyboard;
Expand Down
162 changes: 162 additions & 0 deletions xbmc/games/addons/GameClientInGameSaves.cpp
@@ -0,0 +1,162 @@
/*
* 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
* <http://www.gnu.org/licenses/>.
*
*/

#include "GameClientInGameSaves.h"

#include "GameClient.h"
#include "GameClientTranslator.h"
#include "filesystem/File.h"
#include "filesystem/Directory.h"
#include "profiles/ProfilesManager.h"
#include "utils/URIUtils.h"
#include "utils/log.h"

#include <assert.h>

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 KodiToAddonFuncTable_Game* 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<ssize_t>(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<ssize_t>(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));
}
}
73 changes: 73 additions & 0 deletions 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
* <http://www.gnu.org/licenses/>.
*
*/
#pragma once

#include "addons/kodi-addon-dev-kit/include/kodi/kodi_game_types.h"

#include <string>

struct GameClient;

namespace GAME
{
class CGameClient;

/*!
* \brief This class implements in-game saves.
*
* \details Some games do not implement state persistence 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 cartridges) 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 KodiToAddonFuncTable_Game* 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 KodiToAddonFuncTable_Game* const m_dllStruct;
};
}
19 changes: 19 additions & 0 deletions xbmc/games/addons/GameClientTranslator.cpp
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions xbmc/games/addons/GameClientTranslator.h
Expand Up @@ -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.
Expand Down

0 comments on commit 6b29488

Please sign in to comment.