2014-10-18 21:32:50 +00:00
|
|
|
// Copyright 2014 Dolphin Emulator Project
|
2015-05-17 23:08:10 +00:00
|
|
|
// Licensed under GPLv2+
|
2014-10-18 21:32:50 +00:00
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
2015-08-07 14:04:28 +00:00
|
|
|
#include <algorithm>
|
2014-10-22 23:22:28 +00:00
|
|
|
#include <array>
|
2015-08-06 04:04:34 +00:00
|
|
|
#include <cstring>
|
2015-08-08 04:20:34 +00:00
|
|
|
#include <wx/arrstr.h>
|
2014-10-18 21:32:50 +00:00
|
|
|
#include <wx/button.h>
|
|
|
|
#include <wx/choice.h>
|
2015-08-06 03:55:03 +00:00
|
|
|
#include <wx/listctrl.h>
|
2014-10-18 21:32:50 +00:00
|
|
|
#include <wx/panel.h>
|
2014-10-22 23:22:28 +00:00
|
|
|
#include <wx/radiobox.h>
|
2014-10-18 21:32:50 +00:00
|
|
|
#include <wx/radiobut.h>
|
|
|
|
#include <wx/sizer.h>
|
|
|
|
#include <wx/stattext.h>
|
|
|
|
#include <wx/textctrl.h>
|
2015-08-07 14:04:28 +00:00
|
|
|
#include <wx/timer.h>
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
#include "Common/CommonFuncs.h"
|
|
|
|
#include "Common/CommonTypes.h"
|
|
|
|
#include "Common/StringUtil.h"
|
2015-02-24 23:32:17 +00:00
|
|
|
#include "Core/ActionReplay.h"
|
2015-06-28 15:50:29 +00:00
|
|
|
#include "Core/Core.h"
|
2014-10-18 21:32:50 +00:00
|
|
|
#include "Core/HW/Memmap.h"
|
|
|
|
#include "DolphinWX/WxUtils.h"
|
|
|
|
#include "DolphinWX/Cheats/CheatSearchTab.h"
|
|
|
|
#include "DolphinWX/Cheats/CreateCodeDialog.h"
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2014-10-25 20:32:33 +00:00
|
|
|
const unsigned int MAX_CHEAT_SEARCH_RESULTS_DISPLAY = 1024;
|
2014-10-18 21:32:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CheatSearchTab::CheatSearchTab(wxWindow* const parent)
|
2014-12-02 03:56:18 +00:00
|
|
|
: wxPanel(parent)
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
2015-08-07 14:04:28 +00:00
|
|
|
m_update_timer.SetOwner(this);
|
|
|
|
Bind(wxEVT_TIMER, &CheatSearchTab::OnTimerUpdate, this);
|
|
|
|
|
2014-10-18 21:32:50 +00:00
|
|
|
// first scan button
|
2014-12-02 03:56:18 +00:00
|
|
|
m_btn_init_scan = new wxButton(this, wxID_ANY, _("New Scan"));
|
2015-08-08 04:20:34 +00:00
|
|
|
m_btn_init_scan->Bind(wxEVT_BUTTON, &CheatSearchTab::OnNewScanClicked, this);
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
// next scan button
|
2014-12-02 03:56:18 +00:00
|
|
|
m_btn_next_scan = new wxButton(this, wxID_ANY, _("Next Scan"));
|
2015-08-08 04:20:34 +00:00
|
|
|
m_btn_next_scan->Bind(wxEVT_BUTTON, &CheatSearchTab::OnNextScanClicked, this);
|
2014-10-18 21:32:50 +00:00
|
|
|
m_btn_next_scan->Disable();
|
|
|
|
|
2014-10-22 23:22:28 +00:00
|
|
|
// data sizes radiobox
|
2015-02-15 19:43:31 +00:00
|
|
|
std::array<wxString, 3> data_size_names = { { _("8-bit"), _("16-bit"), _("32-bit") } };
|
2014-10-22 23:22:28 +00:00
|
|
|
m_data_sizes = new wxRadioBox(this, wxID_ANY, _("Data Size"), wxDefaultPosition, wxDefaultSize, static_cast<int>(data_size_names.size()), data_size_names.data());
|
2014-10-18 21:32:50 +00:00
|
|
|
|
2015-08-06 03:55:03 +00:00
|
|
|
// ListView for search results
|
|
|
|
m_lview_search_results = new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL);
|
|
|
|
ResetListViewColumns();
|
2014-10-26 01:44:56 +00:00
|
|
|
|
|
|
|
// Result count
|
2014-12-02 03:56:18 +00:00
|
|
|
m_label_results_count = new wxStaticText(this, wxID_ANY, _("Count:"));
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
// create AR code button
|
2014-12-02 03:56:18 +00:00
|
|
|
wxButton* const button_cheat_search_copy_address = new wxButton(this, wxID_ANY, _("Create AR Code"));
|
2015-08-08 04:20:34 +00:00
|
|
|
button_cheat_search_copy_address->Bind(wxEVT_BUTTON, &CheatSearchTab::OnCreateARCodeClicked, this);
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
// results groupbox
|
|
|
|
wxStaticBoxSizer* const sizer_cheat_search_results = new wxStaticBoxSizer(wxVERTICAL, this, _("Results"));
|
|
|
|
sizer_cheat_search_results->Add(m_label_results_count, 0, wxALIGN_LEFT | wxALL, 5);
|
2015-08-06 03:55:03 +00:00
|
|
|
sizer_cheat_search_results->Add(m_lview_search_results, 1, wxEXPAND | wxALL, 5);
|
2014-10-18 21:32:50 +00:00
|
|
|
sizer_cheat_search_results->Add(button_cheat_search_copy_address, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5);
|
|
|
|
|
|
|
|
// search value textbox
|
2014-12-02 03:56:18 +00:00
|
|
|
m_textctrl_value_x = new wxTextCtrl(this, wxID_ANY, "0x0", wxDefaultPosition, wxSize(96, -1));
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
wxBoxSizer* const sizer_cheat_filter_text = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
sizer_cheat_filter_text->Add(m_textctrl_value_x, 1, wxALIGN_CENTER_VERTICAL, 5);
|
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
// Filter types in the compare dropdown
|
|
|
|
// TODO: Implement between search
|
|
|
|
wxArrayString filters;
|
|
|
|
filters.Add(_("Unknown"));
|
|
|
|
filters.Add(_("Not Equal"));
|
|
|
|
filters.Add(_("Equal"));
|
|
|
|
filters.Add(_("Greater Than"));
|
|
|
|
filters.Add(_("Less Than"));
|
|
|
|
|
|
|
|
m_search_type = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, filters);
|
2014-10-18 21:32:50 +00:00
|
|
|
m_search_type->Select(0);
|
|
|
|
|
2015-12-30 00:51:36 +00:00
|
|
|
wxStaticBoxSizer* const sizer_cheat_search_filter = new wxStaticBoxSizer(wxVERTICAL, this, _("Search (clear to use previous value)"));
|
2015-08-08 04:20:34 +00:00
|
|
|
sizer_cheat_search_filter->Add(sizer_cheat_filter_text, 0, wxALL | wxEXPAND, 5);
|
2014-10-18 21:32:50 +00:00
|
|
|
sizer_cheat_search_filter->Add(m_search_type, 0, wxALL, 5);
|
|
|
|
|
|
|
|
// left sizer
|
|
|
|
wxBoxSizer* const sizer_left = new wxBoxSizer(wxVERTICAL);
|
|
|
|
sizer_left->Add(sizer_cheat_search_results, 1, wxEXPAND, 5);
|
|
|
|
|
|
|
|
// button sizer
|
|
|
|
wxBoxSizer* boxButtons = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
boxButtons->Add(m_btn_init_scan, 1, wxRIGHT, 5);
|
|
|
|
boxButtons->Add(m_btn_next_scan, 1);
|
|
|
|
|
|
|
|
// right sizer
|
|
|
|
wxBoxSizer* const sizer_right = new wxBoxSizer(wxVERTICAL);
|
2015-02-15 19:43:31 +00:00
|
|
|
sizer_right->Add(m_data_sizes, 0, wxEXPAND | wxBOTTOM, 5);
|
2014-10-18 21:32:50 +00:00
|
|
|
sizer_right->Add(sizer_cheat_search_filter, 0, wxEXPAND | wxBOTTOM, 5);
|
|
|
|
sizer_right->AddStretchSpacer(1);
|
|
|
|
sizer_right->Add(boxButtons, 0, wxTOP | wxEXPAND, 5);
|
|
|
|
|
|
|
|
// main sizer
|
|
|
|
wxBoxSizer* const sizer_main = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
sizer_main->Add(sizer_left, 1, wxEXPAND | wxALL, 5);
|
|
|
|
sizer_main->Add(sizer_right, 0, wxEXPAND | wxALL, 5);
|
|
|
|
|
|
|
|
SetSizerAndFit(sizer_main);
|
|
|
|
}
|
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
void CheatSearchTab::OnNewScanClicked(wxCommandEvent& WXUNUSED(event))
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
2015-06-28 15:50:29 +00:00
|
|
|
if (!Core::IsRunningAndStarted())
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
|
|
|
WxUtils::ShowErrorDialog(_("A game is not currently running."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the user-selected data size for this search.
|
2014-10-22 23:22:28 +00:00
|
|
|
m_search_type_size = (1 << m_data_sizes->GetSelection());
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
// Set up the search results efficiently to prevent automatic re-allocations.
|
|
|
|
m_search_results.clear();
|
|
|
|
m_search_results.reserve(Memory::RAM_SIZE / m_search_type_size);
|
|
|
|
|
|
|
|
// Enable the "Next Scan" button.
|
|
|
|
m_btn_next_scan->Enable();
|
|
|
|
|
|
|
|
CheatSearchResult r;
|
|
|
|
// can I assume cheatable values will be aligned like this?
|
|
|
|
for (u32 addr = 0; addr != Memory::RAM_SIZE; addr += m_search_type_size)
|
|
|
|
{
|
|
|
|
r.address = addr;
|
2015-08-08 04:20:34 +00:00
|
|
|
memcpy(&r.old_value, &Memory::m_pRAM[addr], m_search_type_size);
|
2014-10-18 21:32:50 +00:00
|
|
|
m_search_results.push_back(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateCheatSearchResultsList();
|
|
|
|
}
|
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
void CheatSearchTab::OnNextScanClicked(wxCommandEvent&)
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
2015-06-28 15:50:29 +00:00
|
|
|
if (!Core::IsRunningAndStarted())
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
|
|
|
WxUtils::ShowErrorDialog(_("A game is not currently running."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
u32 user_x_val = 0;
|
2015-12-30 00:51:36 +00:00
|
|
|
bool blank_user_value = m_textctrl_value_x->IsEmpty();
|
|
|
|
if (!blank_user_value)
|
|
|
|
{
|
|
|
|
if (!ParseUserEnteredValue(&user_x_val))
|
|
|
|
return;
|
|
|
|
}
|
2014-10-18 21:32:50 +00:00
|
|
|
|
2015-12-30 00:51:36 +00:00
|
|
|
FilterCheatSearchResults(user_x_val, blank_user_value);
|
2014-10-18 21:32:50 +00:00
|
|
|
|
|
|
|
UpdateCheatSearchResultsList();
|
|
|
|
}
|
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
void CheatSearchTab::OnCreateARCodeClicked(wxCommandEvent&)
|
2015-08-07 14:04:28 +00:00
|
|
|
{
|
|
|
|
long idx = m_lview_search_results->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
|
|
|
if (idx == wxNOT_FOUND)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const u32 address = m_search_results[idx].address | ((m_search_type_size & ~1) << 24);
|
|
|
|
|
2015-09-19 15:20:16 +00:00
|
|
|
CreateCodeDialog arcode_dlg(this, address);
|
2015-08-07 14:04:28 +00:00
|
|
|
arcode_dlg.ShowModal();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatSearchTab::OnTimerUpdate(wxTimerEvent&)
|
|
|
|
{
|
|
|
|
if (Core::GetState() != Core::CORE_RUN)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only update the currently visible list rows.
|
|
|
|
long first = m_lview_search_results->GetTopItem();
|
|
|
|
long last = std::min(m_lview_search_results->GetItemCount(), m_lview_search_results->GetCountPerPage());
|
|
|
|
|
|
|
|
m_lview_search_results->Freeze();
|
|
|
|
|
|
|
|
while (first < last)
|
|
|
|
{
|
|
|
|
UpdateCheatSearchResultItem(first);
|
|
|
|
first++;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_lview_search_results->Thaw();
|
|
|
|
}
|
|
|
|
|
2014-10-18 21:32:50 +00:00
|
|
|
void CheatSearchTab::UpdateCheatSearchResultsList()
|
|
|
|
{
|
2015-08-07 14:04:28 +00:00
|
|
|
m_update_timer.Stop();
|
2015-08-06 03:55:03 +00:00
|
|
|
m_lview_search_results->ClearAll();
|
|
|
|
ResetListViewColumns();
|
2014-10-18 21:32:50 +00:00
|
|
|
|
2014-10-30 03:19:21 +00:00
|
|
|
wxString count_label = wxString::Format(_("Count: %lu"),
|
2014-10-18 21:32:50 +00:00
|
|
|
(unsigned long)m_search_results.size());
|
|
|
|
if (m_search_results.size() > MAX_CHEAT_SEARCH_RESULTS_DISPLAY)
|
|
|
|
{
|
|
|
|
count_label += _(" (too many to display)");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-08-06 03:55:03 +00:00
|
|
|
m_lview_search_results->Freeze();
|
|
|
|
|
|
|
|
for (size_t i = 0; i < m_search_results.size(); i++)
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
2015-08-06 03:55:03 +00:00
|
|
|
// Insert into the list control.
|
2015-08-07 14:04:28 +00:00
|
|
|
wxString address_string = wxString::Format("0x%08X", m_search_results[i].address);
|
|
|
|
long index = m_lview_search_results->InsertItem(static_cast<long>(i), address_string);
|
|
|
|
|
|
|
|
UpdateCheatSearchResultItem(index);
|
2014-10-18 21:32:50 +00:00
|
|
|
}
|
2015-08-06 03:55:03 +00:00
|
|
|
|
|
|
|
m_lview_search_results->Thaw();
|
2015-08-07 14:04:28 +00:00
|
|
|
|
|
|
|
// Half-second update interval
|
|
|
|
m_update_timer.Start(500);
|
2014-10-18 21:32:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m_label_results_count->SetLabel(count_label);
|
|
|
|
}
|
|
|
|
|
2015-08-07 14:04:28 +00:00
|
|
|
void CheatSearchTab::UpdateCheatSearchResultItem(long index)
|
2014-10-18 21:32:50 +00:00
|
|
|
{
|
2015-08-07 14:04:28 +00:00
|
|
|
u32 address_value = 0;
|
|
|
|
std::memcpy(&address_value, &Memory::m_pRAM[m_search_results[index].address], m_search_type_size);
|
2015-08-06 03:55:03 +00:00
|
|
|
|
2015-08-07 14:04:28 +00:00
|
|
|
u32 display_value = SwapValue(address_value);
|
2014-10-18 21:32:50 +00:00
|
|
|
|
2015-08-07 14:04:28 +00:00
|
|
|
wxString buf;
|
|
|
|
buf.Printf("0x%08X", display_value);
|
|
|
|
m_lview_search_results->SetItem(index, 1, buf);
|
|
|
|
|
|
|
|
float display_value_float = 0.0f;
|
|
|
|
std::memcpy(&display_value_float, &display_value, sizeof(u32));
|
|
|
|
buf.Printf("%e", display_value_float);
|
|
|
|
m_lview_search_results->SetItem(index, 2, buf);
|
|
|
|
|
|
|
|
double display_value_double = 0.0;
|
|
|
|
std::memcpy(&display_value_double, &display_value, sizeof(u32));
|
|
|
|
buf.Printf("%e", display_value_double);
|
|
|
|
m_lview_search_results->SetItem(index, 3, buf);
|
2014-10-18 21:32:50 +00:00
|
|
|
}
|
2015-08-06 03:55:03 +00:00
|
|
|
|
2015-12-26 17:40:26 +00:00
|
|
|
enum class ComparisonMask
|
|
|
|
{
|
|
|
|
EQUAL = 0x1,
|
|
|
|
GREATER_THAN = 0x2,
|
|
|
|
LESS_THAN = 0x4
|
|
|
|
};
|
|
|
|
|
|
|
|
static ComparisonMask operator | (ComparisonMask comp1, ComparisonMask comp2)
|
|
|
|
{
|
|
|
|
return static_cast<ComparisonMask>(static_cast<int>(comp1) |
|
|
|
|
static_cast<int>(comp2));
|
|
|
|
}
|
|
|
|
|
|
|
|
static ComparisonMask operator & (ComparisonMask comp1, ComparisonMask comp2)
|
|
|
|
{
|
|
|
|
return static_cast<ComparisonMask>(static_cast<int>(comp1) &
|
|
|
|
static_cast<int>(comp2));
|
|
|
|
}
|
|
|
|
|
2015-12-30 00:51:36 +00:00
|
|
|
void CheatSearchTab::FilterCheatSearchResults(u32 value, bool prev)
|
2015-08-08 04:20:34 +00:00
|
|
|
{
|
2015-12-26 17:40:26 +00:00
|
|
|
static const std::array<ComparisonMask, 5> filters{{
|
|
|
|
ComparisonMask::EQUAL | ComparisonMask::GREATER_THAN | ComparisonMask::LESS_THAN, // Unknown
|
|
|
|
ComparisonMask::GREATER_THAN | ComparisonMask::LESS_THAN, // Not Equal
|
|
|
|
ComparisonMask::EQUAL,
|
|
|
|
ComparisonMask::GREATER_THAN,
|
|
|
|
ComparisonMask::LESS_THAN
|
|
|
|
}};
|
|
|
|
ComparisonMask filter_mask = filters[m_search_type->GetSelection()];
|
2015-08-08 04:20:34 +00:00
|
|
|
|
|
|
|
std::vector<CheatSearchResult> filtered_results;
|
|
|
|
filtered_results.reserve(m_search_results.size());
|
|
|
|
|
|
|
|
for (CheatSearchResult& result : m_search_results)
|
|
|
|
{
|
2015-12-30 00:51:36 +00:00
|
|
|
if (prev)
|
|
|
|
value = result.old_value;
|
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
// with big endian, can just use memcmp for ><= comparison
|
|
|
|
int cmp_result = std::memcmp(&Memory::m_pRAM[result.address], &value, m_search_type_size);
|
2015-12-26 17:40:26 +00:00
|
|
|
ComparisonMask cmp_mask;
|
2015-08-08 04:20:34 +00:00
|
|
|
if (cmp_result < 0)
|
2015-12-26 17:40:26 +00:00
|
|
|
cmp_mask = ComparisonMask::LESS_THAN;
|
2015-08-08 04:20:34 +00:00
|
|
|
else if (cmp_result)
|
2015-12-26 17:40:26 +00:00
|
|
|
cmp_mask = ComparisonMask::GREATER_THAN;
|
2015-08-08 04:20:34 +00:00
|
|
|
else
|
2015-12-26 17:40:26 +00:00
|
|
|
cmp_mask = ComparisonMask::EQUAL;
|
2015-08-08 04:20:34 +00:00
|
|
|
|
2015-12-26 17:40:26 +00:00
|
|
|
if (static_cast<int>(cmp_mask & filter_mask))
|
2015-08-08 04:20:34 +00:00
|
|
|
{
|
|
|
|
std::memcpy(&result.old_value, &Memory::m_pRAM[result.address], m_search_type_size);
|
|
|
|
filtered_results.push_back(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_search_results.swap(filtered_results);
|
|
|
|
}
|
|
|
|
|
2015-08-06 03:55:03 +00:00
|
|
|
void CheatSearchTab::ResetListViewColumns()
|
|
|
|
{
|
|
|
|
m_lview_search_results->AppendColumn(_("Address"));
|
|
|
|
m_lview_search_results->AppendColumn(_("Value"));
|
2015-08-06 04:04:34 +00:00
|
|
|
m_lview_search_results->AppendColumn(_("Value (float)"));
|
|
|
|
m_lview_search_results->AppendColumn(_("Value (double)"));
|
2015-08-06 03:55:03 +00:00
|
|
|
}
|
2015-08-07 14:04:28 +00:00
|
|
|
|
2015-08-08 04:20:34 +00:00
|
|
|
bool CheatSearchTab::ParseUserEnteredValue(u32* out) const
|
|
|
|
{
|
|
|
|
unsigned long parsed_x_val = 0;
|
|
|
|
wxString x_val = m_textctrl_value_x->GetValue();
|
|
|
|
|
|
|
|
if (!x_val.ToULong(&parsed_x_val, 0))
|
|
|
|
{
|
|
|
|
WxUtils::ShowErrorDialog(_("You must enter a valid decimal, hexadecimal or octal value."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*out = SwapValue(static_cast<u32>(parsed_x_val));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-08-07 14:04:28 +00:00
|
|
|
u32 CheatSearchTab::SwapValue(u32 value) const
|
|
|
|
{
|
|
|
|
switch (m_search_type_size)
|
|
|
|
{
|
|
|
|
case 2:
|
|
|
|
*(u16*)&value = Common::swap16((u8*)&value);
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
value = Common::swap32(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|