From 075380f8e05273d1ea0ddac4962d66768c9749c2 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 9 Jun 2020 02:05:55 +1000 Subject: [PATCH] Move most helper logic from base HostInterface to FrontendCommon --- src/core/host_interface.cpp | 899 ++--------------- src/core/host_interface.h | 173 +--- src/core/memory_card.cpp | 5 +- src/core/settings.cpp | 2 +- src/core/system.cpp | 21 +- src/core/system.h | 1 + src/duckstation-qt/qthostinterface.cpp | 38 +- src/duckstation-qt/qthostinterface.h | 1 - src/duckstation-sdl/sdl_host_interface.cpp | 8 +- src/frontend-common/common_host_interface.cpp | 913 +++++++++++++++++- src/frontend-common/common_host_interface.h | 168 +++- .../save_state_selector_ui.cpp | 11 +- src/frontend-common/save_state_selector_ui.h | 2 +- 13 files changed, 1156 insertions(+), 1086 deletions(-) diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index db90fd1f9..53f8eda16 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -7,14 +7,10 @@ #include "common/log.h" #include "common/string_util.h" #include "dma.h" -#include "game_list.h" #include "gpu.h" #include "host_display.h" -#include "mdec.h" #include "save_state_version.h" -#include "spu.h" #include "system.h" -#include "timers.h" #include #include #include @@ -22,13 +18,6 @@ #include Log_SetChannel(HostInterface); -#ifdef WIN32 -#include "common/windows_headers.h" -#include -#include -#include -#endif - HostInterface::HostInterface() { // we can get the program directory at construction time @@ -44,16 +33,6 @@ HostInterface::~HostInterface() bool HostInterface::Initialize() { - SetUserDirectory(); - InitializeUserDirectory(); - LoadSettings(); - UpdateLogSettings(m_settings.log_level, m_settings.log_filter.empty() ? nullptr : m_settings.log_filter.c_str(), - m_settings.log_to_console, m_settings.log_to_debug, m_settings.log_to_window, - m_settings.log_to_file); - m_game_list = std::make_unique(); - m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache")); - m_game_list->SetDatabaseFilename(GetUserDirectoryRelativePath("cache/redump.dat")); - m_game_list->SetCompatibilityFilename(GetProgramDirectoryRelativePath("database/compatibility.xml")); return true; } @@ -112,33 +91,10 @@ bool HostInterface::BootSystem(const SystemBootParameters& parameters) OnSystemCreated(); - m_paused = m_settings.start_paused; - m_audio_stream->PauseOutput(m_paused); - UpdateSpeedLimiterState(); - - if (m_paused) - OnSystemPaused(true); - - if (m_settings.audio_dump_on_boot) - StartDumpingAudio(); - + m_audio_stream->PauseOutput(false); return true; } -void HostInterface::PauseSystem(bool paused) -{ - if (paused == m_paused || !m_system) - return; - - m_paused = paused; - m_audio_stream->PauseOutput(m_paused); - OnSystemPaused(paused); - UpdateSpeedLimiterState(); - - if (!paused) - m_system->ResetPerformanceCounters(); -} - void HostInterface::ResetSystem() { m_system->Reset(); @@ -151,9 +107,6 @@ void HostInterface::PowerOffSystem() if (!m_system) return; - if (m_settings.save_state_on_exit) - SaveResumeSaveState(); - DestroySystem(); } @@ -162,9 +115,6 @@ void HostInterface::DestroySystem() if (!m_system) return; - SetTimerResolutionIncreased(false); - - m_paused = false; m_system.reset(); m_audio_stream.reset(); ReleaseHostDisplay(); @@ -218,91 +168,9 @@ bool HostInterface::ConfirmFormattedMessage(const char* format, ...) return ConfirmMessage(message.c_str()); } -void HostInterface::DrawImGuiWindows() +void HostInterface::AddOSDMessage(std::string message, float duration /* = 2.0f */) { - if (m_system) - { - DrawDebugWindows(); - DrawFPSWindow(); - } - - DrawOSDMessages(); -} - -void HostInterface::DrawFPSWindow() -{ - if (!(m_settings.display_show_fps | m_settings.display_show_vps | m_settings.display_show_speed)) - return; - - const ImVec2 window_size = - ImVec2(175.0f * ImGui::GetIO().DisplayFramebufferScale.x, 16.0f * ImGui::GetIO().DisplayFramebufferScale.y); - ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - window_size.x, 0.0f), ImGuiCond_Always); - ImGui::SetNextWindowSize(window_size); - - if (!ImGui::Begin("FPSWindow", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMouseInputs | - ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNav)) - { - ImGui::End(); - return; - } - - bool first = true; - if (m_settings.display_show_fps) - { - ImGui::Text("%.2f", m_system->GetFPS()); - first = false; - } - if (m_settings.display_show_vps) - { - if (first) - { - first = false; - } - else - { - ImGui::SameLine(); - ImGui::Text("/"); - ImGui::SameLine(); - } - - ImGui::Text("%.2f", m_system->GetVPS()); - } - if (m_settings.display_show_speed) - { - if (first) - { - first = false; - } - else - { - ImGui::SameLine(); - ImGui::Text("/"); - ImGui::SameLine(); - } - - const float speed = m_system->GetEmulationSpeed(); - const u32 rounded_speed = static_cast(std::round(speed)); - if (speed < 90.0f) - ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%u%%", rounded_speed); - else if (speed < 110.0f) - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%u%%", rounded_speed); - else - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%u%%", rounded_speed); - } - - ImGui::End(); -} - -void HostInterface::AddOSDMessage(const char* message, float duration /*= 2.0f*/) -{ - OSDMessage msg; - msg.text = message; - msg.duration = duration; - - std::unique_lock lock(m_osd_messages_lock); - m_osd_messages.push_back(std::move(msg)); + Log_InfoPrintf("OSD: %s", message.c_str()); } void HostInterface::AddFormattedOSDMessage(float duration, const char* format, ...) @@ -312,79 +180,7 @@ void HostInterface::AddFormattedOSDMessage(float duration, const char* format, . std::string message = StringUtil::StdStringFromFormatV(format, ap); va_end(ap); - OSDMessage msg; - msg.text = std::move(message); - msg.duration = duration; - - std::unique_lock lock(m_osd_messages_lock); - m_osd_messages.push_back(std::move(msg)); -} - -void HostInterface::DrawOSDMessages() -{ - constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing; - - std::unique_lock lock(m_osd_messages_lock); - if (m_osd_messages.empty()) - return; - - const float scale = ImGui::GetIO().DisplayFramebufferScale.x; - - auto iter = m_osd_messages.begin(); - float position_x = 10.0f * scale; - float position_y = (10.0f + (static_cast(m_display->GetDisplayTopMargin()))) * scale; - u32 index = 0; - while (iter != m_osd_messages.end()) - { - const OSDMessage& msg = *iter; - const double time = msg.time.GetTimeSeconds(); - const float time_remaining = static_cast(msg.duration - time); - if (time_remaining <= 0.0f) - { - iter = m_osd_messages.erase(iter); - continue; - } - - if (!m_settings.display_show_osd_messages) - continue; - - const float opacity = std::min(time_remaining, 1.0f); - ImGui::SetNextWindowPos(ImVec2(position_x, position_y)); - ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity); - - char buf[64]; - std::snprintf(buf, sizeof(buf), "osd_%u", index++); - - if (ImGui::Begin(buf, nullptr, window_flags)) - { - ImGui::TextUnformatted(msg.text.c_str()); - position_y += ImGui::GetWindowSize().y + (4.0f * scale); - } - - ImGui::End(); - ImGui::PopStyleVar(); - ++iter; - } -} - -void HostInterface::DrawDebugWindows() -{ - const Settings::DebugSettings& debug_settings = m_system->GetSettings().debugging; - - if (debug_settings.show_gpu_state) - m_system->GetGPU()->DrawDebugStateWindow(); - if (debug_settings.show_cdrom_state) - m_system->GetCDROM()->DrawDebugWindow(); - if (debug_settings.show_timers_state) - m_system->GetTimers()->DrawDebugStateWindow(); - if (debug_settings.show_spu_state) - m_system->GetSPU()->DrawDebugStateWindow(); - if (debug_settings.show_mdec_state) - m_system->GetMDEC()->DrawDebugStateWindow(); + AddOSDMessage(std::move(message), duration); } std::optional> HostInterface::GetBIOSImage(ConsoleRegion region) @@ -480,19 +276,6 @@ bool HostInterface::LoadState(const char* filename) return true; } -bool HostInterface::LoadState(bool global, s32 slot) -{ - if (!global && (!m_system || m_system->GetRunningCode().empty())) - { - ReportFormattedError("Can't save per-game state without a running game code."); - return false; - } - - std::string save_path = - global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), slot); - return LoadState(save_path.c_str()); -} - bool HostInterface::SaveState(const char* filename) { std::unique_ptr stream = @@ -516,106 +299,8 @@ bool HostInterface::SaveState(const char* filename) return result; } -bool HostInterface::SaveState(bool global, s32 slot) -{ - const std::string& code = m_system->GetRunningCode(); - if (!global && code.empty()) - { - ReportFormattedError("Can't save per-game state without a running game code."); - return false; - } - - std::string save_path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(code.c_str(), slot); - if (!SaveState(save_path.c_str())) - return false; - - OnSystemStateSaved(global, slot); - return true; -} - -bool HostInterface::ResumeSystemFromState(const char* filename, bool boot_on_failure) -{ - SystemBootParameters boot_params; - boot_params.filename = filename; - if (!BootSystem(boot_params)) - return false; - - const bool global = m_system->GetRunningCode().empty(); - if (m_system->GetRunningCode().empty()) - { - ReportFormattedError("Cannot resume system with undetectable game code from '%s'.", filename); - if (!boot_on_failure) - { - DestroySystem(); - return true; - } - } - else - { - const std::string path = GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), -1); - if (FileSystem::FileExists(path.c_str())) - { - if (!LoadState(path.c_str()) && !boot_on_failure) - { - DestroySystem(); - return false; - } - } - else if (!boot_on_failure) - { - ReportFormattedError("Resume save state not found for '%s' ('%s').", m_system->GetRunningCode().c_str(), - m_system->GetRunningTitle().c_str()); - DestroySystem(); - return false; - } - } - - return true; -} - -bool HostInterface::ResumeSystemFromMostRecentState() -{ - const std::string path = GetMostRecentResumeSaveStatePath(); - if (path.empty()) - { - ReportError("No resume save state found."); - return false; - } - - return LoadState(path.c_str()); -} - -void HostInterface::UpdateSpeedLimiterState() -{ - m_speed_limiter_enabled = m_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; - - const bool is_non_standard_speed = (std::abs(m_settings.emulation_speed - 1.0f) > 0.05f); - const bool audio_sync_enabled = - !m_system || m_paused || (m_speed_limiter_enabled && m_settings.audio_sync_enabled && !is_non_standard_speed); - const bool video_sync_enabled = - !m_system || m_paused || (m_speed_limiter_enabled && m_settings.video_sync_enabled && !is_non_standard_speed); - Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "", - (audio_sync_enabled && video_sync_enabled) ? " and video" : (video_sync_enabled ? "video" : "")); - - m_audio_stream->SetSync(audio_sync_enabled); - if (audio_sync_enabled) - m_audio_stream->EmptyBuffers(); - - m_display->SetVSync(video_sync_enabled); - - if (m_settings.increase_timer_resolution) - SetTimerResolutionIncreased(m_speed_limiter_enabled); - - m_system->ResetPerformanceCounters(); -} - void HostInterface::OnSystemCreated() {} -void HostInterface::OnSystemPaused(bool paused) -{ - ReportFormattedMessage("System %s.", paused ? "paused" : "resumed"); -} - void HostInterface::OnSystemDestroyed() { ReportFormattedMessage("System shut down."); @@ -629,347 +314,6 @@ void HostInterface::OnRunningGameChanged() {} void HostInterface::OnControllerTypeChanged(u32 slot) {} -void HostInterface::UpdateLogSettings(LOGLEVEL level, const char* filter, bool log_to_console, bool log_to_debug, - bool log_to_window, bool log_to_file) -{ - Log::SetFilterLevel(level); - Log::SetConsoleOutputParams(m_settings.log_to_console, filter, level); - Log::SetDebugOutputParams(m_settings.log_to_debug, filter, level); - - if (log_to_file) - { - Log::SetFileOutputParams(m_settings.log_to_file, GetUserDirectoryRelativePath("duckstation.log").c_str(), true, - filter, level); - } - else - { - Log::SetFileOutputParams(false, nullptr); - } -} - -void HostInterface::SetUserDirectory() -{ - if (!m_user_directory.empty()) - return; - - std::fprintf(stdout, "Program directory \"%s\"\n", m_program_directory.c_str()); - - if (FileSystem::FileExists(StringUtil::StdStringFromFormat("%s%c%s", m_program_directory.c_str(), - FS_OSPATH_SEPERATOR_CHARACTER, "portable.txt") - .c_str()) || - FileSystem::FileExists(StringUtil::StdStringFromFormat("%s%c%s", m_program_directory.c_str(), - FS_OSPATH_SEPERATOR_CHARACTER, "settings.ini") - .c_str())) - { - std::fprintf(stdout, "portable.txt or old settings.ini found, using program directory as user directory.\n"); - m_user_directory = m_program_directory; - } - else - { -#ifdef WIN32 - // On Windows, use My Documents\DuckStation. - PWSTR documents_directory; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory))) - { - const size_t documents_directory_len = std::wcslen(documents_directory); - int documents_directory_u8len = WideCharToMultiByte( - CP_UTF8, 0, documents_directory, static_cast(documents_directory_len), nullptr, 0, nullptr, nullptr); - if (documents_directory_u8len > 0) - { - std::string documents_directory_str; - documents_directory_str.resize(documents_directory_u8len); - documents_directory_u8len = WideCharToMultiByte( - CP_UTF8, 0, documents_directory, static_cast(documents_directory_len), documents_directory_str.data(), - static_cast(documents_directory_str.size()), 0, nullptr); - if (documents_directory_u8len > 0) - { - documents_directory_str.resize(documents_directory_u8len); - m_user_directory = StringUtil::StdStringFromFormat("%s%c%s", documents_directory_str.c_str(), - FS_OSPATH_SEPERATOR_CHARACTER, "DuckStation"); - } - } - CoTaskMemFree(documents_directory); - } -#elif __linux__ - // On Linux, use .local/share/duckstation as a user directory by default. - const char* xdg_data_home = getenv("XDG_DATA_HOME"); - if (xdg_data_home && xdg_data_home[0] == '/') - { - m_user_directory = StringUtil::StdStringFromFormat("%s/duckstation", xdg_data_home); - } - else - { - const char* home_path = getenv("HOME"); - if (home_path) - m_user_directory = StringUtil::StdStringFromFormat("%s/.local/share/duckstation", home_path); - } -#elif __APPLE__ - // On macOS, default to ~/Library/Application Support/DuckStation. - const char* home_path = getenv("HOME"); - if (home_path) - m_user_directory = StringUtil::StdStringFromFormat("%s/Library/Application Support/DuckStation", home_path); -#endif - - if (m_user_directory.empty()) - { - std::fprintf(stderr, "User directory path could not be determined, falling back to program directory."); - m_user_directory = m_program_directory; - } - } -} - -void HostInterface::SetUserDirectoryToProgramDirectory() -{ - const std::string program_path = FileSystem::GetProgramPath(); - const std::string program_directory = FileSystem::GetPathDirectory(program_path.c_str()); - m_user_directory = program_directory; -} - -void HostInterface::InitializeUserDirectory() -{ - std::fprintf(stdout, "User directory: \"%s\"\n", m_user_directory.c_str()); - - if (m_user_directory.empty()) - Panic("Cannot continue without user directory set."); - - if (!FileSystem::DirectoryExists(m_user_directory.c_str())) - { - std::fprintf(stderr, "User directory \"%s\" does not exist, creating.\n", m_user_directory.c_str()); - if (!FileSystem::CreateDirectory(m_user_directory.c_str(), true)) - std::fprintf(stderr, "Failed to create user directory \"%s\".\n", m_user_directory.c_str()); - } - - bool result = true; - - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("bios").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("cache").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("dump").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("dump/audio").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("inputprofiles").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("savestates").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("screenshots").c_str(), false); - result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("memcards").c_str(), false); - - if (!result) - ReportError("Failed to create one or more user directories. This may cause issues at runtime."); -} - -std::string HostInterface::GetUserDirectoryRelativePath(const char* format, ...) const -{ - std::va_list ap; - va_start(ap, format); - std::string formatted_path = StringUtil::StdStringFromFormatV(format, ap); - va_end(ap); - - if (m_user_directory.empty()) - { - return formatted_path; - } - else - { - return StringUtil::StdStringFromFormat("%s%c%s", m_user_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, - formatted_path.c_str()); - } -} - -std::string HostInterface::GetProgramDirectoryRelativePath(const char* format, ...) const -{ - std::va_list ap; - va_start(ap, format); - std::string formatted_path = StringUtil::StdStringFromFormatV(format, ap); - va_end(ap); - - if (m_program_directory.empty()) - { - return formatted_path; - } - else - { - return StringUtil::StdStringFromFormat("%s%c%s", m_program_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, - formatted_path.c_str()); - } -} - -TinyString HostInterface::GetTimestampStringForFileName() -{ - const Timestamp ts(Timestamp::Now()); - - TinyString str; - ts.ToString(str, "%Y-%m-%d_%H-%M-%S"); - return str; -} - -std::string HostInterface::GetSettingsFileName() const -{ - return GetUserDirectoryRelativePath("settings.ini"); -} - -std::string HostInterface::GetGameSaveStateFileName(const char* game_code, s32 slot) const -{ - if (slot < 0) - return GetUserDirectoryRelativePath("savestates/%s_resume.sav", game_code); - else - return GetUserDirectoryRelativePath("savestates/%s_%d.sav", game_code, slot); -} - -std::string HostInterface::GetGlobalSaveStateFileName(s32 slot) const -{ - if (slot < 0) - return GetUserDirectoryRelativePath("savestates/resume.sav"); - else - return GetUserDirectoryRelativePath("savestates/savestate_%d.sav", slot); -} - -std::string HostInterface::GetSharedMemoryCardPath(u32 slot) const -{ - return GetUserDirectoryRelativePath("memcards/shared_card_%d.mcd", slot + 1); -} - -std::string HostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const -{ - return GetUserDirectoryRelativePath("memcards/%s_%d.mcd", game_code, slot + 1); -} - -std::vector HostInterface::GetAvailableSaveStates(const char* game_code) const -{ - std::vector si; - std::string path; - - auto add_path = [&si](std::string path, s32 slot, bool global) { - FILESYSTEM_STAT_DATA sd; - if (!FileSystem::StatFile(path.c_str(), &sd)) - return; - - si.push_back(SaveStateInfo{std::move(path), sd.ModificationTime.AsUnixTimestamp(), static_cast(slot), global}); - }; - - if (game_code && std::strlen(game_code) > 0) - { - add_path(GetGameSaveStateFileName(game_code, -1), -1, false); - for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++) - add_path(GetGameSaveStateFileName(game_code, i), i, false); - } - - for (s32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++) - add_path(GetGlobalSaveStateFileName(i), i, true); - - return si; -} - -std::optional HostInterface::GetSaveStateInfo(const char* game_code, s32 slot) -{ - const bool global = (!game_code || game_code[0] == 0); - std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(game_code, slot); - - FILESYSTEM_STAT_DATA sd; - if (!FileSystem::StatFile(path.c_str(), &sd)) - return std::nullopt; - - return SaveStateInfo{std::move(path), sd.ModificationTime.AsUnixTimestamp(), slot, global}; -} - -std::optional HostInterface::GetExtendedSaveStateInfo(const char* game_code, - s32 slot) -{ - const bool global = (!game_code || game_code[0] == 0); - std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(game_code, slot); - - FILESYSTEM_STAT_DATA sd; - if (!FileSystem::StatFile(path.c_str(), &sd)) - return std::nullopt; - - std::unique_ptr stream = - FileSystem::OpenFile(path.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_SEEKABLE); - if (!stream) - return std::nullopt; - - SAVE_STATE_HEADER header; - if (!stream->Read(&header, sizeof(header)) || header.magic != SAVE_STATE_MAGIC) - return std::nullopt; - - ExtendedSaveStateInfo ssi; - ssi.path = std::move(path); - ssi.timestamp = sd.ModificationTime.AsUnixTimestamp(); - ssi.slot = slot; - ssi.global = global; - - if (header.version != SAVE_STATE_VERSION) - { - ssi.title = StringUtil::StdStringFromFormat("Invalid version %u (expected %u)", header.version, header.magic, - SAVE_STATE_VERSION); - return ssi; - } - - header.title[sizeof(header.title) - 1] = 0; - ssi.title = header.title; - header.game_code[sizeof(header.game_code) - 1] = 0; - ssi.game_code = header.game_code; - - if (header.screenshot_width > 0 && header.screenshot_height > 0 && header.screenshot_size > 0 && - (static_cast(header.offset_to_screenshot) + static_cast(header.screenshot_size)) <= stream->GetSize()) - { - ssi.screenshot_data.resize((header.screenshot_size + 3u) / 4u); - if (stream->Read2(ssi.screenshot_data.data(), header.screenshot_size)) - { - ssi.screenshot_width = header.screenshot_width; - ssi.screenshot_height = header.screenshot_height; - } - else - { - decltype(ssi.screenshot_data)().swap(ssi.screenshot_data); - } - } - - return ssi; -} - -void HostInterface::DeleteSaveStates(const char* game_code, bool resume) -{ - const std::vector states(GetAvailableSaveStates(game_code)); - for (const SaveStateInfo& si : states) - { - if (si.global || (!resume && si.slot < 0)) - continue; - - Log_InfoPrintf("Removing save state at '%s'", si.path.c_str()); - if (!FileSystem::DeleteFile(si.path.c_str())) - Log_ErrorPrintf("Failed to delete save state file '%s'", si.path.c_str()); - } -} - -std::string HostInterface::GetMostRecentResumeSaveStatePath() const -{ - std::vector files; - if (!FileSystem::FindFiles(GetUserDirectoryRelativePath("savestates").c_str(), "*resume.sav", FILESYSTEM_FIND_FILES, - &files) || - files.empty()) - { - return {}; - } - - FILESYSTEM_FIND_DATA* most_recent = &files[0]; - for (FILESYSTEM_FIND_DATA& file : files) - { - if (file.ModificationTime > most_recent->ModificationTime) - most_recent = &file; - } - - return std::move(most_recent->FileName); -} - -void HostInterface::CheckSettings(SettingsInterface& si) -{ - const int settings_version = si.GetIntValue("Main", "SettingsVersion", -1); - if (settings_version == SETTINGS_VERSION) - return; - - ReportFormattedError("Settings version %d does not match expected version %d, resetting", settings_version, - SETTINGS_VERSION); - si.Clear(); - si.SetIntValue("Main", "SettingsVersion", SETTINGS_VERSION); - SetDefaultSettings(si); -} - void HostInterface::SetDefaultSettings(SettingsInterface& si) { si.SetStringValue("Console", "Region", Settings::GetConsoleRegionName(ConsoleRegion::Auto)); @@ -1045,21 +389,18 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("Debug", "ShowMDECState", false); } -void HostInterface::ApplySettings(SettingsInterface& si) +void HostInterface::LoadSettings(SettingsInterface& si) { m_settings.Load(si); } -void HostInterface::ExportSettings(SettingsInterface& si) +void HostInterface::SaveSettings(SettingsInterface& si) { m_settings.Save(si); } -void HostInterface::UpdateSettings(SettingsInterface& si) +void HostInterface::CheckForSettingsChanges(const Settings& old_settings) { - Settings old_settings(std::move(m_settings)); - ApplySettings(si); - if (m_system) { if (m_settings.gpu_renderer != old_settings.gpu_renderer || @@ -1079,23 +420,11 @@ void HostInterface::UpdateSettings(SettingsInterface& si) DebugAssert(m_audio_stream); m_audio_stream.reset(); CreateAudioStream(); - m_audio_stream->PauseOutput(m_paused); - UpdateSpeedLimiterState(); - } - - if (m_settings.video_sync_enabled != old_settings.video_sync_enabled || - m_settings.audio_sync_enabled != old_settings.audio_sync_enabled || - m_settings.speed_limiter_enabled != old_settings.speed_limiter_enabled || - m_settings.increase_timer_resolution != old_settings.increase_timer_resolution) - { - UpdateSpeedLimiterState(); + m_audio_stream->PauseOutput(false); } if (m_settings.emulation_speed != old_settings.emulation_speed) - { m_system->UpdateThrottlePeriod(); - UpdateSpeedLimiterState(); - } if (m_settings.cpu_execution_mode != old_settings.cpu_execution_mode) { @@ -1168,15 +497,68 @@ void HostInterface::UpdateSettings(SettingsInterface& si) m_settings.display_software_cursor_scale); } } +} - if (m_settings.log_level != old_settings.log_level || m_settings.log_filter != old_settings.log_filter || - m_settings.log_to_console != old_settings.log_to_console || - m_settings.log_to_window != old_settings.log_to_window || m_settings.log_to_file != old_settings.log_to_file) +void HostInterface::SetUserDirectoryToProgramDirectory() +{ + const std::string program_path = FileSystem::GetProgramPath(); + const std::string program_directory = FileSystem::GetPathDirectory(program_path.c_str()); + m_user_directory = program_directory; +} + +std::string HostInterface::GetUserDirectoryRelativePath(const char* format, ...) const +{ + std::va_list ap; + va_start(ap, format); + std::string formatted_path = StringUtil::StdStringFromFormatV(format, ap); + va_end(ap); + + if (m_user_directory.empty()) { - UpdateLogSettings(m_settings.log_level, m_settings.log_filter.empty() ? nullptr : m_settings.log_filter.c_str(), - m_settings.log_to_console, m_settings.log_to_debug, m_settings.log_to_window, - m_settings.log_to_file); + return formatted_path; } + else + { + return StringUtil::StdStringFromFormat("%s%c%s", m_user_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, + formatted_path.c_str()); + } +} + +std::string HostInterface::GetProgramDirectoryRelativePath(const char* format, ...) const +{ + std::va_list ap; + va_start(ap, format); + std::string formatted_path = StringUtil::StdStringFromFormatV(format, ap); + va_end(ap); + + if (m_program_directory.empty()) + { + return formatted_path; + } + else + { + return StringUtil::StdStringFromFormat("%s%c%s", m_program_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, + formatted_path.c_str()); + } +} + +TinyString HostInterface::GetTimestampStringForFileName() +{ + const Timestamp ts(Timestamp::Now()); + + TinyString str; + ts.ToString(str, "%Y-%m-%d_%H-%M-%S"); + return str; +} + +std::string HostInterface::GetSharedMemoryCardPath(u32 slot) const +{ + return GetUserDirectoryRelativePath("memcards/shared_card_%d.mcd", slot + 1); +} + +std::string HostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const +{ + return GetUserDirectoryRelativePath("memcards/%s_%d.mcd", game_code, slot + 1); } void HostInterface::ToggleSoftwareRendering() @@ -1209,8 +591,6 @@ void HostInterface::ModifyResolutionScale(s32 increment) void HostInterface::RecreateSystem() { - const bool was_paused = m_paused; - std::unique_ptr stream = ByteStream_CreateGrowableMemoryStream(nullptr, 8 * 1024); if (!m_system->SaveState(stream.get()) || !stream->SeekAbsolute(0)) { @@ -1230,153 +610,12 @@ void HostInterface::RecreateSystem() } m_system->ResetPerformanceCounters(); - PauseSystem(was_paused); -} - -void HostInterface::SetTimerResolutionIncreased(bool enabled) -{ - if (m_timer_resolution_increased == enabled) - return; - - m_timer_resolution_increased = enabled; - -#ifdef WIN32 - if (enabled) - timeBeginPeriod(1); - else - timeEndPeriod(1); -#endif } void HostInterface::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/, int progress_value /*= -1*/) { - const auto& io = ImGui::GetIO(); - const float scale = io.DisplayFramebufferScale.x; - const float width = (400.0f * scale); - const bool has_progress = (progress_min < progress_max); - - // eat the last imgui frame, it might've been partially rendered by the caller. - ImGui::EndFrame(); - ImGui::NewFrame(); - - ImGui::SetNextWindowSize(ImVec2(width, (has_progress ? 50.0f : 30.0f) * scale), ImGuiCond_Always); - ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), ImGuiCond_Always, - ImVec2(0.5f, 0.5f)); - if (ImGui::Begin("LoadingScreen", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing)) - { - if (has_progress) - { - ImGui::Text("%s: %d/%d", message, progress_value, progress_max); - ImGui::ProgressBar(static_cast(progress_value) / static_cast(progress_max - progress_min), - ImVec2(-1.0f, 0.0f), ""); - Log_InfoPrintf("%s: %d/%d", message, progress_value, progress_max); - } - else - { - const ImVec2 text_size(ImGui::CalcTextSize(message)); - ImGui::SetCursorPosX((width - text_size.x) / 2.0f); - ImGui::TextUnformatted(message); - Log_InfoPrintf("%s", message); - } - } - ImGui::End(); - - m_display->Render(); -} - -bool HostInterface::SaveResumeSaveState() -{ - if (!m_system) - return false; - - const bool global = m_system->GetRunningCode().empty(); - return SaveState(global, -1); -} - -bool HostInterface::IsDumpingAudio() const -{ - return m_system ? m_system->GetSPU()->IsDumpingAudio() : false; -} - -bool HostInterface::StartDumpingAudio(const char* filename) -{ - if (!m_system) - return false; - - std::string auto_filename; - if (!filename) - { - const auto& code = m_system->GetRunningCode(); - if (code.empty()) - { - auto_filename = GetUserDirectoryRelativePath("dump/audio/%s.wav", GetTimestampStringForFileName().GetCharArray()); - } - else - { - auto_filename = GetUserDirectoryRelativePath("dump/audio/%s_%s.wav", code.c_str(), - GetTimestampStringForFileName().GetCharArray()); - } - - filename = auto_filename.c_str(); - } - - if (m_system->GetSPU()->StartDumpingAudio(filename)) - { - AddFormattedOSDMessage(5.0f, "Started dumping audio to '%s'.", filename); - return true; - } - else - { - AddFormattedOSDMessage(10.0f, "Failed to start dumping audio to '%s'.", filename); - return false; - } -} - -void HostInterface::StopDumpingAudio() -{ - if (!m_system || !m_system->GetSPU()->StopDumpingAudio()) - return; - - AddOSDMessage("Stopped dumping audio.", 5.0f); -} - -bool HostInterface::SaveScreenshot(const char* filename /* = nullptr */, bool full_resolution /* = true */, - bool apply_aspect_ratio /* = true */) -{ - if (!m_system) - return false; - - std::string auto_filename; - if (!filename) - { - const auto& code = m_system->GetRunningCode(); - const char* extension = "png"; - if (code.empty()) - { - auto_filename = - GetUserDirectoryRelativePath("screenshots/%s.%s", GetTimestampStringForFileName().GetCharArray(), extension); - } - else - { - auto_filename = GetUserDirectoryRelativePath("screenshots/%s_%s.%s", code.c_str(), - GetTimestampStringForFileName().GetCharArray(), extension); - } - - filename = auto_filename.c_str(); - } - - if (!m_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio)) - { - AddFormattedOSDMessage(10.0f, "Failed to save screenshot to '%s'", filename); - return false; - } - - AddFormattedOSDMessage(5.0f, "Screenshot saved to '%s'.", filename); - return true; + Log_InfoPrintf("Loading: %s %d of %d-%d", message, progress_value, progress_min, progress_max); } void HostInterface::EnableSoftwareCursor() @@ -1396,3 +635,5 @@ void HostInterface::DisableSoftwareCursor() m_display->ClearSoftwareCursor(); } + +void HostInterface::GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) {} diff --git a/src/core/host_interface.h b/src/core/host_interface.h index c719de509..b0738e67e 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -28,12 +28,6 @@ class HostInterface friend System; public: - enum : s32 - { - PER_GAME_SAVE_STATE_SLOTS = 10, - GLOBAL_SAVE_STATE_SLOTS = 10 - }; - enum : u32 { AUDIO_SAMPLE_RATE = 44100, @@ -41,29 +35,6 @@ public: DEFAULT_AUDIO_BUFFER_SIZE = 2048 }; - struct SaveStateInfo - { - std::string path; - u64 timestamp; - s32 slot; - bool global; - }; - - struct ExtendedSaveStateInfo - { - std::string path; - u64 timestamp; - s32 slot; - bool global; - - std::string title; - std::string game_code; - - u32 screenshot_width; - u32 screenshot_height; - std::vector screenshot_data; - }; - HostInterface(); virtual ~HostInterface(); @@ -76,9 +47,6 @@ public: /// Returns a settings object which can be modified. ALWAYS_INLINE Settings& GetSettings() { return m_settings; } - /// Returns the game list. - ALWAYS_INLINE const GameList* GetGameList() const { return m_game_list.get(); } - /// Access to emulated system. ALWAYS_INLINE System* GetSystem() const { return m_system.get(); } @@ -91,41 +59,12 @@ public: virtual bool BootSystem(const SystemBootParameters& parameters); virtual void PowerOffSystem(); - void PauseSystem(bool paused); - void ResetSystem(); - void DestroySystem(); + virtual void ResetSystem(); + virtual void DestroySystem(); /// Loads state from the specified filename. bool LoadState(const char* filename); - /// Loads the current emulation state from file. Specifying a slot of -1 loads the "resume" game state. - bool LoadState(bool global, s32 slot); - - /// Saves the current emulation state to a file. Specifying a slot of -1 saves the "resume" save state. - bool SaveState(bool global, s32 slot); - - /// Loads the resume save state for the given game. Optionally boots the game anyway if loading fails. - bool ResumeSystemFromState(const char* filename, bool boot_on_failure); - - /// Loads the most recent resume save state. This may be global or per-game. - bool ResumeSystemFromMostRecentState(); - - /// Saves the resume save state, call when shutting down. Not called automatically on DestroySystem() since that can - /// be called as a result of an error. - bool SaveResumeSaveState(); - - /// Returns true if currently dumping audio. - bool IsDumpingAudio() const; - - /// Starts dumping audio to a file. If no file name is provided, one will be generated automatically. - bool StartDumpingAudio(const char* filename = nullptr); - - /// Stops dumping audio to file if it has been started. - void StopDumpingAudio(); - - /// Saves a screenshot to the specified file. IF no file name is provided, one will be generated automatically. - bool SaveScreenshot(const char* filename = nullptr, bool full_resolution = true, bool apply_aspect_ratio = true); - virtual void ReportError(const char* message); virtual void ReportMessage(const char* message); virtual bool ConfirmMessage(const char* message); @@ -135,7 +74,7 @@ public: bool ConfirmFormattedMessage(const char* format, ...); /// Adds OSD messages, duration is in seconds. - void AddOSDMessage(const char* message, float duration = 2.0f); + virtual void AddOSDMessage(std::string message, float duration = 2.0f); void AddFormattedOSDMessage(float duration, const char* format, ...); /// Returns the base user directory path. @@ -152,19 +91,10 @@ public: /// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks /// such as compiling shaders when starting up. - void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1); + virtual void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1); - /// Returns a list of save states for the specified game code. - std::vector GetAvailableSaveStates(const char* game_code) const; - - /// Returns save state info if present. If game_code is null or empty, assumes global state. - std::optional GetSaveStateInfo(const char* game_code, s32 slot); - - /// Returns save state info if present. If game_code is null or empty, assumes global state. - std::optional GetExtendedSaveStateInfo(const char* game_code, s32 slot); - - /// Deletes save states for the specified game code. If resume is set, the resume state is deleted too. - void DeleteSaveStates(const char* game_code, bool resume); + /// Retrieves information about specified game from game list. + virtual void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title); /// Enables the software cursor. Can be called multiple times, but must be matched by a call to DisableSoftwareCursor(). void EnableSoftwareCursor(); @@ -173,118 +103,59 @@ public: void DisableSoftwareCursor(); protected: - enum : u32 - { - SETTINGS_VERSION = 2 - }; - - struct OSDMessage - { - std::string text; - Common::Timer time; - float duration; - }; - virtual bool AcquireHostDisplay() = 0; virtual void ReleaseHostDisplay() = 0; virtual std::unique_ptr CreateAudioStream(AudioBackend backend) = 0; virtual void OnSystemCreated(); - virtual void OnSystemPaused(bool paused); virtual void OnSystemDestroyed(); virtual void OnSystemPerformanceCountersUpdated(); virtual void OnSystemStateSaved(bool global, s32 slot); virtual void OnRunningGameChanged(); virtual void OnControllerTypeChanged(u32 slot); - virtual void DrawImGuiWindows(); - /// Sets the base path for the user directory. Can be overridden by platform/frontend/command line. - virtual void SetUserDirectory(); + /// Restores all settings to defaults. + virtual void SetDefaultSettings(SettingsInterface& si); + + /// Loads settings to m_settings and any frontend-specific parameters. + virtual void LoadSettings(SettingsInterface& si); + + /// Saves current settings variables to ini. + virtual void SaveSettings(SettingsInterface& si); + + /// Checks for settings changes, std::move() the old settings away for comparing beforehand. + virtual void CheckForSettingsChanges(const Settings& old_settings); + + /// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed). + virtual void RecreateSystem(); /// Sets the user directory to the program directory, i.e. "portable mode". void SetUserDirectoryToProgramDirectory(); - /// Performs the initial load of settings. Should call CheckSettings() and ApplySettings(). - virtual void LoadSettings() = 0; - - /// Updates logging settings. - virtual void UpdateLogSettings(LOGLEVEL level, const char* filter, bool log_to_console, bool log_to_debug, - bool log_to_window, bool log_to_file); - - /// Returns the path of the settings file. - std::string GetSettingsFileName() const; - - /// Returns the path to a save state file. Specifying an index of -1 is the "resume" save state. - std::string GetGameSaveStateFileName(const char* game_code, s32 slot) const; - - /// Returns the path to a save state file. Specifying an index of -1 is the "resume" save state. - std::string GetGlobalSaveStateFileName(s32 slot) const; - /// Returns the default path to a memory card. std::string GetSharedMemoryCardPath(u32 slot) const; /// Returns the default path to a memory card for a specific game. std::string GetGameMemoryCardPath(const char* game_code, u32 slot) const; - /// Returns the most recent resume save state. - std::string GetMostRecentResumeSaveStatePath() const; - /// Loads the BIOS image for the specified region. std::optional> GetBIOSImage(ConsoleRegion region); - /// Ensures the settings is valid and the correct version. If not, resets to defaults. - void CheckSettings(SettingsInterface& si); - - /// Restores all settings to defaults. - virtual void SetDefaultSettings(SettingsInterface& si); - - /// Loads settings to m_settings and any frontend-specific parameters. - virtual void ApplySettings(SettingsInterface& si); - - /// Saves current settings variables to ini. - virtual void ExportSettings(SettingsInterface& si); - - /// Applies new settings, updating internal state as needed. - virtual void UpdateSettings(SettingsInterface& si); - /// Quick switch between software and hardware rendering. void ToggleSoftwareRendering(); /// Adjusts the internal (render) resolution of the hardware backends. void ModifyResolutionScale(s32 increment); - /// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed). - void RecreateSystem(); - - /// Increases timer resolution when supported by the host OS. - void SetTimerResolutionIncreased(bool enabled); - - void UpdateSpeedLimiterState(); - - void DrawFPSWindow(); - void DrawOSDMessages(); - void DrawDebugWindows(); + bool SaveState(const char* filename); + void CreateAudioStream(); HostDisplay* m_display = nullptr; std::unique_ptr m_audio_stream; std::unique_ptr m_system; - std::unique_ptr m_game_list; Settings m_settings; std::string m_program_directory; std::string m_user_directory; - std::deque m_osd_messages; - std::mutex m_osd_messages_lock; - u32 m_software_cursor_use_count = 0; - - bool m_paused = false; - bool m_speed_limiter_temp_disabled = false; - bool m_speed_limiter_enabled = false; - bool m_timer_resolution_increased = false; - -private: - void InitializeUserDirectory(); - void CreateAudioStream(); - bool SaveState(const char* filename); }; diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp index f48b62f5b..f1d405bf0 100644 --- a/src/core/memory_card.cpp +++ b/src/core/memory_card.cpp @@ -1,6 +1,7 @@ #include "memory_card.h" #include "common/byte_stream.h" #include "common/file_system.h" +#include "common/string_util.h" #include "common/log.h" #include "common/state_wrapper.h" #include "host_interface.h" @@ -254,7 +255,7 @@ std::unique_ptr MemoryCard::Open(System* system, std::string_view fi message.AppendString(filename.data(), static_cast(filename.length())); message.AppendString("' could not be read, formatting."); Log_ErrorPrint(message); - system->GetHostInterface()->AddOSDMessage(message, 5.0f); + system->GetHostInterface()->AddOSDMessage(message.GetCharArray(), 5.0f); mc->Format(); } @@ -385,7 +386,7 @@ bool MemoryCard::SaveIfChanged(bool display_osd_message) if (display_osd_message) { m_system->GetHostInterface()->AddOSDMessage( - SmallString::FromFormat("Saved memory card to '%s'", m_filename.c_str())); + StringUtil::StdStringFromFormat("Saved memory card to '%s'", m_filename.c_str())); } return true; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index c0cec23ba..7d6103ae7 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -73,7 +73,7 @@ void Settings::Load(SettingsInterface& si) gpu_max_run_ahead = si.GetIntValue("Hacks", "GPUMaxRunAhead", DEFAULT_GPU_MAX_RUN_AHEAD); bios_path = si.GetStringValue("BIOS", "Path", "bios/scph1001.bin"); - bios_patch_tty_enable = si.GetBoolValue("BIOS", "PatchTTYEnable", true); + bios_patch_tty_enable = si.GetBoolValue("BIOS", "PatchTTYEnable", false); bios_patch_fast_boot = si.GetBoolValue("BIOS", "PatchFastBoot", false); controller_types[0] = ParseControllerTypeName(si.GetStringValue("Controller1", "Type", "DigitalController").c_str()) diff --git a/src/core/system.cpp b/src/core/system.cpp index cb5b328b7..3bd4884ba 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1133,26 +1133,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image) if (path && std::strlen(path) > 0) { m_running_game_path = path; - - const GameListEntry* list_entry = m_host_interface->GetGameList()->GetEntryForPath(path); - if (list_entry) - { - m_running_game_code = list_entry->code; - m_running_game_title = list_entry->title; - } - else - { - if (image) - m_running_game_code = GameList::GetGameCodeForImage(image); - - const GameListDatabaseEntry* db_entry = - (!m_running_game_code.empty()) ? m_host_interface->GetGameList()->GetDatabaseEntryForCode(m_running_game_code) : - nullptr; - if (db_entry) - m_running_game_title = db_entry->title; - else - m_running_game_title = GameList::GetTitleForPath(path); - } + m_host_interface->GetGameInfo(path, image, &m_running_game_code, &m_running_game_title); } m_host_interface->OnRunningGameChanged(); diff --git a/src/core/system.h b/src/core/system.h index 85a807816..bddd24192 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -90,6 +90,7 @@ public: float GetEmulationSpeed() const { return m_speed; } float GetAverageFrameTime() const { return m_average_frame_time; } float GetWorstFrameTime() const { return m_worst_frame_time; } + float GetThrottleFrequency() const { return m_throttle_frequency; } bool Boot(const SystemBootParameters& params); void Reset(); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 4c7ca0d41..18233d813 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -144,13 +144,15 @@ void QtHostInterface::setDefaultSettings() return; } - QtSettingsInterface si(m_qsettings.get()); + Settings old_settings(std::move(m_settings)); { + QtSettingsInterface si(m_qsettings.get()); std::lock_guard guard(m_qsettings_mutex); SetDefaultSettings(si); + CommonHostInterface::LoadSettings(si); } - UpdateSettings(si); - CommonHostInterface::UpdateInputMap(si); + + CheckForSettingsChanges(old_settings); } void QtHostInterface::applySettings() @@ -161,15 +163,19 @@ void QtHostInterface::applySettings() return; } - std::lock_guard guard(m_qsettings_mutex); - QtSettingsInterface si(m_qsettings.get()); - UpdateSettings(si); - CommonHostInterface::UpdateInputMap(si); + Settings old_settings(std::move(m_settings)); + { + QtSettingsInterface si(m_qsettings.get()); + std::lock_guard guard(m_qsettings_mutex); + CommonHostInterface::LoadSettings(si); + } + + CheckForSettingsChanges(old_settings); // detect when render-to-main flag changes if (m_system) { - const bool render_to_main = m_qsettings->value("Main/RenderToMainWindow", true).toBool(); + const bool render_to_main = getSettingValue("Main/RenderToMainWindow", true).toBool(); if (getHostDisplay() && !m_is_fullscreen && render_to_main != m_is_rendering_to_main) { m_is_rendering_to_main = render_to_main; @@ -224,9 +230,9 @@ void QtHostInterface::resumeSystemFromState(const QString& filename, bool boot_o emit emulationStarting(); if (filename.isEmpty()) - HostInterface::ResumeSystemFromMostRecentState(); + ResumeSystemFromMostRecentState(); else - HostInterface::ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure); + ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure); } void QtHostInterface::resumeSystemFromMostRecentState() @@ -238,7 +244,7 @@ void QtHostInterface::resumeSystemFromMostRecentState() } emit emulationStarting(); - HostInterface::ResumeSystemFromMostRecentState(); + ResumeSystemFromMostRecentState(); } void QtHostInterface::onDisplayWindowKeyEvent(int key, bool pressed) @@ -510,8 +516,8 @@ void QtHostInterface::LoadSettings() } // load in settings - CheckSettings(si); - ApplySettings(si); + CommonHostInterface::CheckSettings(si); + CommonHostInterface::LoadSettings(si); } void QtHostInterface::SetDefaultSettings(SettingsInterface& si) @@ -521,12 +527,6 @@ void QtHostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("Main", "RenderToMainWindow", true); } -void QtHostInterface::ApplySettings(SettingsInterface& si) -{ - std::lock_guard lock(m_qsettings_mutex); - CommonHostInterface::ApplySettings(si); -} - void QtHostInterface::UpdateInputMap() { updateInputMap(); diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 9a68219f2..302e1b633 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -158,7 +158,6 @@ protected: void LoadSettings() override; void SetDefaultSettings(SettingsInterface& si) override; - void ApplySettings(SettingsInterface& si) override; void UpdateInputMap() override; private: diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 63277751e..c409ee10d 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -299,7 +299,11 @@ void SDLHostInterface::RunLater(std::function callback) void SDLHostInterface::SaveAndUpdateSettings() { m_settings_copy.Save(*m_settings_interface.get()); - UpdateSettings(*m_settings_interface.get()); + + Settings old_settings(std::move(m_settings)); + CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CheckForSettingsChanges(old_settings); + m_settings_interface->Save(); } @@ -382,7 +386,7 @@ void SDLHostInterface::LoadSettings() { // Settings need to be loaded prior to creating the window for OpenGL bits. m_settings_interface = std::make_unique(GetSettingsFileName()); - ApplySettings(*m_settings_interface.get()); + CommonHostInterface::LoadSettings(*m_settings_interface.get()); m_settings_copy = m_settings; } diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 97849586b..3a58db074 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -6,21 +6,42 @@ #include "common/log.h" #include "common/string_util.h" #include "controller_interface.h" +#include "core/cdrom.h" #include "core/controller.h" +#include "core/dma.h" #include "core/game_list.h" #include "core/gpu.h" +#include "core/host_display.h" +#include "core/mdec.h" +#include "core/save_state_version.h" +#include "core/spu.h" #include "core/system.h" +#include "core/timers.h" +#include "imgui.h" +#include "ini_settings_interface.h" #include "save_state_selector_ui.h" #include "scmversion/scmversion.h" +#include +#include +#include +#include + #ifdef WITH_SDL2 #include "sdl_audio_stream.h" #include "sdl_controller_interface.h" #endif + +#ifdef WITH_DISCORD_PRESENCE #include "discord_rpc.h" -#include "ini_settings_interface.h" -#include -#include -#include +#endif + +#ifdef WIN32 +#include "common/windows_headers.h" +#include +#include +#include +#endif + Log_SetChannel(CommonHostInterface); CommonHostInterface::CommonHostInterface() = default; @@ -32,10 +53,23 @@ bool CommonHostInterface::Initialize() if (!HostInterface::Initialize()) return false; + SetUserDirectory(); + InitializeUserDirectory(); + // Change to the user directory so that all default/relative paths in the config are after this. if (!FileSystem::SetWorkingDirectory(m_user_directory.c_str())) Log_ErrorPrintf("Failed to set working directory to '%s'", m_user_directory.c_str()); + LoadSettings(); + UpdateLogSettings(m_settings.log_level, m_settings.log_filter.empty() ? nullptr : m_settings.log_filter.c_str(), + m_settings.log_to_console, m_settings.log_to_debug, m_settings.log_to_window, + m_settings.log_to_file); + + m_game_list = std::make_unique(); + m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache")); + m_game_list->SetDatabaseFilename(GetUserDirectoryRelativePath("cache/redump.dat")); + m_game_list->SetCompatibilityFilename(GetProgramDirectoryRelativePath("database/compatibility.xml")); + m_save_state_selector_ui = std::make_unique(this); RegisterGeneralHotkeys(); @@ -77,6 +111,35 @@ void CommonHostInterface::Shutdown() } } +void CommonHostInterface::InitializeUserDirectory() +{ + std::fprintf(stdout, "User directory: \"%s\"\n", m_user_directory.c_str()); + + if (m_user_directory.empty()) + Panic("Cannot continue without user directory set."); + + if (!FileSystem::DirectoryExists(m_user_directory.c_str())) + { + std::fprintf(stderr, "User directory \"%s\" does not exist, creating.\n", m_user_directory.c_str()); + if (!FileSystem::CreateDirectory(m_user_directory.c_str(), true)) + std::fprintf(stderr, "Failed to create user directory \"%s\".\n", m_user_directory.c_str()); + } + + bool result = true; + + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("bios").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("cache").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("dump").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("dump/audio").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("inputprofiles").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("savestates").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("screenshots").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("memcards").c_str(), false); + + if (!result) + ReportError("Failed to create one or more user directories. This may cause issues at runtime."); +} + bool CommonHostInterface::BootSystem(const SystemBootParameters& parameters) { if (!HostInterface::BootSystem(parameters)) @@ -89,20 +152,48 @@ bool CommonHostInterface::BootSystem(const SystemBootParameters& parameters) } // enter fullscreen if requested in the parameters - if ((parameters.override_fullscreen.has_value() && *parameters.override_fullscreen) || - (!parameters.override_fullscreen.has_value() && m_settings.start_fullscreen)) + if (!m_settings.start_paused && ((parameters.override_fullscreen.has_value() && *parameters.override_fullscreen) || + (!parameters.override_fullscreen.has_value() && m_settings.start_fullscreen))) { SetFullscreen(true); } + if (m_settings.audio_dump_on_boot) + StartDumpingAudio(); + + UpdateSpeedLimiterState(); return true; } +void CommonHostInterface::PauseSystem(bool paused) +{ + if (paused == m_paused || !m_system) + return; + + m_paused = paused; + m_audio_stream->PauseOutput(m_paused); + OnSystemPaused(paused); + UpdateSpeedLimiterState(); + + if (!paused) + m_system->ResetPerformanceCounters(); +} + +void CommonHostInterface::DestroySystem() +{ + SetTimerResolutionIncreased(false); + + m_paused = false; + + HostInterface::DestroySystem(); +} + void CommonHostInterface::PowerOffSystem() { - HostInterface::PowerOffSystem(); + if (m_settings.save_state_on_exit) + SaveResumeSaveState(); - // TODO: Do we want to move the resume state saving here? + HostInterface::PowerOffSystem(); if (m_batch_mode) RequestExit(); @@ -359,6 +450,209 @@ std::unique_ptr CommonHostInterface::CreateControllerInterf #endif } +bool CommonHostInterface::LoadState(bool global, s32 slot) +{ + if (!global && (!m_system || m_system->GetRunningCode().empty())) + { + ReportFormattedError("Can't save per-game state without a running game code."); + return false; + } + + std::string save_path = + global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), slot); + return LoadState(save_path.c_str()); +} + +bool CommonHostInterface::SaveState(bool global, s32 slot) +{ + const std::string& code = m_system->GetRunningCode(); + if (!global && code.empty()) + { + ReportFormattedError("Can't save per-game state without a running game code."); + return false; + } + + std::string save_path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(code.c_str(), slot); + if (!SaveState(save_path.c_str())) + return false; + + OnSystemStateSaved(global, slot); + return true; +} + +bool CommonHostInterface::ResumeSystemFromState(const char* filename, bool boot_on_failure) +{ + SystemBootParameters boot_params; + boot_params.filename = filename; + if (!BootSystem(boot_params)) + return false; + + const bool global = m_system->GetRunningCode().empty(); + if (m_system->GetRunningCode().empty()) + { + ReportFormattedError("Cannot resume system with undetectable game code from '%s'.", filename); + if (!boot_on_failure) + { + DestroySystem(); + return true; + } + } + else + { + const std::string path = GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), -1); + if (FileSystem::FileExists(path.c_str())) + { + if (!LoadState(path.c_str()) && !boot_on_failure) + { + DestroySystem(); + return false; + } + } + else if (!boot_on_failure) + { + ReportFormattedError("Resume save state not found for '%s' ('%s').", m_system->GetRunningCode().c_str(), + m_system->GetRunningTitle().c_str()); + DestroySystem(); + return false; + } + } + + return true; +} + +bool CommonHostInterface::ResumeSystemFromMostRecentState() +{ + const std::string path = GetMostRecentResumeSaveStatePath(); + if (path.empty()) + { + ReportError("No resume save state found."); + return false; + } + + return LoadState(path.c_str()); +} + +void CommonHostInterface::UpdateSpeedLimiterState() +{ + m_speed_limiter_enabled = m_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; + + const bool is_non_standard_speed = (std::abs(m_settings.emulation_speed - 1.0f) > 0.05f); + const bool audio_sync_enabled = + !m_system || m_paused || (m_speed_limiter_enabled && m_settings.audio_sync_enabled && !is_non_standard_speed); + const bool video_sync_enabled = + !m_system || m_paused || (m_speed_limiter_enabled && m_settings.video_sync_enabled && !is_non_standard_speed); + Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "", + (audio_sync_enabled && video_sync_enabled) ? " and video" : (video_sync_enabled ? "video" : "")); + + m_audio_stream->SetSync(audio_sync_enabled); + if (audio_sync_enabled) + m_audio_stream->EmptyBuffers(); + + m_display->SetVSync(video_sync_enabled); + + if (m_settings.increase_timer_resolution) + SetTimerResolutionIncreased(m_speed_limiter_enabled); + + m_system->ResetPerformanceCounters(); +} + +void CommonHostInterface::RecreateSystem() +{ + const bool was_paused = m_paused; + HostInterface::RecreateSystem(); + if (was_paused) + PauseSystem(true); +} + +void CommonHostInterface::UpdateLogSettings(LOGLEVEL level, const char* filter, bool log_to_console, bool log_to_debug, + bool log_to_window, bool log_to_file) +{ + Log::SetFilterLevel(level); + Log::SetConsoleOutputParams(m_settings.log_to_console, filter, level); + Log::SetDebugOutputParams(m_settings.log_to_debug, filter, level); + + if (log_to_file) + { + Log::SetFileOutputParams(m_settings.log_to_file, GetUserDirectoryRelativePath("duckstation.log").c_str(), true, + filter, level); + } + else + { + Log::SetFileOutputParams(false, nullptr); + } +} + +void CommonHostInterface::SetUserDirectory() +{ + if (!m_user_directory.empty()) + return; + + std::fprintf(stdout, "Program directory \"%s\"\n", m_program_directory.c_str()); + + if (FileSystem::FileExists(StringUtil::StdStringFromFormat("%s%c%s", m_program_directory.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER, "portable.txt") + .c_str()) || + FileSystem::FileExists(StringUtil::StdStringFromFormat("%s%c%s", m_program_directory.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER, "settings.ini") + .c_str())) + { + std::fprintf(stdout, "portable.txt or old settings.ini found, using program directory as user directory.\n"); + m_user_directory = m_program_directory; + } + else + { +#ifdef WIN32 + // On Windows, use My Documents\DuckStation. + PWSTR documents_directory; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory))) + { + const size_t documents_directory_len = std::wcslen(documents_directory); + int documents_directory_u8len = WideCharToMultiByte( + CP_UTF8, 0, documents_directory, static_cast(documents_directory_len), nullptr, 0, nullptr, nullptr); + if (documents_directory_u8len > 0) + { + std::string documents_directory_str; + documents_directory_str.resize(documents_directory_u8len); + documents_directory_u8len = WideCharToMultiByte( + CP_UTF8, 0, documents_directory, static_cast(documents_directory_len), documents_directory_str.data(), + static_cast(documents_directory_str.size()), 0, nullptr); + if (documents_directory_u8len > 0) + { + documents_directory_str.resize(documents_directory_u8len); + m_user_directory = StringUtil::StdStringFromFormat("%s%c%s", documents_directory_str.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER, "DuckStation"); + } + } + CoTaskMemFree(documents_directory); + } +#elif __linux__ + // On Linux, use .local/share/duckstation as a user directory by default. + const char* xdg_data_home = getenv("XDG_DATA_HOME"); + if (xdg_data_home && xdg_data_home[0] == '/') + { + m_user_directory = StringUtil::StdStringFromFormat("%s/duckstation", xdg_data_home); + } + else + { + const char* home_path = getenv("HOME"); + if (home_path) + m_user_directory = StringUtil::StdStringFromFormat("%s/.local/share/duckstation", home_path); + } +#elif __APPLE__ + // On macOS, default to ~/Library/Application Support/DuckStation. + const char* home_path = getenv("HOME"); + if (home_path) + m_user_directory = StringUtil::StdStringFromFormat("%s/Library/Application Support/DuckStation", home_path); +#endif + + if (m_user_directory.empty()) + { + std::fprintf(stderr, "User directory path could not be determined, falling back to program directory."); + m_user_directory = m_program_directory; + } + } +} + void CommonHostInterface::OnSystemCreated() { HostInterface::OnSystemCreated(); @@ -366,7 +660,7 @@ void CommonHostInterface::OnSystemCreated() void CommonHostInterface::OnSystemPaused(bool paused) { - HostInterface::OnSystemPaused(paused); + ReportFormattedMessage("System %s.", paused ? "paused" : "resumed"); if (paused) { @@ -375,6 +669,8 @@ void CommonHostInterface::OnSystemPaused(bool paused) StopControllerRumble(); } + + m_audio_stream->PauseOutput(paused); } void CommonHostInterface::OnSystemDestroyed() @@ -402,55 +698,159 @@ void CommonHostInterface::OnControllerTypeChanged(u32 slot) void CommonHostInterface::DrawImGuiWindows() { - HostInterface::DrawImGuiWindows(); + if (m_system) + { + DrawDebugWindows(); + DrawFPSWindow(); + } + + DrawOSDMessages(); if (m_save_state_selector_ui->IsOpen()) m_save_state_selector_ui->Draw(); } -void CommonHostInterface::SetDefaultSettings(SettingsInterface& si) +void CommonHostInterface::DrawFPSWindow() { - HostInterface::SetDefaultSettings(si); + if (!(m_settings.display_show_fps | m_settings.display_show_vps | m_settings.display_show_speed)) + return; - si.SetStringValue("Controller1", "ButtonUp", "Keyboard/W"); - si.SetStringValue("Controller1", "ButtonDown", "Keyboard/S"); - si.SetStringValue("Controller1", "ButtonLeft", "Keyboard/A"); - si.SetStringValue("Controller1", "ButtonRight", "Keyboard/D"); - si.SetStringValue("Controller1", "ButtonSelect", "Keyboard/Backspace"); - si.SetStringValue("Controller1", "ButtonStart", "Keyboard/Return"); - si.SetStringValue("Controller1", "ButtonTriangle", "Keyboard/Keypad+8"); - si.SetStringValue("Controller1", "ButtonCross", "Keyboard/Keypad+2"); - si.SetStringValue("Controller1", "ButtonSquare", "Keyboard/Keypad+4"); - si.SetStringValue("Controller1", "ButtonCircle", "Keyboard/Keypad+6"); - si.SetStringValue("Controller1", "ButtonL1", "Keyboard/Q"); - si.SetStringValue("Controller1", "ButtonL2", "Keyboard/1"); - si.SetStringValue("Controller1", "ButtonR1", "Keyboard/E"); - si.SetStringValue("Controller1", "ButtonR2", "Keyboard/3"); - si.SetStringValue("Hotkeys", "FastForward", "Keyboard/Tab"); - si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Pause"); - si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/Alt+Return"); - si.SetStringValue("Hotkeys", "PowerOff", "Keyboard/Escape"); - si.SetStringValue("Hotkeys", "LoadSelectedSaveState", "Keyboard/F1"); - si.SetStringValue("Hotkeys", "SaveSelectedSaveState", "Keyboard/F2"); - si.SetStringValue("Hotkeys", "SelectPreviousSaveStateSlot", "Keyboard/F3"); - si.SetStringValue("Hotkeys", "SelectNextSaveStateSlot", "Keyboard/F4"); - si.SetStringValue("Hotkeys", "Screenshot", "Keyboard/F10"); - si.SetStringValue("Hotkeys", "IncreaseResolutionScale", "Keyboard/PageUp"); - si.SetStringValue("Hotkeys", "DecreaseResolutionScale", "Keyboard/PageDown"); - si.SetStringValue("Hotkeys", "ToggleSoftwareRendering", "Keyboard/End"); + const ImVec2 window_size = + ImVec2(175.0f * ImGui::GetIO().DisplayFramebufferScale.x, 16.0f * ImGui::GetIO().DisplayFramebufferScale.y); + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - window_size.x, 0.0f), ImGuiCond_Always); + ImGui::SetNextWindowSize(window_size); -#ifdef WITH_DISCORD_PRESENCE - si.SetBoolValue("Main", "EnableDiscordPresence", false); -#endif + if (!ImGui::Begin("FPSWindow", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMouseInputs | + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNav)) + { + ImGui::End(); + return; + } + + bool first = true; + if (m_settings.display_show_fps) + { + ImGui::Text("%.2f", m_system->GetFPS()); + first = false; + } + if (m_settings.display_show_vps) + { + if (first) + { + first = false; + } + else + { + ImGui::SameLine(); + ImGui::Text("/"); + ImGui::SameLine(); + } + + ImGui::Text("%.2f", m_system->GetVPS()); + } + if (m_settings.display_show_speed) + { + if (first) + { + first = false; + } + else + { + ImGui::SameLine(); + ImGui::Text("/"); + ImGui::SameLine(); + } + + const float speed = m_system->GetEmulationSpeed(); + const u32 rounded_speed = static_cast(std::round(speed)); + if (speed < 90.0f) + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%u%%", rounded_speed); + else if (speed < 110.0f) + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%u%%", rounded_speed); + else + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%u%%", rounded_speed); + } + + ImGui::End(); } -void CommonHostInterface::ApplySettings(SettingsInterface& si) +void CommonHostInterface::AddOSDMessage(std::string message, float duration /*= 2.0f*/) { - HostInterface::ApplySettings(si); + OSDMessage msg; + msg.text = std::move(message); + msg.duration = duration; -#ifdef WITH_DISCORD_PRESENCE - SetDiscordPresenceEnabled(si.GetBoolValue("Main", "EnableDiscordPresence", false)); -#endif + std::unique_lock lock(m_osd_messages_lock); + m_osd_messages.push_back(std::move(msg)); +} + +void CommonHostInterface::DrawOSDMessages() +{ + constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing; + + std::unique_lock lock(m_osd_messages_lock); + if (m_osd_messages.empty()) + return; + + const float scale = ImGui::GetIO().DisplayFramebufferScale.x; + + auto iter = m_osd_messages.begin(); + float position_x = 10.0f * scale; + float position_y = (10.0f + (static_cast(m_display->GetDisplayTopMargin()))) * scale; + u32 index = 0; + while (iter != m_osd_messages.end()) + { + const OSDMessage& msg = *iter; + const double time = msg.time.GetTimeSeconds(); + const float time_remaining = static_cast(msg.duration - time); + if (time_remaining <= 0.0f) + { + iter = m_osd_messages.erase(iter); + continue; + } + + if (!m_settings.display_show_osd_messages) + continue; + + const float opacity = std::min(time_remaining, 1.0f); + ImGui::SetNextWindowPos(ImVec2(position_x, position_y)); + ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity); + + char buf[64]; + std::snprintf(buf, sizeof(buf), "osd_%u", index++); + + if (ImGui::Begin(buf, nullptr, window_flags)) + { + ImGui::TextUnformatted(msg.text.c_str()); + position_y += ImGui::GetWindowSize().y + (4.0f * scale); + } + + ImGui::End(); + ImGui::PopStyleVar(); + ++iter; + } +} + +void CommonHostInterface::DrawDebugWindows() +{ + const Settings::DebugSettings& debug_settings = m_system->GetSettings().debugging; + + if (debug_settings.show_gpu_state) + m_system->GetGPU()->DrawDebugStateWindow(); + if (debug_settings.show_cdrom_state) + m_system->GetCDROM()->DrawDebugWindow(); + if (debug_settings.show_timers_state) + m_system->GetTimers()->DrawDebugStateWindow(); + if (debug_settings.show_spu_state) + m_system->GetSPU()->DrawDebugStateWindow(); + if (debug_settings.show_mdec_state) + m_system->GetMDEC()->DrawDebugStateWindow(); } std::optional @@ -802,7 +1202,7 @@ void CommonHostInterface::RegisterGeneralHotkeys() RegisterHotkey(StaticString("General"), StaticString("FastForward"), StaticString("Fast Forward"), [this](bool pressed) { m_speed_limiter_temp_disabled = pressed; - HostInterface::UpdateSpeedLimiterState(); + UpdateSpeedLimiterState(); }); RegisterHotkey(StaticString("General"), StaticString("ToggleFastForward"), StaticString("Toggle Fast Forward"), @@ -810,7 +1210,7 @@ void CommonHostInterface::RegisterGeneralHotkeys() if (!pressed) { m_speed_limiter_temp_disabled = !m_speed_limiter_temp_disabled; - HostInterface::UpdateSpeedLimiterState(); + UpdateSpeedLimiterState(); AddFormattedOSDMessage(1.0f, "Speed limiter %s.", m_speed_limiter_enabled ? "enabled" : "disabled"); } @@ -1106,7 +1506,7 @@ bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInt profile.SetStringValue(section_name, "Rumble", rumble_value.c_str()); } - if(!profile.Save()) + if (!profile.Save()) { Log_ErrorPrintf("Failed to save input profile to '%s'", profile_path); return false; @@ -1116,6 +1516,419 @@ bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInt return true; } +std::string CommonHostInterface::GetSettingsFileName() const +{ + return GetUserDirectoryRelativePath("settings.ini"); +} + +std::string CommonHostInterface::GetGameSaveStateFileName(const char* game_code, s32 slot) const +{ + if (slot < 0) + return GetUserDirectoryRelativePath("savestates/%s_resume.sav", game_code); + else + return GetUserDirectoryRelativePath("savestates/%s_%d.sav", game_code, slot); +} + +std::string CommonHostInterface::GetGlobalSaveStateFileName(s32 slot) const +{ + if (slot < 0) + return GetUserDirectoryRelativePath("savestates/resume.sav"); + else + return GetUserDirectoryRelativePath("savestates/savestate_%d.sav", slot); +} + +std::vector CommonHostInterface::GetAvailableSaveStates(const char* game_code) const +{ + std::vector si; + std::string path; + + auto add_path = [&si](std::string path, s32 slot, bool global) { + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(path.c_str(), &sd)) + return; + + si.push_back(SaveStateInfo{std::move(path), sd.ModificationTime.AsUnixTimestamp(), static_cast(slot), global}); + }; + + if (game_code && std::strlen(game_code) > 0) + { + add_path(GetGameSaveStateFileName(game_code, -1), -1, false); + for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++) + add_path(GetGameSaveStateFileName(game_code, i), i, false); + } + + for (s32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++) + add_path(GetGlobalSaveStateFileName(i), i, true); + + return si; +} + +std::optional CommonHostInterface::GetSaveStateInfo(const char* game_code, s32 slot) +{ + const bool global = (!game_code || game_code[0] == 0); + std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(game_code, slot); + + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(path.c_str(), &sd)) + return std::nullopt; + + return SaveStateInfo{std::move(path), sd.ModificationTime.AsUnixTimestamp(), slot, global}; +} + +std::optional +CommonHostInterface::GetExtendedSaveStateInfo(const char* game_code, s32 slot) +{ + const bool global = (!game_code || game_code[0] == 0); + std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(game_code, slot); + + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(path.c_str(), &sd)) + return std::nullopt; + + std::unique_ptr stream = + FileSystem::OpenFile(path.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_SEEKABLE); + if (!stream) + return std::nullopt; + + SAVE_STATE_HEADER header; + if (!stream->Read(&header, sizeof(header)) || header.magic != SAVE_STATE_MAGIC) + return std::nullopt; + + ExtendedSaveStateInfo ssi; + ssi.path = std::move(path); + ssi.timestamp = sd.ModificationTime.AsUnixTimestamp(); + ssi.slot = slot; + ssi.global = global; + + if (header.version != SAVE_STATE_VERSION) + { + ssi.title = StringUtil::StdStringFromFormat("Invalid version %u (expected %u)", header.version, header.magic, + SAVE_STATE_VERSION); + return ssi; + } + + header.title[sizeof(header.title) - 1] = 0; + ssi.title = header.title; + header.game_code[sizeof(header.game_code) - 1] = 0; + ssi.game_code = header.game_code; + + if (header.screenshot_width > 0 && header.screenshot_height > 0 && header.screenshot_size > 0 && + (static_cast(header.offset_to_screenshot) + static_cast(header.screenshot_size)) <= stream->GetSize()) + { + ssi.screenshot_data.resize((header.screenshot_size + 3u) / 4u); + if (stream->Read2(ssi.screenshot_data.data(), header.screenshot_size)) + { + ssi.screenshot_width = header.screenshot_width; + ssi.screenshot_height = header.screenshot_height; + } + else + { + decltype(ssi.screenshot_data)().swap(ssi.screenshot_data); + } + } + + return ssi; +} + +void CommonHostInterface::DeleteSaveStates(const char* game_code, bool resume) +{ + const std::vector states(GetAvailableSaveStates(game_code)); + for (const SaveStateInfo& si : states) + { + if (si.global || (!resume && si.slot < 0)) + continue; + + Log_InfoPrintf("Removing save state at '%s'", si.path.c_str()); + if (!FileSystem::DeleteFile(si.path.c_str())) + Log_ErrorPrintf("Failed to delete save state file '%s'", si.path.c_str()); + } +} + +std::string CommonHostInterface::GetMostRecentResumeSaveStatePath() const +{ + std::vector files; + if (!FileSystem::FindFiles(GetUserDirectoryRelativePath("savestates").c_str(), "*resume.sav", FILESYSTEM_FIND_FILES, + &files) || + files.empty()) + { + return {}; + } + + FILESYSTEM_FIND_DATA* most_recent = &files[0]; + for (FILESYSTEM_FIND_DATA& file : files) + { + if (file.ModificationTime > most_recent->ModificationTime) + most_recent = &file; + } + + return std::move(most_recent->FileName); +} + +void CommonHostInterface::CheckSettings(SettingsInterface& si) +{ + const int settings_version = si.GetIntValue("Main", "SettingsVersion", -1); + if (settings_version == SETTINGS_VERSION) + return; + + ReportFormattedError("Settings version %d does not match expected version %d, resetting", settings_version, + SETTINGS_VERSION); + si.Clear(); + si.SetIntValue("Main", "SettingsVersion", SETTINGS_VERSION); + SetDefaultSettings(si); +} + +void CommonHostInterface::SetDefaultSettings(SettingsInterface& si) +{ + HostInterface::SetDefaultSettings(si); + + si.SetStringValue("Controller1", "ButtonUp", "Keyboard/W"); + si.SetStringValue("Controller1", "ButtonDown", "Keyboard/S"); + si.SetStringValue("Controller1", "ButtonLeft", "Keyboard/A"); + si.SetStringValue("Controller1", "ButtonRight", "Keyboard/D"); + si.SetStringValue("Controller1", "ButtonSelect", "Keyboard/Backspace"); + si.SetStringValue("Controller1", "ButtonStart", "Keyboard/Return"); + si.SetStringValue("Controller1", "ButtonTriangle", "Keyboard/Keypad+8"); + si.SetStringValue("Controller1", "ButtonCross", "Keyboard/Keypad+2"); + si.SetStringValue("Controller1", "ButtonSquare", "Keyboard/Keypad+4"); + si.SetStringValue("Controller1", "ButtonCircle", "Keyboard/Keypad+6"); + si.SetStringValue("Controller1", "ButtonL1", "Keyboard/Q"); + si.SetStringValue("Controller1", "ButtonL2", "Keyboard/1"); + si.SetStringValue("Controller1", "ButtonR1", "Keyboard/E"); + si.SetStringValue("Controller1", "ButtonR2", "Keyboard/3"); + si.SetStringValue("Hotkeys", "FastForward", "Keyboard/Tab"); + si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Pause"); + si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/Alt+Return"); + si.SetStringValue("Hotkeys", "PowerOff", "Keyboard/Escape"); + si.SetStringValue("Hotkeys", "LoadSelectedSaveState", "Keyboard/F1"); + si.SetStringValue("Hotkeys", "SaveSelectedSaveState", "Keyboard/F2"); + si.SetStringValue("Hotkeys", "SelectPreviousSaveStateSlot", "Keyboard/F3"); + si.SetStringValue("Hotkeys", "SelectNextSaveStateSlot", "Keyboard/F4"); + si.SetStringValue("Hotkeys", "Screenshot", "Keyboard/F10"); + si.SetStringValue("Hotkeys", "IncreaseResolutionScale", "Keyboard/PageUp"); + si.SetStringValue("Hotkeys", "DecreaseResolutionScale", "Keyboard/PageDown"); + si.SetStringValue("Hotkeys", "ToggleSoftwareRendering", "Keyboard/End"); + +#ifdef WITH_DISCORD_PRESENCE + si.SetBoolValue("Main", "EnableDiscordPresence", false); +#endif +} + +void CommonHostInterface::LoadSettings(SettingsInterface& si) +{ + HostInterface::LoadSettings(si); + +#ifdef WITH_DISCORD_PRESENCE + SetDiscordPresenceEnabled(si.GetBoolValue("Main", "EnableDiscordPresence", false)); +#endif +} + +void CommonHostInterface::SaveSettings(SettingsInterface& si) +{ + HostInterface::SaveSettings(si); +} + +void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings) +{ + HostInterface::CheckForSettingsChanges(old_settings); + + if (m_system) + { + if (m_settings.audio_backend != old_settings.audio_backend || + m_settings.audio_buffer_size != old_settings.audio_buffer_size) + { + m_audio_stream->PauseOutput(m_paused); + UpdateSpeedLimiterState(); + } + + if (m_settings.video_sync_enabled != old_settings.video_sync_enabled || + m_settings.audio_sync_enabled != old_settings.audio_sync_enabled || + m_settings.speed_limiter_enabled != old_settings.speed_limiter_enabled || + m_settings.increase_timer_resolution != old_settings.increase_timer_resolution || + m_settings.emulation_speed != old_settings.emulation_speed) + { + UpdateSpeedLimiterState(); + } + } + + if (m_settings.log_level != old_settings.log_level || m_settings.log_filter != old_settings.log_filter || + m_settings.log_to_console != old_settings.log_to_console || + m_settings.log_to_window != old_settings.log_to_window || m_settings.log_to_file != old_settings.log_to_file) + { + UpdateLogSettings(m_settings.log_level, m_settings.log_filter.empty() ? nullptr : m_settings.log_filter.c_str(), + m_settings.log_to_console, m_settings.log_to_debug, m_settings.log_to_window, + m_settings.log_to_file); + } + + UpdateInputMap(); +} + +void CommonHostInterface::SetTimerResolutionIncreased(bool enabled) +{ + if (m_timer_resolution_increased == enabled) + return; + + m_timer_resolution_increased = enabled; + +#ifdef WIN32 + if (enabled) + timeBeginPeriod(1); + else + timeEndPeriod(1); +#endif +} + +void CommonHostInterface::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, + int progress_max /*= -1*/, int progress_value /*= -1*/) +{ + const auto& io = ImGui::GetIO(); + const float scale = io.DisplayFramebufferScale.x; + const float width = (400.0f * scale); + const bool has_progress = (progress_min < progress_max); + + // eat the last imgui frame, it might've been partially rendered by the caller. + ImGui::EndFrame(); + ImGui::NewFrame(); + + ImGui::SetNextWindowSize(ImVec2(width, (has_progress ? 50.0f : 30.0f) * scale), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), ImGuiCond_Always, + ImVec2(0.5f, 0.5f)); + if (ImGui::Begin("LoadingScreen", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (has_progress) + { + ImGui::Text("%s: %d/%d", message, progress_value, progress_max); + ImGui::ProgressBar(static_cast(progress_value) / static_cast(progress_max - progress_min), + ImVec2(-1.0f, 0.0f), ""); + Log_InfoPrintf("%s: %d/%d", message, progress_value, progress_max); + } + else + { + const ImVec2 text_size(ImGui::CalcTextSize(message)); + ImGui::SetCursorPosX((width - text_size.x) / 2.0f); + ImGui::TextUnformatted(message); + Log_InfoPrintf("%s", message); + } + } + ImGui::End(); + + m_display->Render(); +} + +void CommonHostInterface::GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) +{ + const GameListEntry* list_entry = m_game_list->GetEntryForPath(path); + if (list_entry) + { + *code = list_entry->code; + *title = list_entry->title; + } + else + { + if (image) + *code = GameList::GetGameCodeForImage(image); + + const GameListDatabaseEntry* db_entry = (!code->empty()) ? m_game_list->GetDatabaseEntryForCode(*code) : nullptr; + if (db_entry) + *title = db_entry->title; + else + *title = GameList::GetTitleForPath(path); + } +} + +bool CommonHostInterface::SaveResumeSaveState() +{ + if (!m_system) + return false; + + const bool global = m_system->GetRunningCode().empty(); + return SaveState(global, -1); +} + +bool CommonHostInterface::IsDumpingAudio() const +{ + return m_system ? m_system->GetSPU()->IsDumpingAudio() : false; +} + +bool CommonHostInterface::StartDumpingAudio(const char* filename) +{ + if (!m_system) + return false; + + std::string auto_filename; + if (!filename) + { + const auto& code = m_system->GetRunningCode(); + if (code.empty()) + { + auto_filename = GetUserDirectoryRelativePath("dump/audio/%s.wav", GetTimestampStringForFileName().GetCharArray()); + } + else + { + auto_filename = GetUserDirectoryRelativePath("dump/audio/%s_%s.wav", code.c_str(), + GetTimestampStringForFileName().GetCharArray()); + } + + filename = auto_filename.c_str(); + } + + if (m_system->GetSPU()->StartDumpingAudio(filename)) + { + AddFormattedOSDMessage(5.0f, "Started dumping audio to '%s'.", filename); + return true; + } + else + { + AddFormattedOSDMessage(10.0f, "Failed to start dumping audio to '%s'.", filename); + return false; + } +} + +void CommonHostInterface::StopDumpingAudio() +{ + if (!m_system || !m_system->GetSPU()->StopDumpingAudio()) + return; + + AddOSDMessage("Stopped dumping audio.", 5.0f); +} + +bool CommonHostInterface::SaveScreenshot(const char* filename /* = nullptr */, bool full_resolution /* = true */, + bool apply_aspect_ratio /* = true */) +{ + if (!m_system) + return false; + + std::string auto_filename; + if (!filename) + { + const auto& code = m_system->GetRunningCode(); + const char* extension = "png"; + if (code.empty()) + { + auto_filename = + GetUserDirectoryRelativePath("screenshots/%s.%s", GetTimestampStringForFileName().GetCharArray(), extension); + } + else + { + auto_filename = GetUserDirectoryRelativePath("screenshots/%s_%s.%s", code.c_str(), + GetTimestampStringForFileName().GetCharArray(), extension); + } + + filename = auto_filename.c_str(); + } + + if (!m_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio)) + { + AddFormattedOSDMessage(10.0f, "Failed to save screenshot to '%s'", filename); + return false; + } + + AddFormattedOSDMessage(5.0f, "Screenshot saved to '%s'.", filename); + return true; +} + #ifdef WITH_DISCORD_PRESENCE void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled) @@ -1186,4 +1999,4 @@ void CommonHostInterface::PollDiscordPresence() Discord_RunCallbacks(); } -#endif \ No newline at end of file +#endif diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index e482a5996..3d4ad0d5c 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -22,6 +22,12 @@ class CommonHostInterface : public HostInterface public: friend ControllerInterface; + enum : s32 + { + PER_GAME_SAVE_STATE_SLOTS = 10, + GLOBAL_SAVE_STATE_SLOTS = 10 + }; + using HostKeyCode = s32; using HostMouseButton = s32; @@ -39,6 +45,32 @@ public: using HotkeyInfoList = std::vector; + struct SaveStateInfo + { + std::string path; + u64 timestamp; + s32 slot; + bool global; + }; + + struct ExtendedSaveStateInfo + { + std::string path; + u64 timestamp; + s32 slot; + bool global; + + std::string title; + std::string game_code; + + u32 screenshot_width; + u32 screenshot_height; + std::vector screenshot_data; + }; + + using HostInterface::LoadState; + using HostInterface::SaveState; + /// Returns the name of the frontend. virtual const char* GetFrontendName() const = 0; @@ -47,6 +79,10 @@ public: virtual bool BootSystem(const SystemBootParameters& parameters) override; virtual void PowerOffSystem() override; + virtual void DestroySystem() override; + + /// Returns the game list. + ALWAYS_INLINE const GameList* GetGameList() const { return m_game_list.get(); } /// Returns a list of all available hotkeys. ALWAYS_INLINE const HotkeyInfoList& GetHotkeyInfoList() const { return m_hotkeys; } @@ -57,10 +93,75 @@ public: /// Returns true if running in batch mode, i.e. exit after emulation. ALWAYS_INLINE bool InBatchMode() const { return m_batch_mode; } + void PauseSystem(bool paused); + /// Parses command line parameters for all frontends. bool ParseCommandLineParameters(int argc, char* argv[], std::unique_ptr* out_boot_params); + /// Loads the current emulation state from file. Specifying a slot of -1 loads the "resume" game state. + bool LoadState(bool global, s32 slot); + + /// Saves the current emulation state to a file. Specifying a slot of -1 saves the "resume" save state. + bool SaveState(bool global, s32 slot); + + /// Loads the resume save state for the given game. Optionally boots the game anyway if loading fails. + bool ResumeSystemFromState(const char* filename, bool boot_on_failure); + + /// Loads the most recent resume save state. This may be global or per-game. + bool ResumeSystemFromMostRecentState(); + + /// Saves the resume save state, call when shutting down. Not called automatically on DestroySystem() since that can + /// be called as a result of an error. + bool SaveResumeSaveState(); + + /// Returns a list of save states for the specified game code. + std::vector GetAvailableSaveStates(const char* game_code) const; + + /// Returns save state info if present. If game_code is null or empty, assumes global state. + std::optional GetSaveStateInfo(const char* game_code, s32 slot); + + /// Returns save state info if present. If game_code is null or empty, assumes global state. + std::optional GetExtendedSaveStateInfo(const char* game_code, s32 slot); + + /// Deletes save states for the specified game code. If resume is set, the resume state is deleted too. + void DeleteSaveStates(const char* game_code, bool resume); + + /// Adds OSD messages, duration is in seconds. + void AddOSDMessage(std::string message, float duration = 2.0f) override; + + /// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks + /// such as compiling shaders when starting up. + void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, + int progress_value = -1) override; + + /// Retrieves information about specified game from game list. + void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) override; + + /// Returns true if currently dumping audio. + bool IsDumpingAudio() const; + + /// Starts dumping audio to a file. If no file name is provided, one will be generated automatically. + bool StartDumpingAudio(const char* filename = nullptr); + + /// Stops dumping audio to file if it has been started. + void StopDumpingAudio(); + + /// Saves a screenshot to the specified file. IF no file name is provided, one will be generated automatically. + bool SaveScreenshot(const char* filename = nullptr, bool full_resolution = true, bool apply_aspect_ratio = true); + protected: + enum : u32 + { + SETTINGS_VERSION = 2 + }; + + struct OSDMessage + { + std::string text; + Common::Timer time; + float duration; + }; + CommonHostInterface(); ~CommonHostInterface(); @@ -77,14 +178,10 @@ protected: virtual std::unique_ptr CreateControllerInterface(); virtual void OnSystemCreated() override; - virtual void OnSystemPaused(bool paused) override; + virtual void OnSystemPaused(bool paused); virtual void OnSystemDestroyed() override; virtual void OnRunningGameChanged() override; virtual void OnControllerTypeChanged(u32 slot) override; - virtual void DrawImGuiWindows() override; - - virtual void SetDefaultSettings(SettingsInterface& si) override; - virtual void ApplySettings(SettingsInterface& si) override; virtual std::optional GetHostKeyCode(const std::string_view key_code) const; @@ -118,9 +215,70 @@ protected: void UpdateControllerRumble(); void StopControllerRumble(); + /// Returns the path to a save state file. Specifying an index of -1 is the "resume" save state. + std::string GetGameSaveStateFileName(const char* game_code, s32 slot) const; + + /// Returns the path to a save state file. Specifying an index of -1 is the "resume" save state. + std::string GetGlobalSaveStateFileName(s32 slot) const; + + /// Sets the base path for the user directory. Can be overridden by platform/frontend/command line. + virtual void SetUserDirectory(); + + /// Performs the initial load of settings. Should call CheckSettings() and LoadSettings(SettingsInterface&). + virtual void LoadSettings() = 0; + + /// Updates logging settings. + virtual void UpdateLogSettings(LOGLEVEL level, const char* filter, bool log_to_console, bool log_to_debug, + bool log_to_window, bool log_to_file); + + /// Returns the path of the settings file. + std::string GetSettingsFileName() const; + + /// Returns the most recent resume save state. + std::string GetMostRecentResumeSaveStatePath() const; + + /// Ensures the settings is valid and the correct version. If not, resets to defaults. + void CheckSettings(SettingsInterface& si); + + /// Restores all settings to defaults. + virtual void SetDefaultSettings(SettingsInterface& si); + + /// Loads settings to m_settings and any frontend-specific parameters. + virtual void LoadSettings(SettingsInterface& si); + + /// Saves current settings variables to ini. + virtual void SaveSettings(SettingsInterface& si); + + /// Checks for settings changes, std::move() the old settings away for comparing beforehand. + virtual void CheckForSettingsChanges(const Settings& old_settings); + + /// Increases timer resolution when supported by the host OS. + void SetTimerResolutionIncreased(bool enabled); + + void UpdateSpeedLimiterState(); + + void RecreateSystem() override; + + virtual void DrawImGuiWindows(); + + void DrawFPSWindow(); + void DrawOSDMessages(); + void DrawDebugWindows(); + + std::unique_ptr m_game_list; + std::unique_ptr m_controller_interface; + std::deque m_osd_messages; + std::mutex m_osd_messages_lock; + + bool m_paused = false; + bool m_speed_limiter_temp_disabled = false; + bool m_speed_limiter_enabled = false; + bool m_timer_resolution_increased = false; + private: + void InitializeUserDirectory(); void RegisterGeneralHotkeys(); void RegisterGraphicsHotkeys(); void RegisterSaveStateHotkeys(); diff --git a/src/frontend-common/save_state_selector_ui.cpp b/src/frontend-common/save_state_selector_ui.cpp index 097eb77a1..5da1f6e4b 100644 --- a/src/frontend-common/save_state_selector_ui.cpp +++ b/src/frontend-common/save_state_selector_ui.cpp @@ -45,9 +45,9 @@ void SaveStateSelectorUI::RefreshList() const System* system = m_host_interface->GetSystem(); if (system && !system->GetRunningCode().empty()) { - for (s32 i = 1; i <= HostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) + for (s32 i = 1; i <= CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) { - std::optional ssi = + std::optional ssi = m_host_interface->GetExtendedSaveStateInfo(system->GetRunningCode().c_str(), i); ListEntry li; @@ -60,9 +60,10 @@ void SaveStateSelectorUI::RefreshList() } } - for (s32 i = 1; i <= HostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) + for (s32 i = 1; i <= CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) { - std::optional ssi = m_host_interface->GetExtendedSaveStateInfo(nullptr, i); + std::optional ssi = + m_host_interface->GetExtendedSaveStateInfo(nullptr, i); ListEntry li; if (ssi) @@ -118,7 +119,7 @@ void SaveStateSelectorUI::SelectPreviousSlot() (m_current_selection == 0) ? (static_cast(m_slots.size()) - 1u) : (m_current_selection - 1); } -void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, HostInterface::ExtendedSaveStateInfo* ssi) +void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, CommonHostInterface::ExtendedSaveStateInfo* ssi) { li->title = std::move(ssi->title); li->game_code = std::move(ssi->game_code); diff --git a/src/frontend-common/save_state_selector_ui.h b/src/frontend-common/save_state_selector_ui.h index 78c94f301..103907d7f 100644 --- a/src/frontend-common/save_state_selector_ui.h +++ b/src/frontend-common/save_state_selector_ui.h @@ -48,7 +48,7 @@ private: }; void InitializePlaceholderListEntry(ListEntry* li, s32 slot, bool global); - void InitializeListEntry(ListEntry* li, HostInterface::ExtendedSaveStateInfo* ssi); + void InitializeListEntry(ListEntry* li, CommonHostInterface::ExtendedSaveStateInfo* ssi); CommonHostInterface* m_host_interface; std::vector m_slots;