From c04dcc2e7d834218ef2d4194331e383402495ae1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 20:07:22 +0200 Subject: Adding upstream version 2:20.4+dfsg. Signed-off-by: Daniel Baumann --- xbmc/cores/RetroPlayer/RetroPlayer.cpp | 698 +++++++++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 xbmc/cores/RetroPlayer/RetroPlayer.cpp (limited to 'xbmc/cores/RetroPlayer/RetroPlayer.cpp') diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp new file mode 100644 index 0000000..030b851 --- /dev/null +++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp @@ -0,0 +1,698 @@ +/* + * 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 "RetroPlayer.h" + +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "RetroPlayerAutoSave.h" +#include "RetroPlayerInput.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonType.h" +#include "cores/DataCacheCore.h" +#include "cores/IPlayerCallback.h" +#include "cores/RetroPlayer/cheevos/Cheevos.h" +#include "cores/RetroPlayer/guibridge/GUIGameMessenger.h" +#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h" +#include "cores/RetroPlayer/guiplayback/GUIPlaybackControl.h" +#include "cores/RetroPlayer/playback/IPlayback.h" +#include "cores/RetroPlayer/playback/RealtimePlayback.h" +#include "cores/RetroPlayer/playback/ReversiblePlayback.h" +#include "cores/RetroPlayer/process/RPProcessInfo.h" +#include "cores/RetroPlayer/rendering/RPRenderManager.h" +#include "cores/RetroPlayer/savestates/ISavestate.h" +#include "cores/RetroPlayer/savestates/SavestateDatabase.h" +#include "cores/RetroPlayer/streams/RPStreamManager.h" +#include "dialogs/GUIDialogYesNo.h" +#include "games/GameServices.h" +#include "games/GameSettings.h" +#include "games/GameUtils.h" +#include "games/addons/GameClient.h" +#include "games/addons/input/GameClientInput.h" +#include "games/tags/GameInfoTag.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/AnnouncementManager.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/JobManager.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "windowing/WinSystem.h" + +#include + +using namespace KODI; +using namespace GAME; +using namespace RETRO; + +CRetroPlayer::CRetroPlayer(IPlayerCallback& callback) + : IPlayer(callback), m_gameServices(CServiceBroker::GetGameServices()) +{ + ResetPlayback(); + CServiceBroker::GetWinSystem()->RegisterRenderLoop(this); +} + +CRetroPlayer::~CRetroPlayer() +{ + CServiceBroker::GetWinSystem()->UnregisterRenderLoop(this); + CloseFile(); +} + +bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options) +{ + CFileItem fileCopy(file); + + std::string savestatePath; + + // When playing a game, set the game client that we'll use to open the game. + // This will prompt the user to select a savestate if there are any. + // If there are no savestates, or the user wants to create a new savestate + // it will prompt the user to select a game client + if (!GAME::CGameUtils::FillInGameClient(fileCopy, savestatePath)) + { + CLog::Log(LOGINFO, + "RetroPlayer[PLAYER]: No compatible game client selected, aborting playback"); + return false; + } + + // Check if we should open in standalone mode + const bool bStandalone = fileCopy.GetPath().empty(); + + m_processInfo.reset(CRPProcessInfo::CreateInstance()); + if (!m_processInfo) + { + CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to create - no process info registered"); + return false; + } + + m_processInfo->SetDataCache(&CServiceBroker::GetDataCacheCore()); + m_processInfo->ResetInfo(); + + m_guiMessenger = std::make_unique(*m_processInfo); + m_renderManager.reset(new CRPRenderManager(*m_processInfo)); + + std::unique_lock lock(m_mutex); + + if (IsPlaying()) + CloseFile(); + + PrintGameInfo(fileCopy); + + bool bSuccess = false; + + std::string gameClientId = fileCopy.GetGameInfoTag()->GetGameClient(); + + ADDON::AddonPtr addon; + if (gameClientId.empty()) + { + CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Can't play game, no game client was passed!"); + } + else if (!CServiceBroker::GetAddonMgr().GetAddon(gameClientId, addon, ADDON::AddonType::GAMEDLL, + ADDON::OnlyEnabled::CHOICE_YES)) + { + CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Can't find add-on {} for game file!", gameClientId); + } + else + { + m_gameClient = std::static_pointer_cast(addon); + if (m_gameClient->Initialize()) + { + m_streamManager.reset(new CRPStreamManager(*m_renderManager, *m_processInfo)); + + // Initialize input + m_input = std::make_unique(CServiceBroker::GetPeripherals(), + *m_processInfo, m_gameClient); + m_input->StartAgentManager(); + + if (!bStandalone) + { + std::string redactedPath = CURL::GetRedacted(fileCopy.GetPath()); + CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening: {}", redactedPath); + bSuccess = m_gameClient->OpenFile(fileCopy, *m_streamManager, m_input.get()); + } + else + { + CLog::Log(LOGINFO, "RetroPlayer[PLAYER]: Opening standalone"); + bSuccess = m_gameClient->OpenStandalone(*m_streamManager, m_input.get()); + } + + if (bSuccess) + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Using game client {}", gameClientId); + else + CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to open file using {}", gameClientId); + } + else + CLog::Log(LOGERROR, "RetroPlayer[PLAYER]: Failed to initialize {}", gameClientId); + } + + if (bSuccess && !bStandalone) + { + CSavestateDatabase savestateDb; + + std::unique_ptr save = CSavestateDatabase::AllocateSavestate(); + if (savestateDb.GetSavestate(savestatePath, *save)) + { + // Check if game client is the same + if (save->GameClientID() != m_gameClient->ID()) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(save->GameClientID(), addon, + ADDON::OnlyEnabled::CHOICE_YES)) + { + // Warn the user that continuing with a different game client will + // overwrite the save + bool dummy; + if (!CGUIDialogYesNo::ShowAndGetInput( + 438, StringUtils::Format(g_localizeStrings.Get(35217), addon->Name()), dummy, 222, + 35218, 0)) + bSuccess = false; + } + } + } + } + + if (bSuccess) + { + // Switch to fullscreen + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN); + + m_cheevos = std::make_shared(m_gameClient.get(), + m_gameServices.GameSettings().GetRAUsername(), + m_gameServices.GameSettings().GetRAToken()); + + m_cheevos->EnableRichPresence(); + + // Initialize gameplay + CreatePlayback(savestatePath); + RegisterWindowCallbacks(); + m_playbackControl.reset(new CGUIPlaybackControl(*this)); + m_callback.OnPlayBackStarted(fileCopy); + m_callback.OnAVStarted(fileCopy); + if (!bStandalone) + m_autoSave.reset(new CRetroPlayerAutoSave(*this, m_gameServices.GameSettings())); + + // Set video framerate + m_processInfo->SetVideoFps(static_cast(m_gameClient->GetFrameRate())); + } + else + { + m_input.reset(); + m_streamManager.reset(); + if (m_gameClient) + m_gameClient->Unload(); + m_gameClient.reset(); + } + + return bSuccess; +} + +bool CRetroPlayer::CloseFile(bool reopen /* = false */) +{ + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing file"); + + m_autoSave.reset(); + + UnregisterWindowCallbacks(); + + m_playbackControl.reset(); + + std::unique_lock lock(m_mutex); + + if (m_gameClient && m_gameServices.GameSettings().AutosaveEnabled()) + { + std::string savePath = m_playback->CreateSavestate(true); + if (!savePath.empty()) + CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Saved state to {}", CURL::GetRedacted(savePath)); + else + CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Failed to save state at close"); + } + + m_playback.reset(); + + if (m_input) + m_input->StopAgentManager(); + + m_cheevos.reset(); + + if (m_gameClient) + m_gameClient->CloseFile(); + + m_input.reset(); + + m_streamManager.reset(); + + if (m_gameClient) + m_gameClient->Unload(); + m_gameClient.reset(); + + m_renderManager.reset(); + m_processInfo.reset(); + + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Playback ended"); + m_callback.OnPlayBackEnded(); + + return true; +} + +bool CRetroPlayer::IsPlaying() const +{ + if (m_gameClient) + return m_gameClient->IsPlaying(); + return false; +} + +bool CRetroPlayer::CanPause() const +{ + return m_playback->CanPause(); +} + +void CRetroPlayer::Pause() +{ + if (!CanPause()) + return; + + float speed; + + if (m_playback->GetSpeed() == 0.0) + speed = 1.0f; + else + speed = 0.0f; + + SetSpeed(speed); +} + +bool CRetroPlayer::CanSeek() const +{ + return m_playback->CanSeek(); +} + +void CRetroPlayer::Seek(bool bPlus /* = true */, + bool bLargeStep /* = false */, + bool bChapterOverride /* = false */) +{ + if (!CanSeek()) + return; + + if (m_gameClient) + { + //! @todo + /* + if (bPlus) + { + if (bLargeStep) + m_playback->BigSkipForward(); + else + m_playback->SmallSkipForward(); + } + else + { + if (bLargeStep) + m_playback->BigSkipBackward(); + else + m_playback->SmallSkipBackward(); + } + */ + } +} + +void CRetroPlayer::SeekPercentage(float fPercent /* = 0 */) +{ + if (!CanSeek()) + return; + + if (fPercent < 0.0f) + fPercent = 0.0f; + else if (fPercent > 100.0f) + fPercent = 100.0f; + + uint64_t totalTime = GetTotalTime(); + if (totalTime != 0) + SeekTime(static_cast(totalTime * fPercent / 100.0f)); +} + +float CRetroPlayer::GetCachePercentage() const +{ + const float cacheMs = static_cast(m_playback->GetCacheTimeMs()); + const float totalMs = static_cast(m_playback->GetTotalTimeMs()); + + if (totalMs != 0.0f) + return cacheMs / totalMs * 100.0f; + + return 0.0f; +} + +void CRetroPlayer::SetMute(bool bOnOff) +{ + if (m_streamManager) + m_streamManager->EnableAudio(!bOnOff); +} + +void CRetroPlayer::SeekTime(int64_t iTime /* = 0 */) +{ + if (!CanSeek()) + return; + + m_playback->SeekTimeMs(static_cast(iTime)); +} + +bool CRetroPlayer::SeekTimeRelative(int64_t iTime) +{ + if (!CanSeek()) + return false; + + SeekTime(GetTime() + iTime); + + return true; +} + +uint64_t CRetroPlayer::GetTime() +{ + return m_playback->GetTimeMs(); +} + +uint64_t CRetroPlayer::GetTotalTime() +{ + return m_playback->GetTotalTimeMs(); +} + +void CRetroPlayer::SetSpeed(float speed) +{ + if (m_playback->GetSpeed() != static_cast(speed)) + { + if (speed == 1.0f) + m_callback.OnPlayBackResumed(); + else if (speed == 0.0f) + m_callback.OnPlayBackPaused(); + + SetSpeedInternal(static_cast(speed)); + + if (speed == 0.0f) + { + const int dialogId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(); + if (dialogId == WINDOW_FULLSCREEN_GAME) + { + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Opening OSD via speed change ({:f})", speed); + OpenOSD(); + } + } + else + { + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing OSD via speed change ({:f})", speed); + CloseOSD(); + } + } +} + +bool CRetroPlayer::OnAction(const CAction& action) +{ + switch (action.GetID()) + { + case ACTION_PLAYER_RESET: + { + if (m_gameClient) + { + float speed = static_cast(m_playback->GetSpeed()); + + 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 + if (speed <= 0.0f) + speed = 1.0f; + + SetSpeed(speed); + } + return true; + } + case ACTION_SHOW_OSD: + { + if (m_gameClient) + { + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Closing OSD via ACTION_SHOW_OSD"); + CloseOSD(); + return true; + } + break; + } + default: + break; + } + + return false; +} + +std::string CRetroPlayer::GetPlayerState() +{ + std::string savestatePath; + + if (m_autoSave) + { + savestatePath = m_playback->CreateSavestate(true); + if (savestatePath.empty()) + { + CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Continuing without saving"); + m_autoSave.reset(); + } + } + return savestatePath; +} + +bool CRetroPlayer::SetPlayerState(const std::string& state) +{ + return m_playback->LoadSavestate(state); +} + +void CRetroPlayer::FrameMove() +{ + if (m_renderManager) + m_renderManager->FrameMove(); + + if (m_playbackControl) + m_playbackControl->FrameMove(); + + if (m_processInfo) + m_processInfo->SetPlayTimes(0, GetTime(), 0, GetTotalTime()); +} + +void CRetroPlayer::Render(bool clear, uint32_t alpha /* = 255 */, bool gui /* = true */) +{ + // Performed by callbacks +} + +bool CRetroPlayer::IsRenderingVideo() const +{ + return true; +} + +bool CRetroPlayer::HasGameAgent() const +{ + if (m_gameClient) + return m_gameClient->Input().HasAgent(); + + return false; +} + +std::string CRetroPlayer::GameClientID() const +{ + if (m_gameClient) + return m_gameClient->ID(); + + return ""; +} + +std::string CRetroPlayer::GetPlayingGame() const +{ + if (m_gameClient) + return m_gameClient->GetGamePath(); + + return ""; +} + +std::string CRetroPlayer::CreateSavestate(bool autosave) +{ + if (m_playback) + return m_playback->CreateSavestate(autosave); + + return ""; +} + +bool CRetroPlayer::UpdateSavestate(const std::string& savestatePath) +{ + if (m_playback) + return !m_playback->CreateSavestate(false, savestatePath).empty(); + + return false; +} + +bool CRetroPlayer::LoadSavestate(const std::string& savestatePath) +{ + if (m_playback) + return m_playback->LoadSavestate(savestatePath); + + return false; +} + +void CRetroPlayer::FreeSavestateResources(const std::string& savestatePath) +{ + if (m_renderManager) + m_renderManager->ClearVideoFrame(savestatePath); +} + +void CRetroPlayer::CloseOSDCallback() +{ + CloseOSD(); +} + +void CRetroPlayer::SetPlaybackSpeed(double speed) +{ + if (m_playback) + { + if (m_playback->GetSpeed() != speed) + { + if (speed == 1.0) + { + IPlayerCallback* callback = &m_callback; + CServiceBroker::GetJobManager()->Submit([callback]() { callback->OnPlayBackResumed(); }, + CJob::PRIORITY_NORMAL); + } + else if (speed == 0.0) + { + IPlayerCallback* callback = &m_callback; + CServiceBroker::GetJobManager()->Submit([callback]() { callback->OnPlayBackPaused(); }, + CJob::PRIORITY_NORMAL); + } + } + } + + SetSpeedInternal(speed); +} + +void CRetroPlayer::EnableInput(bool bEnable) +{ + if (m_input) + m_input->EnableInput(bEnable); +} + +bool CRetroPlayer::IsAutoSaveEnabled() const +{ + return m_playback->GetSpeed() > 0.0; +} + +std::string CRetroPlayer::CreateAutosave() +{ + return m_playback->CreateSavestate(true); +} + +void CRetroPlayer::SetSpeedInternal(double speed) +{ + OnSpeedChange(speed); + + if (speed == 0.0) + m_playback->PauseAsync(); + else + m_playback->SetSpeed(speed); +} + +void CRetroPlayer::OnSpeedChange(double newSpeed) +{ + m_streamManager->EnableAudio(newSpeed == 1.0); + m_input->SetSpeed(newSpeed); + m_renderManager->SetSpeed(newSpeed); + m_processInfo->SetSpeed(static_cast(newSpeed)); +} + +void CRetroPlayer::CreatePlayback(const std::string& savestatePath) +{ + if (m_gameClient->RequiresGameLoop()) + { + m_playback->Deinitialize(); + m_playback = std::make_unique( + m_gameClient.get(), *m_renderManager, m_cheevos.get(), *m_guiMessenger, + m_gameClient->GetFrameRate(), m_gameClient->GetSerializeSize()); + } + else + ResetPlayback(); + + if (!savestatePath.empty()) + { + const bool bStandalone = m_gameClient->GetGamePath().empty(); + if (!bStandalone) + { + CLog::Log(LOGDEBUG, "RetroPlayer[SAVE]: Loading savestate"); + + if (!SetPlayerState(savestatePath)) + CLog::Log(LOGERROR, "RetroPlayer[SAVE]: Failed to load savestate"); + } + } + + m_playback->Initialize(); +} + +void CRetroPlayer::ResetPlayback() +{ + // Called from the constructor, m_playback might not be initialized + if (m_playback) + m_playback->Deinitialize(); + + m_playback.reset(new CRealtimePlayback); +} + +void CRetroPlayer::OpenOSD() +{ + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_OSD); +} + +void CRetroPlayer::CloseOSD() +{ + CServiceBroker::GetGUI()->GetWindowManager().CloseDialogs(true); +} + +void CRetroPlayer::RegisterWindowCallbacks() +{ + m_gameServices.GameRenderManager().RegisterPlayer(m_renderManager->GetGUIRenderTargetFactory(), + m_renderManager.get(), this); +} + +void CRetroPlayer::UnregisterWindowCallbacks() +{ + m_gameServices.GameRenderManager().UnregisterPlayer(); +} + +void CRetroPlayer::PrintGameInfo(const CFileItem& file) const +{ + const CGameInfoTag* tag = file.GetGameInfoTag(); + if (tag) + { + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: ---------------------------------------"); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game tag loaded"); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: URL: {}", tag->GetURL()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Title: {}", tag->GetTitle()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Platform: {}", tag->GetPlatform()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Genres: {}", + StringUtils::Join(tag->GetGenres(), ", ")); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Developer: {}", tag->GetDeveloper()); + if (tag->GetYear() > 0) + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Year: {}", tag->GetYear()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game Code: {}", tag->GetID()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Region: {}", tag->GetRegion()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Publisher: {}", tag->GetPublisher()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Format: {}", tag->GetFormat()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Cartridge type: {}", tag->GetCartridgeType()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: Game client: {}", tag->GetGameClient()); + CLog::Log(LOGDEBUG, "RetroPlayer[PLAYER]: ---------------------------------------"); + } +} -- cgit v1.2.3