diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 873e34b4b..41ccac317 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -324,6 +324,44 @@ bool CheatList::SaveToPCSXRFile(const char* filename) return (std::ferror(fp.get()) == 0); } +u32 CheatList::GetEnabledCodeCount() const +{ + u32 count = 0; + for (const CheatCode& cc : m_codes) + { + if (cc.enabled) + count++; + } + + return count; +} + +void CheatList::SetCodeEnabled(u32 index, bool state) +{ + if (index >= m_codes.size()) + return; + + m_codes[index].enabled = state; +} + +void CheatList::EnableCode(u32 index) +{ + SetCodeEnabled(index, true); +} + +void CheatList::DisableCode(u32 index) +{ + SetCodeEnabled(index, false); +} + +void CheatList::ApplyCode(u32 index) +{ + if (index >= m_codes.size()) + return; + + m_codes[index].Apply(); +} + void CheatCode::Apply() const { const u32 count = static_cast(instructions.size()); diff --git a/src/core/cheats.h b/src/core/cheats.h index 058f3a7ab..02e4d5ae9 100644 --- a/src/core/cheats.h +++ b/src/core/cheats.h @@ -68,10 +68,16 @@ public: ALWAYS_INLINE const CheatCode& GetCode(u32 i) const { return m_codes[i]; } ALWAYS_INLINE CheatCode& GetCode(u32 i) { return m_codes[i]; } ALWAYS_INLINE u32 GetCodeCount() const { return static_cast(m_codes.size()); } + ALWAYS_INLINE bool IsCodeEnabled(u32 index) const { return m_codes[index].enabled; } void AddCode(CheatCode cc); void RemoveCode(u32 i); + u32 GetEnabledCodeCount() const; + void EnableCode(u32 index); + void DisableCode(u32 index); + void SetCodeEnabled(u32 index, bool state); + static std::optional DetectFileFormat(const char* filename); bool LoadFromFile(const char* filename, Format format); @@ -82,6 +88,8 @@ public: void Apply(); + void ApplyCode(u32 index); + private: std::vector m_codes; }; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 0d9c1f169..045a7a221 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -86,6 +86,7 @@ void Settings::Load(SettingsInterface& si) confim_power_off = si.GetBoolValue("Main", "ConfirmPowerOff", true); load_devices_from_save_states = si.GetBoolValue("Main", "LoadDevicesFromSaveStates", false); apply_game_settings = si.GetBoolValue("Main", "ApplyGameSettings", true); + auto_load_cheats = si.GetBoolValue("Main", "AutoLoadCheats", false); cpu_execution_mode = ParseCPUExecutionMode( @@ -204,6 +205,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Main", "ConfirmPowerOff", confim_power_off); si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states); si.SetBoolValue("Main", "ApplyGameSettings", apply_game_settings); + si.SetBoolValue("Main", "AutoLoadCheats", auto_load_cheats); si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); diff --git a/src/core/settings.h b/src/core/settings.h index 5b98bde8e..26cc88491 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -80,6 +80,7 @@ struct Settings bool confim_power_off = true; bool load_devices_from_save_states = false; bool apply_game_settings = true; + bool auto_load_cheats = false; GPURenderer gpu_renderer = GPURenderer::Software; std::string gpu_adapter; diff --git a/src/core/system.cpp b/src/core/system.cpp index a86dd6f1b..50faa1867 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -757,6 +757,7 @@ void Shutdown() s_running_game_title.clear(); s_media_playlist.clear(); s_media_playlist_filename.clear(); + s_cheat_list.reset(); s_state = State::Shutdown; } diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index e429911f1..c05269a55 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -733,27 +733,14 @@ void SDLHostInterface::DrawMainMenuBar() { nfdchar_t* path = nullptr; if (NFD_OpenDialog("cht", nullptr, &path) && path && std::strlen(path) > 0) - { - std::unique_ptr cl = std::make_unique(); - if (cl->LoadFromFile(path, CheatList::Format::Autodetect)) - { - System::SetCheatList(std::move(cl)); - } - else - { - ReportFormattedMessage("Failed to load cheats from '%s'", path); - } - } + LoadCheatList(path); } if (ImGui::MenuItem("Save Cheats...", nullptr, false, has_cheat_file)) { nfdchar_t* path = nullptr; if (NFD_SaveDialog("cht", nullptr, &path) && path && std::strlen(path) > 0) - { - if (!System::GetCheatList()->SaveToPCSXRFile(path)) - ReportFormattedMessage("Failed to save cheats to '%s'", path); - } + SaveCheatList(path); } if (ImGui::BeginMenu("Enabled Cheats", has_cheat_file)) @@ -761,8 +748,9 @@ void SDLHostInterface::DrawMainMenuBar() CheatList* cl = System::GetCheatList(); for (u32 i = 0; i < cl->GetCodeCount(); i++) { - CheatCode& cc = cl->GetCode(i); - ImGui::MenuItem(cc.description.c_str(), nullptr, &cc.enabled, true); + const CheatCode& cc = cl->GetCode(i); + if (ImGui::MenuItem(cc.description.c_str(), nullptr, cc.enabled, true)) + SetCheatCodeState(i, !cc.enabled, g_settings.auto_load_cheats); } ImGui::EndMenu(); @@ -775,7 +763,7 @@ void SDLHostInterface::DrawMainMenuBar() { const CheatCode& cc = cl->GetCode(i); if (ImGui::MenuItem(cc.description.c_str())) - cc.Apply(); + ApplyCheatCode(i); } ImGui::EndMenu(); @@ -1198,6 +1186,8 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed |= ImGui::Checkbox("Pause On Start", &m_settings_copy.start_paused); settings_changed |= ImGui::Checkbox("Start Fullscreen", &m_settings_copy.start_fullscreen); settings_changed |= ImGui::Checkbox("Save State On Exit", &m_settings_copy.save_state_on_exit); + settings_changed |= ImGui::Checkbox("Apply Game Settings", &m_settings_copy.apply_game_settings); + settings_changed |= ImGui::Checkbox("Automatically Load Cheats", &m_settings_copy.auto_load_cheats); settings_changed |= ImGui::Checkbox("Load Devices From Save States", &m_settings_copy.load_devices_from_save_states); } diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index cf121d60f..8df599450 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -7,6 +7,7 @@ #include "common/string_util.h" #include "controller_interface.h" #include "core/cdrom.h" +#include "core/cheats.h" #include "core/cpu_code_cache.h" #include "core/dma.h" #include "core/gpu.h" @@ -116,6 +117,7 @@ void CommonHostInterface::InitializeUserDirectory() result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("bios").c_str(), false); result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("cache").c_str(), false); + result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("cheats").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); @@ -710,6 +712,13 @@ void CommonHostInterface::OnRunningGameChanged() { HostInterface::OnRunningGameChanged(); + if (!System::IsShutdown()) + { + System::SetCheatList(nullptr); + if (g_settings.auto_load_cheats) + LoadCheatListFromGameTitle(); + } + #ifdef WITH_DISCORD_PRESENCE UpdateDiscordPresence(); #endif @@ -2140,6 +2149,111 @@ void CommonHostInterface::ApplyGameSettings(bool display_osd_messages) gs->ApplySettings(display_osd_messages); } +std::string CommonHostInterface::GetCheatFileName() const +{ + const std::string& title = System::GetRunningTitle(); + if (title.empty()) + return {}; + + return GetUserDirectoryRelativePath("cheats/%s.cht", title.c_str()); +} + +bool CommonHostInterface::LoadCheatList(const char* filename) +{ + if (System::IsShutdown()) + return false; + + std::unique_ptr cl = std::make_unique(); + if (!cl->LoadFromPCSXRFile(filename)) + { + AddFormattedOSDMessage(15.0f, TranslateString("OSDMessage", "Failed to load cheats from '%s'."), filename); + return false; + } + + AddFormattedOSDMessage(10.0f, TranslateString("OSDMessage", "Loaded %u cheats from list. %u cheats are enabled."), + cl->GetCodeCount(), cl->GetEnabledCodeCount()); + System::SetCheatList(std::move(cl)); + return true; +} + +bool CommonHostInterface::LoadCheatListFromGameTitle() +{ + const std::string filename(GetCheatFileName()); + if (filename.empty() || !FileSystem::FileExists(filename.c_str())) + return false; + + return LoadCheatList(filename.c_str()); +} + +bool CommonHostInterface::SaveCheatList(const char* filename) +{ + if (!System::IsValid() || !System::HasCheatList()) + return false; + + if (!System::GetCheatList()->SaveToPCSXRFile(filename)) + return false; + + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Saved %u cheats to '%s'."), + System::GetCheatList()->GetCodeCount(), filename); + return true; +} + +void CommonHostInterface::SetCheatCodeState(u32 index, bool enabled, bool save_to_file) +{ + if (!System::IsValid() || !System::HasCheatList()) + return; + + CheatList* cl = System::GetCheatList(); + if (index >= cl->GetCodeCount()) + return; + + CheatCode& cc = cl->GetCode(index); + if (cc.enabled == enabled) + return; + + cc.enabled = enabled; + + if (enabled) + { + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Cheat '%s' enabled."), cc.description.c_str()); + } + else + { + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Cheat '%s' disabled."), cc.description.c_str()); + } + + if (save_to_file) + { + const std::string filename(GetCheatFileName()); + if (!filename.empty()) + { + if (!cl->SaveToPCSXRFile(filename.c_str())) + { + AddFormattedOSDMessage(15.0f, TranslateString("OSDMessage", "Failed to save cheat list to '%s'"), + filename.c_str()); + } + } + } +} + +void CommonHostInterface::ApplyCheatCode(u32 index) +{ + if (!System::HasCheatList() || index >= System::GetCheatList()->GetCodeCount()) + return; + + const CheatCode& cc = System::GetCheatList()->GetCode(index); + if (!cc.enabled) + { + cc.Apply(); + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Applied cheat '%s'."), cc.description.c_str()); + } + else + { + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Cheat '%s' is already enabled."), + cc.description.c_str()); + } +} + #ifdef WITH_DISCORD_PRESENCE void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled) diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 9364df3b2..ec3662005 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -151,6 +151,21 @@ public: /// 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); + /// Loads the cheat list from the specified file. + bool LoadCheatList(const char* filename); + + /// Loads the cheat list for the current game title from the user directory. + bool LoadCheatListFromGameTitle(); + + /// Saves the current cheat list to the specified file. + bool SaveCheatList(const char* filename); + + /// Enables/disabled the specified cheat code. + void SetCheatCodeState(u32 index, bool enabled, bool save_to_file); + + /// Immediately applies the specified cheat code. + void ApplyCheatCode(u32 index); + protected: enum : u32 { @@ -248,6 +263,9 @@ protected: /// Returns the most recent resume save state. std::string GetMostRecentResumeSaveStatePath() const; + /// Returns the path to the cheat file for the specified game title. + std::string GetCheatFileName() const; + /// Ensures the settings is valid and the correct version. If not, resets to defaults. void CheckSettings(SettingsInterface& si);