Memory search: Save, Load, Undo and Redo

This commit is contained in:
Ilari Liusvaara 2013-10-02 21:42:46 +03:00
parent ae643213e9
commit 504dcb912e
5 changed files with 338 additions and 46 deletions

View file

@ -74,12 +74,30 @@ public:
template<typename T> void s_seqge() throw();
template<typename T> void s_seqgt() throw();
template<typename T> T v_read(uint64_t addr) throw();
template<typename T> T v_readold(uint64_t addr) throw();
template<typename T> void v_write(uint64_t addr, T val) throw();
static bool searchable_region(memory_region* r)
{
return (r && !r->readonly && !r->special);
}
/**
* Savestate type.
*/
enum savestate_type
{
ST_PREVMEM,
ST_SET,
ST_ALL
};
/**
* Save state.
*/
void savestate(std::vector<char>& buffer, enum savestate_type type) const;
/**
* Load state.
*/
void loadstate(const std::vector<char>& buffer);
private:
memory_space& mspace;
std::vector<uint8_t> previous_content;

View file

@ -58,6 +58,9 @@ extern single_type filetype_sox;
extern single_type filetype_sub;
extern single_type filetype_png;
extern single_type filetype_hexbookmarks;
extern single_type filetype_memorysearch;
extern single_type filetype_textfile;
filedialog_output_params show_filedialog(wxWindow* parent, const std::string& title, const std::string& basepath,
const filedialog_input_params& p, const std::string& defaultname, bool saving);

View file

@ -369,6 +369,34 @@ template<typename T> void memory_search::s_seqgt() throw() { search(search_seqgt
template<typename T> T memory_search::v_read(uint64_t addr) throw() { return mspace.read<T>(addr); }
template<typename T> void memory_search::v_write(uint64_t addr, T val) throw() { mspace.write<T>(addr, val); }
template<typename T> T memory_search::v_readold(uint64_t addr) throw()
{
uint64_t i = 0;
auto t = mspace.lookup_linear(0);
if(!t.first)
return 0;
//Search for linear range containing the address.
while(true) {
t = mspace.lookup_linear(i);
if(!t.first)
return 0;
if(t.first->base <= addr && t.first->base + t.first->size > addr) {
//Global address t.first->base <=> linear address i.
uint64_t linaddr = addr - t.first->base + i;
uint64_t maxr = t.first->size + t.first->base - addr;
maxr = min(maxr, (uint64_t)sizeof(T));
char buf[sizeof(T)] = {0};
if(previous_content.size() < linaddr + maxr)
return 0;
memcpy(buf, &previous_content[linaddr], maxr);
return read_of_endian<T>(buf, t.first->endian);
}
i += t.first->size - t.second;
}
return 0;
}
template<typename T> void memorysearch_pull_type(memory_search& s)
{
T val;
@ -385,6 +413,7 @@ template<typename T> void memorysearch_pull_type(memory_search& s)
eat_argument(&memory_search::s_seqge<T>);
eat_argument(&memory_search::s_seqgt<T>);
eat_argument(&memory_search::v_read<T>);
eat_argument(&memory_search::v_readold<T>);
eat_argument(&memory_search::v_write<T>);
}
@ -400,6 +429,7 @@ template<typename T> void memorysearch_pull_type2(memory_search& s)
eat_argument(&memory_search::s_ge<T>);
eat_argument(&memory_search::s_gt<T>);
eat_argument(&memory_search::v_read<T>);
eat_argument(&memory_search::v_readold<T>);
eat_argument(&memory_search::v_write<T>);
}
@ -564,3 +594,62 @@ void memory_search::reset() throw(std::bad_alloc)
i += t.first->size - t.second;
}
}
void memory_search::savestate(std::vector<char>& buffer, enum savestate_type type) const
{
size_t size;
uint64_t linsize = mspace.get_linear_size();
if(type == ST_PREVMEM)
size = 9 + linsize;
else if(type == ST_SET)
size = 17 + (linsize + 63) / 64 * 8;
else if(type == ST_ALL)
size = 17 + linsize + (linsize + 63) / 64 * 8;
else
throw std::runtime_error("Invalid savestate type");
buffer.resize(size);
buffer[0] = type;
write64ube(&buffer[1], linsize);
size_t offset = 9;
if(type == ST_PREVMEM || type == ST_ALL) {
memcpy(&buffer[offset], &previous_content[0], min(linsize, (uint64_t)previous_content.size()));
offset += linsize;
}
if(type == ST_SET || type == ST_ALL) {
write64ube(&buffer[offset], candidates);
offset += 8;
size_t bound = min((linsize + 63) / 64, (uint64_t)still_in.size());
for(unsigned i = 0; i < bound; i++) {
write64ube(&buffer[offset], still_in[i]);
offset += 8;
}
}
}
void memory_search::loadstate(const std::vector<char>& buffer)
{
if(buffer.size() < 9 || buffer[0] < ST_PREVMEM || buffer[0] > ST_ALL)
throw std::runtime_error("Invalid memory search save");
uint64_t linsize = read64ube(&buffer[1]);
if(linsize != mspace.get_linear_size())
throw std::runtime_error("Save size mismatch (not from this game)");
if(!previous_content.size())
reset();
savestate_type type = (savestate_type)buffer[0];
size_t offset = 9;
if(type == ST_PREVMEM || type == ST_ALL) {
memcpy(&previous_content[0], &buffer[offset], min(linsize, (uint64_t)previous_content.size()));
offset += linsize;
}
if(type == ST_SET || type == ST_ALL) {
candidates = read64ube(&buffer[offset]);
offset += 8;
size_t bound = min((linsize + 63) / 64, (uint64_t)still_in.size());
for(unsigned i = 0; i < bound; i++) {
still_in[i] = read64ube(&buffer[offset]);
offset += 8;
}
}
}

View file

@ -99,4 +99,5 @@ single_type filetype_sox("sox", "SoX file");
single_type filetype_sub("sub", "Microsub subtitles");
single_type filetype_png("png", "Portable Network Graphics");
single_type filetype_hexbookmarks("lhb", "Hex editor bookmarks");
single_type filetype_memorysearch("lms", "Memory search save");
single_type filetype_textfile("txt", "Text file");

View file

@ -1,15 +1,19 @@
#include "core/dispatch.hpp"
#include "core/memorymanip.hpp"
#include "core/memorywatch.hpp"
#include "core/project.hpp"
#include "library/string.hpp"
#include "library/memorysearch.hpp"
#include "library/int24.hpp"
#include "library/zip.hpp"
#include "platform/wxwidgets/loadsave.hpp"
#include "platform/wxwidgets/platform.hpp"
#include "platform/wxwidgets/scrollbar.hpp"
#include "platform/wxwidgets/textrender.hpp"
#include <sstream>
#include <fstream>
#include <iomanip>
#include <wx/wx.h>
@ -27,6 +31,13 @@
#define wxID_DISQUALIFY (wxID_HIGHEST + 8)
#define wxID_POKE (wxID_HIGHEST + 9)
#define wxID_SHOW_HEXEDITOR (wxID_HIGHEST + 10)
#define wxID_MENU_SAVE_PREVMEM (wxID_HIGHEST + 11)
#define wxID_MENU_SAVE_SET (wxID_HIGHEST + 12)
#define wxID_MENU_SAVE_ALL (wxID_HIGHEST + 13)
#define wxID_MENU_LOAD (wxID_HIGHEST + 14)
#define wxID_MENU_UNDO (wxID_HIGHEST + 15)
#define wxID_MENU_REDO (wxID_HIGHEST + 16)
#define wxID_MENU_DUMP_CANDIDATES (wxID_HIGHEST + 17)
#define wxID_BUTTONS_BASE (wxID_HIGHEST + 128)
#define DATATYPES 12
@ -37,6 +48,7 @@ memory_search* wxwindow_memorysearch_active();
namespace
{
unsigned UNDOHISTORY_MAXSIZE = 48;
const char* watchchars = "bBwWoOdDqQfF";
wxwindow_memorysearch* mwatch;
@ -255,6 +267,28 @@ public:
template<void(memory_search::*sfn)()> void search_0();
template<typename T, typename T2, void(memory_search::*sfn)(T2 val)> void search_1();
template<typename T> void _do_poke_addr(uint64_t addr);
template<typename T> std::string _do_format_signed(uint64_t addr, bool hex, bool old)
{
if(old)
return format_number_signed<T>(msearch->v_readold<T>(addr), hex);
else
return format_number_signed<T>(lsnes_memory.read<T>(addr), hex);
}
template<typename T> std::string _do_format_unsigned(uint64_t addr, bool hex, bool old)
{
if(old)
return format_number_unsigned<T>(msearch->v_readold<T>(addr), hex);
else
return format_number_unsigned<T>(lsnes_memory.read<T>(addr), hex);
}
template<typename T> std::string _do_format_float(uint64_t addr, bool hex, bool old)
{
if(old)
return format_number_float(msearch->v_readold<T>(addr));
else
return format_number_float(lsnes_memory.read<T>(addr));
}
void dump_candidates_text();
private:
friend memory_search* wxwindow_memorysearch_active();
friend class panel;
@ -264,6 +298,10 @@ private:
void on_mouse0(wxMouseEvent& e, bool polarity);
void on_mousedrag();
void on_mouse2(wxMouseEvent& e);
void handle_undo_redo(bool redo);
void push_undo();
void handle_save(memory_search::savestate_type type);
void handle_load();
wxStaticText* count;
scroll_bar* scroll;
panel* matches;
@ -280,6 +318,10 @@ private:
bool toomany;
int scroll_delta;
std::set<std::string> vmas_enabled;
wxMenuItem* undoitem;
wxMenuItem* redoitem;
std::list<std::vector<char>> undohistory;
std::list<std::vector<char>> redohistory;
};
namespace
@ -299,6 +341,22 @@ namespace
&wxwindow_memorysearch::_do_poke_addr<float>,
&wxwindow_memorysearch::_do_poke_addr<double>,
};
typedef std::string (wxwindow_memorysearch::*displayfn_t)(uint64_t, bool hexmode, bool old);
displayfn_t displays[] = {
&wxwindow_memorysearch::_do_format_signed<uint8_t>,
&wxwindow_memorysearch::_do_format_unsigned<uint8_t>,
&wxwindow_memorysearch::_do_format_signed<uint16_t>,
&wxwindow_memorysearch::_do_format_unsigned<uint16_t>,
&wxwindow_memorysearch::_do_format_signed<ss_uint24_t>,
&wxwindow_memorysearch::_do_format_unsigned<ss_uint24_t>,
&wxwindow_memorysearch::_do_format_signed<uint32_t>,
&wxwindow_memorysearch::_do_format_signed<uint32_t>,
&wxwindow_memorysearch::_do_format_unsigned<uint64_t>,
&wxwindow_memorysearch::_do_format_unsigned<uint64_t>,
&wxwindow_memorysearch::_do_format_float<float>,
&wxwindow_memorysearch::_do_format_float<double>,
};
struct searchtype searchtbl[] = {
{
@ -578,6 +636,38 @@ wxwindow_memorysearch::wxwindow_memorysearch()
if(memory_search::searchable_region(i))
vmas_enabled.insert(i->name);
wxMenuBar* menubar = new wxMenuBar();
SetMenuBar(menubar);
wxMenu* filemenu = new wxMenu();
filemenu->Append(wxID_MENU_DUMP_CANDIDATES, wxT("Dump candidates..."));
filemenu->AppendSeparator();
filemenu->Append(wxID_MENU_SAVE_PREVMEM, wxT("Save previous memory..."));
filemenu->Append(wxID_MENU_SAVE_SET, wxT("Save set of addresses..."));
filemenu->Append(wxID_MENU_SAVE_ALL, wxT("Save previous memory and set of addresses..."));
filemenu->AppendSeparator();
filemenu->Append(wxID_MENU_LOAD, wxT("Load save..."));
menubar->Append(filemenu, wxT("File"));
wxMenu* editmenu = new wxMenu();
undoitem = editmenu->Append(wxID_UNDO, wxT("Undo"));
redoitem = editmenu->Append(wxID_REDO, wxT("Redo"));
undoitem->Enable(false);
redoitem->Enable(false);
menubar->Append(editmenu, wxT("Edit"));
Connect(wxID_MENU_DUMP_CANDIDATES, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
Connect(wxID_MENU_SAVE_PREVMEM, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
Connect(wxID_MENU_SAVE_SET, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
Connect(wxID_MENU_SAVE_ALL, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
Connect(wxID_MENU_LOAD, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
Connect(wxID_UNDO, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
Connect(wxID_REDO, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(wxwindow_memorysearch::on_button_click));
dragging = false;
toomany = true;
matches->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxwindow_memorysearch::on_mouse), NULL, this);
@ -634,51 +724,10 @@ void wxwindow_memorysearch::panel::prepare_paint()
long j = 0;
for(auto i : addrs2) {
std::string row = hexformat_address(i) + " ";
switch(_parent->typecode) {
case 0:
row += format_number_signed(lsnes_memory.read<uint8_t>(i), _parent->hexmode);
break;
case 1:
row += format_number_unsigned(lsnes_memory.read<uint8_t>(i),
_parent->hexmode);
break;
case 2:
row += format_number_signed(lsnes_memory.read<uint16_t>(i), _parent->hexmode);
break;
case 3:
row += format_number_unsigned(lsnes_memory.read<uint16_t>(i),
_parent->hexmode);
break;
case 4:
row += format_number_signed(lsnes_memory.read<ss_uint24_t>(i),
_parent->hexmode);
break;
case 5:
row += format_number_unsigned(lsnes_memory.read<ss_uint24_t>(i),
_parent->hexmode);
break;
case 6:
row += format_number_signed(lsnes_memory.read<uint32_t>(i), _parent->hexmode);
break;
case 7:
row += format_number_unsigned(lsnes_memory.read<uint32_t>(i),
_parent->hexmode);
break;
case 8:
row += format_number_signed(lsnes_memory.read<uint64_t>(i),
_parent->hexmode);
break;
case 9:
row += format_number_unsigned(lsnes_memory.read<uint64_t>(i),
_parent->hexmode);
break;
case 10:
row += format_number_float(lsnes_memory.read<float>(i));
break;
case 11:
row += format_number_float(lsnes_memory.read<double>(i));
break;
};
row += (_parent->*displays[_parent->typecode])(i, _parent->hexmode, false);
row += " (Was: ";
row += (_parent->*displays[_parent->typecode])(i, _parent->hexmode, true);
row += ')';
if(j >= first && j < last)
lines.push_back(row);
addrs[j++] = i;
@ -733,6 +782,110 @@ bool wxwindow_memorysearch::ShouldPreventAppExit() const
return false;
}
void wxwindow_memorysearch::dump_candidates_text()
{
try {
std::string filename = choose_file_save(this, "Dump memory search", project_otherpath(),
filetype_textfile);
std::ofstream out(filename);
auto ms = msearch;
runemufn([ms, this, &out]() {
std::list<uint64_t> addrs2 = ms->get_candidates();
long j = 0;
for(auto i : addrs2) {
std::string row = hexformat_address(i) + " ";
row += (this->*displays[this->typecode])(i, this->hexmode, false);
row += " (Was: ";
row += (this->*displays[this->typecode])(i, this->hexmode, true);
row += ')';
out << row << std::endl;
}
});
if(!out)
throw std::runtime_error("Can't write save file");
} catch(canceled_exception& e) {
} catch(std::exception& e) {
show_message_ok(this, "Save error", std::string(e.what()), wxICON_WARNING);
return;
}
}
void wxwindow_memorysearch::handle_save(memory_search::savestate_type type)
{
try {
std::vector<char> state;
msearch->savestate(state, type);
std::string filename = choose_file_save(this, "Save memory search", project_otherpath(),
filetype_memorysearch);
std::ofstream out(filename, std::ios::binary);
out.write(&state[0], state.size());
if(!out)
throw std::runtime_error("Can't write save file");
} catch(canceled_exception& e) {
} catch(std::exception& e) {
show_message_ok(this, "Save error", std::string(e.what()), wxICON_WARNING);
return;
}
}
void wxwindow_memorysearch::handle_load()
{
try {
std::string filename = choose_file_load(this, "Load memory search", project_otherpath(),
filetype_memorysearch);
std::vector<char> state = read_file_relative(filename, "");
push_undo();
msearch->loadstate(state);
update();
} catch(canceled_exception& e) {
} catch(std::exception& e) {
show_message_ok(this, "Load error", std::string(e.what()), wxICON_WARNING);
return;
}
}
void wxwindow_memorysearch::handle_undo_redo(bool redo)
{
std::list<std::vector<char>>& a = *(redo ? &redohistory : &undohistory);
std::list<std::vector<char>>& b = *(redo ? &undohistory : &redohistory);
if(!a.size()) {
show_message_ok(this, "Undo/Redo error", "Can't find state to undo/redo to", wxICON_WARNING);
return;
}
bool pushed = false;
try {
std::vector<char> state;
msearch->savestate(state, memory_search::ST_SET);
b.push_back(state);
pushed = true;
msearch->loadstate(a.back());
a.pop_back();
} catch(std::exception& e) {
if(pushed)
b.pop_back();
show_message_ok(this, "Undo/Redo error", std::string(e.what()), wxICON_WARNING);
return;
}
undoitem->Enable(undohistory.size());
redoitem->Enable(redohistory.size());
update();
}
void wxwindow_memorysearch::push_undo()
{
try {
std::vector<char> state;
msearch->savestate(state, memory_search::ST_SET);
undohistory.push_back(state);
if(undohistory.size() > UNDOHISTORY_MAXSIZE)
undohistory.pop_front();
redohistory.clear();
undoitem->Enable(undohistory.size());
redoitem->Enable(redohistory.size());
} catch(...) {
}
}
void wxwindow_memorysearch::on_mouse(wxMouseEvent& e)
{
if(e.RightUp() || (e.LeftUp() && e.ControlDown()))
@ -825,6 +978,7 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e)
{
int id = e.GetId();
if(id == wxID_RESET) {
push_undo();
msearch->reset();
for(auto i : lsnes_memory.get_regions())
if(memory_search::searchable_region(i) && !vmas_enabled.count(i->name))
@ -869,6 +1023,7 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e)
start = act_line;
end = act_line + 1;
}
push_undo();
for(long r = start; r < end; r++) {
if(!addresses.count(r))
return;
@ -882,7 +1037,12 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e)
wxwindow_memorysearch_vmasel* d = new wxwindow_memorysearch_vmasel(this, vmas_enabled);
if(d->ShowModal() == wxID_OK)
vmas_enabled = d->get_vmas();
else {
d->Destroy();
return;
}
d->Destroy();
push_undo();
for(auto i : lsnes_memory.get_regions())
if(memory_search::searchable_region(i) && !vmas_enabled.count(i->name))
msearch->dq_range(i->base, i->last_address());
@ -919,8 +1079,29 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e)
}
} else if(id >= wxID_BUTTONS_BASE && id < wxID_BUTTONS_BASE + (sizeof(searchtbl)/sizeof(searchtbl[0]))) {
int button = id - wxID_BUTTONS_BASE;
push_undo();
uint64_t old_count = msearch->get_candidate_count();
(this->*(searchtbl[button].searches[typecode]))();
uint64_t new_count = msearch->get_candidate_count();
if(old_count == new_count) {
undohistory.pop_back(); //Shouldn't be undoable.
undoitem->Enable(undohistory.size());
}
wxeditor_hexeditor_update();
} else if(id == wxID_MENU_DUMP_CANDIDATES) {
dump_candidates_text();
} else if(id == wxID_MENU_SAVE_PREVMEM) {
handle_save(memory_search::ST_PREVMEM);
} else if(id == wxID_MENU_SAVE_SET) {
handle_save(memory_search::ST_SET);
} else if(id == wxID_MENU_SAVE_ALL) {
handle_save(memory_search::ST_ALL);
} else if(id == wxID_MENU_LOAD) {
handle_load();
} else if(id == wxID_UNDO) {
handle_undo_redo(false);
} else if(id == wxID_REDO) {
handle_undo_redo(true);
}
update();
}