diff --git a/Source/Core/Core/ActionReplay.cpp b/Source/Core/Core/ActionReplay.cpp index b6bd722069..fc4748f478 100644 --- a/Source/Core/Core/ActionReplay.cpp +++ b/Source/Core/Core/ActionReplay.cpp @@ -19,7 +19,10 @@ // Zero Codes: any code with no address. These codes are used to do special operations like memory copy, etc // ------------------------------------------------------------------------------------------------------------- +#include +#include #include +#include #include #include "Common/CommonTypes.h" @@ -81,6 +84,9 @@ static std::vector activeCodes; static bool logSelf = false; static std::vector arLog; +static std::mutex s_callbacks_lock; +static std::list> s_callbacks; + struct ARAddr { union @@ -100,16 +106,68 @@ struct ARAddr operator u32() const { return address; } }; +static void RunCodeChangeCallbacks() +{ + std::lock_guard guard(s_callbacks_lock); + for (const auto& cb : s_callbacks) + cb(); +} + // ---------------------- // AR Remote Functions -void LoadCodes(const IniFile& globalIni, const IniFile& localIni, bool forceLoad) +void ApplyCodes(const std::vector& codes) { - // Parses the Action Replay section of a game ini file. - if (!SConfig::GetInstance().bEnableCheats && - !forceLoad) + if (!SConfig::GetInstance().bEnableCheats) return; - arCodes.clear(); + arCodes = codes; + UpdateActiveList(); + RunCodeChangeCallbacks(); +} + +void AddCode(const ARCode& code) +{ + if (!SConfig::GetInstance().bEnableCheats) + return; + + arCodes.push_back(code); + if (code.active) + UpdateActiveList(); + RunCodeChangeCallbacks(); +} + +void* RegisterCodeChangeCallback(std::function callback) +{ + if (!callback) + return nullptr; + + std::lock_guard guard(s_callbacks_lock); + s_callbacks.emplace_back(std::move(callback)); + return &s_callbacks.back(); +} + +void UnregisterCodeChangeCallback(void* token) +{ + std::lock_guard guard(s_callbacks_lock); + for (auto i = s_callbacks.begin(); i != s_callbacks.end(); ++i) + { + if (&*i == token) + { + s_callbacks.erase(i); + break; + } + } +} + +void LoadAndApplyCodes(const IniFile& globalIni, const IniFile& localIni) +{ + ApplyCodes(LoadCodes(globalIni, localIni)); +} + +// Parses the Action Replay section of a game ini file. +std::vector LoadCodes(const IniFile& globalIni, const IniFile& localIni) +{ + std::vector codes; std::vector enabledLines; std::set enabledNames; @@ -146,13 +204,13 @@ void LoadCodes(const IniFile& globalIni, const IniFile& localIni, bool forceLoad { if (currentCode.ops.size()) { - arCodes.push_back(currentCode); + codes.push_back(currentCode); currentCode.ops.clear(); } if (encryptedLines.size()) { DecryptARCode(encryptedLines, currentCode.ops); - arCodes.push_back(currentCode); + codes.push_back(currentCode); currentCode.ops.clear(); encryptedLines.clear(); } @@ -204,22 +262,16 @@ void LoadCodes(const IniFile& globalIni, const IniFile& localIni, bool forceLoad // Handle the last code correctly. if (currentCode.ops.size()) { - arCodes.push_back(currentCode); + codes.push_back(currentCode); } if (encryptedLines.size()) { DecryptARCode(encryptedLines, currentCode.ops); - arCodes.push_back(currentCode); + codes.push_back(currentCode); } } - UpdateActiveList(); -} - -void LoadCodes(std::vector &_arCodes, IniFile &globalIni, IniFile& localIni) -{ - LoadCodes(globalIni, localIni, true); - _arCodes = arCodes; + return codes; } @@ -270,6 +322,7 @@ void SetARCode_IsActive(bool active, size_t index) } arCodes[index].active = active; UpdateActiveList(); + RunCodeChangeCallbacks(); } void UpdateActiveList() diff --git a/Source/Core/Core/ActionReplay.h b/Source/Core/Core/ActionReplay.h index d2bf3621bf..be6c4d0bbc 100644 --- a/Source/Core/Core/ActionReplay.h +++ b/Source/Core/Core/ActionReplay.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include "Common/CommonTypes.h" @@ -31,8 +32,12 @@ struct ARCode void RunAllActive(); bool RunCode(const ARCode &arcode); -void LoadCodes(const IniFile &globalini, const IniFile &localIni, bool forceLoad); -void LoadCodes(std::vector &_arCodes, IniFile &globalini, IniFile &localIni); +void ApplyCodes(const std::vector& codes); +void AddCode(const ARCode& new_code); +void* RegisterCodeChangeCallback(std::function callback); +void UnregisterCodeChangeCallback(void* token); +void LoadAndApplyCodes(const IniFile& globalini, const IniFile& localIni); +std::vector LoadCodes(const IniFile& globalini, const IniFile& localIni); size_t GetCodeListSize(); ARCode GetARCode(size_t index); void SetARCode_IsActive(bool active, size_t index); diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 13fb24f5e6..48211a61e0 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -166,7 +166,7 @@ void LoadPatches() IniFile localIni = SConfig::GetInstance().LoadLocalGameIni(); LoadPatchSection("OnFrame", onFrame, globalIni, localIni); - ActionReplay::LoadCodes(globalIni, localIni, false); + ActionReplay::LoadAndApplyCodes(globalIni, localIni); // lil silly std::vector gcodes; diff --git a/Source/Core/DolphinWX/Cheats/CheatsWindow.cpp b/Source/Core/DolphinWX/Cheats/CheatsWindow.cpp index 49a58504a5..83089b58c8 100644 --- a/Source/Core/DolphinWX/Cheats/CheatsWindow.cpp +++ b/Source/Core/DolphinWX/Cheats/CheatsWindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -38,12 +39,19 @@ #include "DolphinWX/Cheats/CreateCodeDialog.h" #include "DolphinWX/Cheats/GeckoCodeDiag.h" +namespace +{ +wxDEFINE_EVENT(DOLPHIN_EVT_UPDATE_CHEAT_LIST, wxThreadEvent); +} + wxCheatsWindow::wxCheatsWindow(wxWindow* const parent) : wxDialog(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxDIALOG_NO_PARENT) { // Create the GUI controls Init_ChildControls(); + m_ar_callback_token = ActionReplay::RegisterCodeChangeCallback(std::bind(&wxCheatsWindow::OnActionReplayModified, this)); + // load codes UpdateGUI(); @@ -54,6 +62,7 @@ wxCheatsWindow::wxCheatsWindow(wxWindow* const parent) wxCheatsWindow::~wxCheatsWindow() { + ActionReplay::UnregisterCodeChangeCallback(m_ar_callback_token); main_frame->g_CheatsWindow = nullptr; } @@ -126,7 +135,7 @@ void wxCheatsWindow::Init_ChildControls() button_cancel->Bind(wxEVT_BUTTON, &wxCheatsWindow::OnEvent_ButtonClose_Press, this); Bind(wxEVT_CLOSE_WINDOW, &wxCheatsWindow::OnEvent_Close, this); - Bind(UPDATE_CHEAT_LIST_EVENT, &wxCheatsWindow::OnEvent_CheatsList_Update, this); + Bind(DOLPHIN_EVT_UPDATE_CHEAT_LIST, &wxCheatsWindow::OnEvent_CheatsList_Update, this); wxStdDialogButtonSizer* const sButtons = new wxStdDialogButtonSizer(); sButtons->AddButton(m_button_apply); @@ -232,24 +241,30 @@ void wxCheatsWindow::OnEvent_CheatsList_ItemToggled(wxCommandEvent& WXUNUSED(eve { if ((int)code_index.uiIndex == index) { + m_ar_ignore_callback = true; ActionReplay::SetARCode_IsActive(m_checklistbox_cheats_list->IsChecked(index), code_index.index); } } } -void wxCheatsWindow::OnEvent_CheatsList_Update(wxCommandEvent& event) +void wxCheatsWindow::OnEvent_CheatsList_Update(wxThreadEvent&) { + if (m_ar_ignore_callback) + { + m_ar_ignore_callback = false; + return; + } Load_ARCodes(); } +void wxCheatsWindow::OnActionReplayModified() +{ + // NOTE: This is an arbitrary thread context + GetEventHandler()->QueueEvent(new wxThreadEvent(DOLPHIN_EVT_UPDATE_CHEAT_LIST)); +} + void wxCheatsWindow::OnEvent_ApplyChanges_Press(wxCommandEvent& ev) { - // Apply AR Code changes - for (const ARCodeIndex& code_index : m_index_list) - { - ActionReplay::SetARCode_IsActive(m_checklistbox_cheats_list->IsChecked(code_index.uiIndex), code_index.index); - } - // Apply Gecko Code changes Gecko::SetActiveCodes(m_geckocode_panel->GetCodes()); @@ -265,11 +280,15 @@ void wxCheatsWindow::OnEvent_ApplyChanges_Press(wxCommandEvent& ev) void wxCheatsWindow::OnEvent_ButtonUpdateLog_Press(wxCommandEvent& WXUNUSED(event)) { + wxBeginBusyCursor(); + m_textctrl_log->Freeze(); m_textctrl_log->Clear(); for (const std::string& text : ActionReplay::GetSelfLog()) { m_textctrl_log->AppendText(StrToWxStr(text)); } + m_textctrl_log->Thaw(); + wxEndBusyCursor(); } void wxCheatsWindow::OnEvent_CheckBoxEnableLogging_StateChange(wxCommandEvent& WXUNUSED(event)) diff --git a/Source/Core/DolphinWX/Cheats/CheatsWindow.h b/Source/Core/DolphinWX/Cheats/CheatsWindow.h index 990219cac3..34e66bb7ff 100644 --- a/Source/Core/DolphinWX/Cheats/CheatsWindow.h +++ b/Source/Core/DolphinWX/Cheats/CheatsWindow.h @@ -72,6 +72,10 @@ private: IniFile m_gameini_local; std::string m_gameini_local_path; + // ActionReplay::UnregisterCodeChangeCallback handle + void* m_ar_callback_token = nullptr; + bool m_ar_ignore_callback = false; + void Init_ChildControls(); void Load_ARCodes(); @@ -86,7 +90,8 @@ private: // Cheats List void OnEvent_CheatsList_ItemSelected(wxCommandEvent& event); void OnEvent_CheatsList_ItemToggled(wxCommandEvent& event); - void OnEvent_CheatsList_Update(wxCommandEvent& event); + void OnEvent_CheatsList_Update(wxThreadEvent& event); + void OnActionReplayModified(); // Apply Changes Button void OnEvent_ApplyChanges_Press(wxCommandEvent& event); diff --git a/Source/Core/DolphinWX/Cheats/CreateCodeDialog.cpp b/Source/Core/DolphinWX/Cheats/CreateCodeDialog.cpp index 364dfbad41..d53136987a 100644 --- a/Source/Core/DolphinWX/Cheats/CreateCodeDialog.cpp +++ b/Source/Core/DolphinWX/Cheats/CreateCodeDialog.cpp @@ -14,9 +14,6 @@ #include "DolphinWX/WxUtils.h" #include "DolphinWX/Cheats/CreateCodeDialog.h" -// Fired when an ActionReplay code is created. -wxDEFINE_EVENT(UPDATE_CHEAT_LIST_EVENT, wxCommandEvent); - CreateCodeDialog::CreateCodeDialog(wxWindow* const parent, const u32 address) : wxDialog(parent, wxID_ANY, _("Create AR Code")) , m_code_address(address) @@ -80,24 +77,21 @@ void CreateCodeDialog::PressOK(wxCommandEvent& ev) // create the new code ActionReplay::ARCode new_cheat; new_cheat.active = false; + new_cheat.user_defined = true; new_cheat.name = WxStrToStr(code_name); - const ActionReplay::AREntry new_entry(m_code_address, code_value); - new_cheat.ops.push_back(new_entry); + new_cheat.ops.emplace_back(ActionReplay::AREntry(m_code_address, code_value)); + ActionReplay::AddCode(new_cheat); // pretty hacky - add the code to the gameini + // FIXME: The save logic should be ActionReplay since it mirrors the parser { - CISOProperties isoprops(GameListItem(SConfig::GetInstance().m_LastFilename, std::unordered_map()), this); + CISOProperties isoprops(GameListItem(SConfig::GetInstance().m_LastFilename, {}), this); // add the code to the isoproperties arcode list isoprops.AddARCode(new_cheat); // save the gameini isoprops.SaveGameConfig(); - isoprops.ActionReplayList_Load(); // loads the new arcodes - //ActionReplay::UpdateActiveList(); } - // Propagate back to the parent frame to update the cheat list. - GetEventHandler()->AddPendingEvent(wxCommandEvent(UPDATE_CHEAT_LIST_EVENT)); - Close(); } diff --git a/Source/Core/DolphinWX/Cheats/CreateCodeDialog.h b/Source/Core/DolphinWX/Cheats/CreateCodeDialog.h index 7c5b752320..bc9ca0ad2b 100644 --- a/Source/Core/DolphinWX/Cheats/CreateCodeDialog.h +++ b/Source/Core/DolphinWX/Cheats/CreateCodeDialog.h @@ -12,8 +12,6 @@ class wxCheckBox; class wxTextCtrl; -wxDECLARE_EVENT(UPDATE_CHEAT_LIST_EVENT, wxCommandEvent); - class CreateCodeDialog final : public wxDialog { public: diff --git a/Source/Core/DolphinWX/ISOProperties.cpp b/Source/Core/DolphinWX/ISOProperties.cpp index d7606c9dad..77c64cd150 100644 --- a/Source/Core/DolphinWX/ISOProperties.cpp +++ b/Source/Core/DolphinWX/ISOProperties.cpp @@ -95,6 +95,7 @@ BEGIN_EVENT_TABLE(CISOProperties, wxDialog) EVT_MENU(IDM_EXTRACTDOL, CISOProperties::OnExtractDataFromHeader) EVT_MENU(IDM_CHECKINTEGRITY, CISOProperties::CheckPartitionIntegrity) EVT_CHOICE(ID_LANG, CISOProperties::OnChangeBannerLang) + EVT_CHECKLISTBOX(ID_CHEATS_LIST, CISOProperties::OnActionReplayCodeChecked) END_EVENT_TABLE() CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& position, const wxSize& size, long style) @@ -1314,6 +1315,11 @@ void CISOProperties::ListSelectionChanged(wxCommandEvent& event) } } +void CISOProperties::OnActionReplayCodeChecked(wxCommandEvent& event) +{ + arCodes[event.GetSelection()].active = Cheats->IsChecked(event.GetSelection()); +} + void CISOProperties::PatchList_Load() { onFrame.clear(); @@ -1399,9 +1405,8 @@ void CISOProperties::PatchButtonClicked(wxCommandEvent& event) void CISOProperties::ActionReplayList_Load() { - arCodes.clear(); Cheats->Clear(); - ActionReplay::LoadCodes(arCodes, GameIniDefault, GameIniLocal); + arCodes = ActionReplay::LoadCodes(GameIniDefault, GameIniLocal); u32 index = 0; for (const ActionReplay::ARCode& arCode : arCodes) @@ -1422,8 +1427,7 @@ void CISOProperties::ActionReplayList_Save() u32 cheats_chkbox_count = Cheats->GetCount(); for (const ActionReplay::ARCode& code : arCodes) { - // Check the index against the count because of the hacky way codes are added from the "Cheat Search" dialog - if ((index < cheats_chkbox_count) && Cheats->IsChecked(index)) + if (code.active) enabledLines.push_back("$" + code.name); // Do not save default cheats. diff --git a/Source/Core/DolphinWX/ISOProperties.h b/Source/Core/DolphinWX/ISOProperties.h index d1b05e047d..9ef253d8ea 100644 --- a/Source/Core/DolphinWX/ISOProperties.h +++ b/Source/Core/DolphinWX/ISOProperties.h @@ -208,6 +208,7 @@ private: void OnComputeMD5Sum(wxCommandEvent& event); void OnShowDefaultConfig(wxCommandEvent& event); void ListSelectionChanged(wxCommandEvent& event); + void OnActionReplayCodeChecked(wxCommandEvent& event); void PatchButtonClicked(wxCommandEvent& event); void ActionReplayButtonClicked(wxCommandEvent& event); void RightClickOnBanner(wxMouseEvent& event);