From 7c1acd22195dd4ffaca61c865db4cb9011135400 Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Sun, 29 Sep 2013 18:35:15 +0300 Subject: [PATCH] Hex editor --- include/core/dispatch.hpp | 1 + include/core/window.hpp | 4 + include/library/memorysearch.hpp | 8 + include/library/memoryspace.hpp | 6 + include/platform/wxwidgets/loadsave.hpp | 1 + include/platform/wxwidgets/platform.hpp | 4 + src/core/dispatch.cpp | 1 + src/core/mainloop.cpp | 2 + src/core/rom.cpp | 2 + src/core/window.cpp | 16 +- src/emulation/sky/sky.cpp | 10 +- src/library/memorysearch.cpp | 97 ++++ src/library/memoryspace.cpp | 11 + src/platform/wxwidgets/editor-hexedit.cpp | 804 ++++++++++++++++++++++++++++++ src/platform/wxwidgets/loadsave.cpp | 1 + src/platform/wxwidgets/main.cpp | 1 + src/platform/wxwidgets/mainwindow.cpp | 5 + src/platform/wxwidgets/memorysearch.cpp | 14 + src/platform/wxwidgets/scrollbar.cpp | 2 + 19 files changed, 988 insertions(+), 2 deletions(-) create mode 100644 src/platform/wxwidgets/editor-hexedit.cpp diff --git a/include/core/dispatch.hpp b/include/core/dispatch.hpp index eae2f055..aa51633d 100644 --- a/include/core/dispatch.hpp +++ b/include/core/dispatch.hpp @@ -266,6 +266,7 @@ extern struct dispatcher<> notify_status_update; extern struct dispatcher notify_sound_unmute; extern struct dispatcher notify_mode_change; extern struct dispatcher<> notify_core_change; +extern struct dispatcher notify_core_changed; extern struct dispatcher<> notify_new_core; extern struct dispatcher<> notify_voice_stream_change; extern struct dispatcher<> notify_vu_change; diff --git a/include/core/window.hpp b/include/core/window.hpp index a926c1eb..07f4aca8 100644 --- a/include/core/window.hpp +++ b/include/core/window.hpp @@ -311,6 +311,10 @@ struct platform * Run all queues. */ static void run_queues() throw(); +/** + * Set availablinty of system thread. + */ + static void system_thread_available(bool av) throw(); static bool pausing_allowed; static double global_volume; diff --git a/include/library/memorysearch.hpp b/include/library/memorysearch.hpp index d601b05f..eda57352 100644 --- a/include/library/memorysearch.hpp +++ b/include/library/memorysearch.hpp @@ -52,6 +52,14 @@ public: * Returns list of all candidates. This function isn't lazy, so be careful when calling with many candidates. */ std::list get_candidates() throw(std::bad_alloc); +/** + * Is specified address a candidate? + */ + bool is_candidate(uint64_t addr) throw(); +/** + * Next candidate in VMA. + */ + uint64_t cycle_candidate_vma(uint64_t addr, bool next) throw(); template void s_value(T value) throw(); template void s_difference(T value) throw(); diff --git a/include/library/memoryspace.hpp b/include/library/memoryspace.hpp index c3723dc7..0bfe58a8 100644 --- a/include/library/memoryspace.hpp +++ b/include/library/memoryspace.hpp @@ -227,6 +227,12 @@ public: * Returns: True on success, false on failure. */ bool write_range_linear(uint64_t linear, const void* buffer, size_t bsize); +/** + * Read complete linear memory. + * + * Parameter buffer: Buffer to store to (get_linear_size() bytes). + */ + void read_all_linear_memory(uint8_t* buffer); private: mutex_class mutex; std::vector u_regions; diff --git a/include/platform/wxwidgets/loadsave.hpp b/include/platform/wxwidgets/loadsave.hpp index 3f3d0807..24d86fa1 100644 --- a/include/platform/wxwidgets/loadsave.hpp +++ b/include/platform/wxwidgets/loadsave.hpp @@ -57,6 +57,7 @@ extern single_type filetype_commentary; extern single_type filetype_sox; extern single_type filetype_sub; extern single_type filetype_png; +extern single_type filetype_hexbookmarks; 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); diff --git a/include/platform/wxwidgets/platform.hpp b/include/platform/wxwidgets/platform.hpp index 6a105fff..b564a400 100644 --- a/include/platform/wxwidgets/platform.hpp +++ b/include/platform/wxwidgets/platform.hpp @@ -62,10 +62,14 @@ void wxeditor_movie_update(); void wxeditor_autohold_display(wxWindow* parent); void wxeditor_tasinput_display(wxWindow* parent); void wxeditor_macro_display(wxWindow* parent); +void wxeditor_hexedit_display(wxWindow* parent); //Auxillary windows. void wxwindow_memorysearch_display(); void wxwindow_memorysearch_update(); +void wxeditor_hexeditor_update(); +class memory_search; +memory_search* wxwindow_memorysearch_active(); template void functor_call_helper2(void* args) diff --git a/src/core/dispatch.cpp b/src/core/dispatch.cpp index e04634e1..2ce19fbc 100644 --- a/src/core/dispatch.cpp +++ b/src/core/dispatch.cpp @@ -350,6 +350,7 @@ struct dispatcher<> notify_status_update("status_update"); struct dispatcher notify_sound_unmute("sound_unmute"); struct dispatcher notify_mode_change("mode_change"); struct dispatcher<> notify_core_change("core_change"); +struct dispatcher notify_core_changed("core_changed"); struct dispatcher<> notify_new_core("new_core"); struct dispatcher<> notify_voice_stream_change("voice_stream_change"); struct dispatcher<> notify_vu_change("vu_change"); diff --git a/src/core/mainloop.cpp b/src/core/mainloop.cpp index 8d8e14d7..d91ea2b0 100644 --- a/src/core/mainloop.cpp +++ b/src/core/mainloop.cpp @@ -1074,6 +1074,7 @@ nothing_to_do: void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_to_succeed) throw(std::bad_alloc, std::runtime_error) { + platform::system_thread_available(true); //Basic initialization. dispatch_set_error_streams(&messages.getstream()); emulation_thread = this_thread_id(); @@ -1179,6 +1180,7 @@ void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_ information_dispatch::do_dump_end(); core_core::uninstall_all_handlers(); voicethread_kill(); + platform::system_thread_available(false); } void set_stop_at_frame(uint64_t frame) diff --git a/src/core/rom.cpp b/src/core/rom.cpp index f98259c3..7da6264c 100644 --- a/src/core/rom.cpp +++ b/src/core/rom.cpp @@ -543,6 +543,7 @@ loaded_rom::loaded_rom(const std::string file[ROM_SLOT_COUNT], const std::string void loaded_rom::load(std::map& settings, uint64_t rtc_sec, uint64_t rtc_subsec) throw(std::bad_alloc, std::runtime_error) { + core_type* old_type = current_rom_type; core_core* old_core = current_rom_type->get_core(); current_rom_type = &core_null; if(!orig_region && rtype != &core_null) @@ -575,6 +576,7 @@ void loaded_rom::load(std::map& settings, uint64_t rtc if(old_core != current_rom_type->get_core()) try { old_core->unload_cartridge(); } catch(...) {} refresh_cart_mappings(); + notify_core_changed(old_type != current_rom_type); } std::map> load_sram_commandline(const std::vector& cmdline) diff --git a/src/core/window.cpp b/src/core/window.cpp index 175b1841..8ee3fe34 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -30,6 +30,11 @@ #define MAXMESSAGES 5000 #define INIT_WIN_SIZE 6 +namespace +{ + volatile bool _system_thread_available = false; +} + keypress::keypress() { key1 = NULL; @@ -476,13 +481,17 @@ void platform::queue(const std::string& c) throw(std::bad_alloc) void platform::queue(void (*f)(void* arg), void* arg, bool sync) throw(std::bad_alloc) { + if(!_system_thread_available) { + f(arg); + return; + } init_threading(); umutex_class h(queue_lock); ++next_function; functions.push_back(std::make_pair(f, arg)); queue_condition.notify_all(); if(sync) - while(functions_executed < next_function) + while(functions_executed < next_function && _system_thread_available) cv_timed_wait(queue_condition, h, microsec_class(10000)); } @@ -491,6 +500,11 @@ void platform::run_queues() throw() internal_run_queues(false); } +void platform::system_thread_available(bool av) throw() +{ + _system_thread_available = av; +} + namespace { mutex_class _msgbuf_lock; diff --git a/src/emulation/sky/sky.cpp b/src/emulation/sky/sky.cpp index fb64bde7..fa8d25af 100644 --- a/src/emulation/sky/sky.cpp +++ b/src/emulation/sky/sky.cpp @@ -371,11 +371,19 @@ namespace sky core_vma_info ram; ram.name = "RAM"; ram.backing_ram = corei.state.as_ram().first; - ram.size = corei.state.as_ram().second - 32; + ram.size = 131072; ram.base = 0; ram.endian = 0; ram.volatile_flag = true; r.push_back(ram); + core_vma_info wram; + wram.name = "WRAM"; + wram.backing_ram = corei.state.as_ram().first + 131072; + wram.size = corei.state.as_ram().second - 131072 - 32; + wram.base = 131072; + wram.endian = 0; + wram.volatile_flag = true; + r.push_back(wram); core_vma_info sram; sram.name = "SRAM"; sram.backing_ram = corei.state.as_ram().first + corei.state.as_ram().second - 32; diff --git a/src/library/memorysearch.cpp b/src/library/memorysearch.cpp index 9c1861ed..1104c616 100644 --- a/src/library/memorysearch.cpp +++ b/src/library/memorysearch.cpp @@ -171,6 +171,11 @@ namespace return (i + 64) >> 6 << 6; } + inline uint64_t prev_multiple_of_64(uint64_t i) + { + return ((i - 64) >> 6 << 6) + 63; + } + template void search_block_mapped(uint64_t* still_in, uint64_t& candidates, memory_region& region, uint64_t rbase, uint64_t ibase, T& helper, std::vector& previous_content) @@ -296,6 +301,7 @@ namespace out.push_back(region.base + j); } } + } void memory_search::dq_range(uint64_t first, uint64_t last) @@ -431,6 +437,97 @@ std::list memory_search::get_candidates() throw(std::bad_alloc) return out; } +bool memory_search::is_candidate(uint64_t addr) throw() +{ + auto t = mspace.lookup_linear(0); + if(!t.first) + return false; + uint64_t i = 0; + while(true) { + //Switch blocks. + t = mspace.lookup_linear(i); + if(!t.first) + return false; + if(i >= previous_content.size()) + return false; + uint64_t rsize = t.first->size; + uint64_t switch_at = i + rsize - t.second; //The smallest i not in this region. + rsize = min(rsize, previous_content.size() - i); + if(addr >= t.first->base + t.second && addr < t.first->base + rsize) { + uint64_t adv = addr - (t.first->base + t.second); + uint64_t ix = i + adv; + return ((still_in[ix / 64] >> (ix % 64)) & 1); + } + i += t.first->size - t.second; + } + return false; +} + +uint64_t memory_search::cycle_candidate_vma(uint64_t addr, bool next) throw() +{ + auto t = mspace.lookup_linear(0); + if(!t.first) + return false; + uint64_t i = 0; + while(true) { + //Switch blocks. + t = mspace.lookup_linear(i); + if(!t.first) + return addr; + if(i >= previous_content.size()) + return addr; + uint64_t rsize = t.first->size; + uint64_t switch_at = i + rsize - t.second; //The smallest i not in this region. + rsize = min(rsize, previous_content.size() - i); + if(addr >= t.first->base + t.second && addr < t.first->base + rsize) { + uint64_t baseaddr = t.first->base + t.second; + int64_t tryoff = addr - baseaddr + i; + uint64_t finoff = tryoff; + uint64_t warp = i; + bool warped = false; + if(next) { + //Cycle forwards. + tryoff++; + while(tryoff < finoff || !warped) { + if(tryoff >= switch_at) { + tryoff = warp; + if(warped) + return addr; + warped = true; + } + if(still_in[tryoff / 64] == 0) + tryoff = next_multiple_of_64(tryoff); + else { + if((still_in[tryoff / 64] >> (tryoff % 64)) & 1) + return tryoff - i + baseaddr; + tryoff++; + } + } + } else { + //Cycle backwards. + tryoff--; + while(tryoff > finoff || !warped) { + if(tryoff < warp) { + tryoff = switch_at - 1; + if(warped) + return addr; + warped = true; + } + if(still_in[tryoff / 64] == 0) + tryoff = prev_multiple_of_64(tryoff); + else { + if((still_in[tryoff / 64] >> (tryoff % 64)) & 1) + return tryoff - i + baseaddr; + tryoff--; + } + } + } + } + i += t.first->size - t.second; + } + return addr; +} + void memory_search::reset() throw(std::bad_alloc) { uint64_t linearram = mspace.get_linear_size(); diff --git a/src/library/memoryspace.cpp b/src/library/memoryspace.cpp index 30c9517e..982c6ef8 100644 --- a/src/library/memoryspace.cpp +++ b/src/library/memoryspace.cpp @@ -142,6 +142,17 @@ std::pair memory_space::lookup_linear(uint64_t linear) return std::make_pair(reinterpret_cast(NULL), 0); } +void memory_space::read_all_linear_memory(uint8_t* buffer) +{ + auto g = lookup_linear(0); + size_t off = 0; + while(g.first) { + read_range_r(*g.first, g.second, buffer + off, g.first->size); + off += g.first->size; + g = lookup_linear(off); + } +} + #define MSR memory_space::read #define MSW memory_space::write #define MSRL memory_space::read_linear diff --git a/src/platform/wxwidgets/editor-hexedit.cpp b/src/platform/wxwidgets/editor-hexedit.cpp new file mode 100644 index 00000000..869ffe21 --- /dev/null +++ b/src/platform/wxwidgets/editor-hexedit.cpp @@ -0,0 +1,804 @@ +#include "core/moviedata.hpp" +#include "core/memorywatch.hpp" +#include "core/dispatch.hpp" +#include "core/project.hpp" +#include "core/memorymanip.hpp" +#include "library/memorysearch.hpp" + +#include "platform/wxwidgets/platform.hpp" +#include "platform/wxwidgets/textrender.hpp" +#include "platform/wxwidgets/loadsave.hpp" +#include "platform/wxwidgets/scrollbar.hpp" + +#include +#include +#include +#include +#include + +#include "library/string.hpp" +#include "library/json.hpp" +#include "library/zip.hpp" +#include "interface/romtype.hpp" + +#include + +class wxeditor_hexedit; + +namespace +{ + const size_t maxvaluelen = 8; //The length of longest value type. + wxeditor_hexedit* editor; + + struct val_type + { + const char* name; + unsigned len; + bool hard_bigendian; + std::string (*read)(const uint8_t* x); + }; + + val_type datatypes[] = { + {"1 byte (signed)", 1, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << (int)(char)x[0]).str(); + }}, + {"1 byte (unsigned)", 1, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << (int)x[0]).str(); + }}, + {"1 byte (hex)", 1, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << std::hex << std::setw(2) << std::setfill('0') << (int)x[0]).str(); + }}, + {"2 bytes (signed)", 2, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(int16_t*)x).str(); + }}, + {"2 bytes (unsigned)", 2, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(uint16_t*)x).str(); + }}, + {"2 bytes (hex)", 2, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << std::hex << std::setw(4) << std::setfill('0') << *(uint16_t*)x).str(); + }}, + {"3 bytes (signed)", 3, true, [](const uint8_t* x) -> std::string { + int32_t a = 0; + a |= (uint32_t)x[0] << 16; + a |= (uint32_t)x[1] << 8; + a |= (uint32_t)x[2]; + if(a & 0x800000) + a -= 0x1000000; + return (stringfmt() << a).str(); + }}, + {"3 bytes (unsigned)", 3, true, [](const uint8_t* x) -> std::string { + int32_t a = 0; + a |= (uint32_t)x[0] << 16; + a |= (uint32_t)x[1] << 8; + a |= (uint32_t)x[2]; + return (stringfmt() << a).str(); + }}, + {"3 bytes (hex)", 3, true, [](const uint8_t* x) -> std::string { + int32_t a = 0; + a |= (uint32_t)x[0] << 16; + a |= (uint32_t)x[1] << 8; + a |= (uint32_t)x[2]; + return (stringfmt() << std::hex << std::setw(6) << std::setfill('0') << a).str(); + }}, + {"4 bytes (signed)", 4, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(int32_t*)x).str(); + }}, + {"4 bytes (unsigned)", 4, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(uint32_t*)x).str(); + }}, + {"4 bytes (hex)", 4, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << std::hex << std::setw(8) << std::setfill('0') << *(uint32_t*)x).str(); + }}, + {"4 bytes (float)", 4, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(float*)x).str(); + }}, + {"8 bytes (signed)", 8, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(int64_t*)x).str(); + }}, + {"8 bytes (unsigned)", 8, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(uint64_t*)x).str(); + }}, + {"8 bytes (hex)", 8, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << std::hex << std::setw(16) << std::setfill('0') << *(uint64_t*)x).str(); + }}, + {"8 bytes (float)", 8, false, [](const uint8_t* x) -> std::string { + return (stringfmt() << *(double*)x).str(); + }} + }; + + unsigned hexaddr = 6; + int separators[5] = {6, 15, 24, 28, 42}; + const char32_t* sepchars[5] = {U"\u2502", U" ", U".", U" ", U"\u2502"}; + int hexcol[16] = {7, 9, 11, 13, 16, 18, 20, 22, 25, 27, 29, 31, 34, 36, 38, 40}; + int charcol[16] = {43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58}; + const char32_t* hexes[16] = {U"0", U"1", U"2", U"3", U"4", U"5", U"6", U"7", U"8", U"9", U"A", U"B", U"C", + U"D", U"E", U"F"}; + + enum { + wxID_OPPOSITE_ENDIAN = wxID_HIGHEST + 1, + wxID_DATATYPES_FIRST, + wxID_DATATYPES_LAST = wxID_DATATYPES_FIRST + 255, + wxID_REGIONS_FIRST, + wxID_REGIONS_LAST = wxID_REGIONS_FIRST + 255, + wxID_ADD_BOOKMARK, + wxID_DELETE_BOOKMARK, + wxID_LOAD_BOOKMARKS, + wxID_SAVE_BOOKMARKS, + wxID_BOOKMARKS_FIRST, + wxID_BOOKMARKS_LAST = wxID_BOOKMARKS_FIRST + 255, + wxID_SEARCH_DISQUALIFY, + wxID_SEARCH_PREV, + wxID_SEARCH_NEXT, + }; +} + +class wxeditor_hexedit : public wxFrame +{ +public: + wxeditor_hexedit(wxWindow* parent) + : wxFrame(parent, wxID_ANY, wxT("lsnes: Memory editor"), wxDefaultPosition, wxSize(-1, -1), + wxCAPTION | wxMINIMIZE_BOX | wxCLOSE_BOX | wxSYSTEM_MENU) + { + Centre(); + wxBoxSizer* top = new wxBoxSizer(wxVERTICAL); + SetSizer(top); + + destructing = false; + hex_input_state = -1; + + Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this); + + wxBoxSizer* parea = new wxBoxSizer(wxHORIZONTAL); + parea->Add(hpanel = new _panel(this), 1, wxGROW); + hpanel->SetFocus(); + parea->Add(scroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW); + top->Add(parea, 1, wxGROW); + scroll->Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this); + + SetStatusBar(statusbar = new wxStatusBar(this)); + SetMenuBar(menubar = new wxMenuBar); + + valuemenu = new wxMenu(); + menubar->Append(valuemenu, wxT("Value")); + regionmenu = new wxMenu(); + menubar->Append(regionmenu, wxT("Region")); + typemenu = new wxMenu(); + bookmarkmenu = new wxMenu(); + bookmarkmenu->Append(wxID_ADD_BOOKMARK, wxT("Add bookmark...")); + bookmarkmenu->Append(wxID_DELETE_BOOKMARK, wxT("Delete bookmark...")); + bookmarkmenu->AppendSeparator(); + bookmarkmenu->Append(wxID_LOAD_BOOKMARKS, wxT("Load bookmarks...")); + bookmarkmenu->Append(wxID_SAVE_BOOKMARKS, wxT("Save bookmarks...")); + bookmarkmenu->AppendSeparator(); + menubar->Append(bookmarkmenu, wxT("Bookmarks")); + valuemenu->AppendSubMenu(typemenu, wxT("Type")); + oendian = valuemenu->AppendCheckItem(wxID_OPPOSITE_ENDIAN, wxT("Little endian")); + for(size_t i = 0; i < sizeof(datatypes) / sizeof(datatypes[0]); i++) + typemenu->AppendRadioItem(wxID_DATATYPES_FIRST + i, towxstring(datatypes[i].name)); + typemenu->FindItem(wxID_DATATYPES_FIRST)->Check(); + searchmenu = new wxMenu(); + menubar->Append(searchmenu, wxT("Search")); + searchmenu->Append(wxID_SEARCH_PREV, wxT("Previous...\tCtrl+P")); + searchmenu->Append(wxID_SEARCH_NEXT, wxT("Next...\tCtrl+N")); + searchmenu->AppendSeparator(); + searchmenu->Append(wxID_SEARCH_DISQUALIFY, wxT("Disqualify...\tCtrl+D")); + set_search_status(); + + littleendian = true; + valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian); + curtype = 0; + Connect(wxID_ADD_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_addbookmark)); + Connect(wxID_DELETE_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_deletebookmark)); + Connect(wxID_LOAD_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_loadbookmarks)); + Connect(wxID_SAVE_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_savebookmarks)); + Connect(wxID_OPPOSITE_ENDIAN, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_changeendian)); + Connect(wxID_DATATYPES_FIRST, wxID_DATATYPES_LAST, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_typechange)); + Connect(wxID_REGIONS_FIRST, wxID_REGIONS_LAST, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_vmasel)); + Connect(wxID_BOOKMARKS_FIRST, wxID_BOOKMARKS_LAST, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_bookmark)); + Connect(wxID_SEARCH_DISQUALIFY, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_search_discard)); + Connect(wxID_SEARCH_PREV, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext)); + Connect(wxID_SEARCH_NEXT, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext)); + + scroll->set_page_size(hpanel->lines); + scroll->set_handler([this](scroll_bar& s) { + this->hpanel->offset = s.get_position(); + this->hpanel->request_paint(); + }); + + corechange.set(notify_core_changed, [this](bool hard) { this->on_core_changed(hard); }); + on_core_changed(true); + top->SetSizeHints(this); + Fit(); + } + ~wxeditor_hexedit() + { + destructing = true; + editor = NULL; + } + bool ShouldPreventAppExit() const + { + return false; + } + void set_search_status() + { + bool e = wxwindow_memorysearch_active(); + searchmenu->FindItem(wxID_SEARCH_DISQUALIFY)->Enable(e); + searchmenu->FindItem(wxID_SEARCH_PREV)->Enable(e); + searchmenu->FindItem(wxID_SEARCH_NEXT)->Enable(e); + } + void on_keyboard(wxKeyEvent& e) + { + int c = e.GetKeyCode(); + if(c == WXK_ESCAPE) { + hex_input_state = -1; + hpanel->request_paint(); + return; + } + if(c == WXK_LEFT && hex_input_state < 0) { + if(hpanel->seloff > 0) hpanel->seloff--; + hpanel->request_paint(); + return; + } + if(c == WXK_RIGHT && hex_input_state < 0) { + if(hpanel->seloff + 1 < hpanel->vmasize) hpanel->seloff++; + hpanel->request_paint(); + return; + } + if(c == WXK_UP && hex_input_state < 0) { + if(hpanel->seloff >= 16) hpanel->seloff -= 16; + hpanel->request_paint(); + return; + } + if(c == WXK_DOWN && hex_input_state < 0) { + if(hpanel->seloff + 16 < hpanel->vmasize) hpanel->seloff += 16; + hpanel->request_paint(); + return; + } + if(c == WXK_PAGEUP && hex_input_state < 0) { + scroll->apply_delta(-static_cast(hpanel->lines)); + hpanel->offset = scroll->get_position(); + hpanel->request_paint(); + return; + } + if(c == WXK_PAGEDOWN && hex_input_state < 0) { + scroll->apply_delta(static_cast(hpanel->lines)); + hpanel->offset = scroll->get_position(); + hpanel->request_paint(); + return; + } + if(c >= '0' && c <= '9') { + do_hex(c - '0'); + return; + } + if(c >= 'A' && c <= 'F') { + do_hex(c - 'A' + 10); + return; + } + if(c >= 'a' && c <= 'f') { + do_hex(c - 'a' + 10); + return; + } + e.Skip(); + } + void on_mouse(wxMouseEvent& e) + { + auto cell = hpanel->get_cell(); + if(e.LeftDown()) + hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true); + if(e.LeftUp()) + hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false); + unsigned speed = 1; + if(e.ShiftDown()) + speed = 10; + if(e.ShiftDown() && e.ControlDown()) + speed = 50; + scroll->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed); + hpanel->offset = scroll->get_position(); + } + void on_loadbookmarks(wxCommandEvent& e) + { + try { + std::string filename = choose_file_load(this, "Load bookmarks from file", project_otherpath(), + filetype_hexbookmarks); + auto _in = read_file_relative(filename, ""); + std::string in(_in.begin(), _in.end()); + JSON::node root(in); + std::vector newbookmarks; + for(auto i : root) { + bookmark_entry e; + e.name = i["name"].as_string8(); + e.vma = i["vma"].as_string8(); + e.scroll = i["offset"].as_int(); + e.sel = i["selected"].as_uint(); + newbookmarks.push_back(e); + } + std::swap(bookmarks, newbookmarks); + for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) { + auto p = bookmarkmenu->FindItem(i); + if(p) + bookmarkmenu->Delete(p); + } + int idx = 0; + for(auto i : bookmarks) { + if(wxID_BOOKMARKS_FIRST + idx > wxID_BOOKMARKS_LAST) + break; + bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name)); + idx++; + } + } catch(canceled_exception& e) { + } catch(std::exception& e) { + show_message_ok(this, "Error", std::string("Can't load bookmarks: ") + e.what(), + wxICON_EXCLAMATION); + return; + } + } + void on_savebookmarks(wxCommandEvent& e) + { + JSON::node root(JSON::array); + for(auto i : bookmarks) { + JSON::node n(JSON::object); + n["name"] = JSON::string(i.name); + n["vma"] = JSON::string(i.vma); + n["offset"] = JSON::number((int64_t)i.scroll); + n["selected"] = JSON::number(i.sel); + root.append(n); + } + std::string doc = root.serialize(); + try { + std::string filename = choose_file_save(this, "Save bookmarks to file", project_otherpath(), + filetype_hexbookmarks); + std::ofstream out(filename.c_str()); + out << doc << std::endl; + out.close(); + } catch(canceled_exception& e) { + } catch(std::exception& e) { + show_message_ok(this, "Error", std::string("Can't save bookmarks: ") + e.what(), + wxICON_EXCLAMATION); + } + } + void on_addbookmark(wxCommandEvent& e) + { + if(bookmarks.size() <= wxID_BOOKMARKS_LAST - wxID_BOOKMARKS_FIRST) { + std::string name = pick_text(this, "Add bookmark", "Enter name for bookmark", "", false); + bookmark_entry ent; + ent.name = name; + ent.vma = get_current_vma_name(); + ent.scroll = hpanel->offset; + ent.sel = hpanel->seloff; + int idx = bookmarks.size(); + bookmarks.push_back(ent); + bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(name)); + } else { + show_message_ok(this, "Error adding bookmark", "Too many bookmarks", wxICON_EXCLAMATION); + } + } + void on_deletebookmark(wxCommandEvent& e) + { + if(bookmarks.size() > 0) { + std::vector _choices; + for(auto i : bookmarks) + _choices.push_back(towxstring(i.name)); + wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(this, towxstring("Select bookmark " + "to delete"), towxstring("Delete bookmark"), _choices.size(), &_choices[0]); + d2->SetSelection(0); + if(d2->ShowModal() == wxID_CANCEL) { + d2->Destroy(); + return; + } + int sel = d2->GetSelection(); + d2->Destroy(); + if(sel >= 0) + bookmarks.erase(bookmarks.begin() + sel); + for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) { + auto p = bookmarkmenu->FindItem(i); + if(p) + bookmarkmenu->Delete(p); + } + int idx = 0; + for(auto i : bookmarks) { + bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name)); + idx++; + } + } + } + void rescroll_panel() + { + uint64_t vfirst = static_cast(hpanel->offset) * 16; + uint64_t vlast = static_cast(hpanel->offset + hpanel->lines) * 16; + if(hpanel->seloff < vfirst || hpanel->seloff >= vlast) { + int l = hpanel->seloff / 16; + int r = hpanel->lines / 4; + hpanel->offset = (l > r) ? (l - r) : 0; + scroll->set_position(hpanel->offset); + } + } + void on_search_discard(wxCommandEvent& e) + { + auto p = wxwindow_memorysearch_active(); + if(!p) + return; + if(hpanel->seloff < hpanel->vmasize) { + p->dq_range(hpanel->vmabase + hpanel->seloff, hpanel->vmabase + hpanel->seloff); + wxwindow_memorysearch_update(); + hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, true) - + hpanel->vmabase; + rescroll_panel(); + hpanel->request_paint(); + } + } + void on_search_prevnext(wxCommandEvent& e) + { + auto p = wxwindow_memorysearch_active(); + if(!p) + return; + if(hpanel->seloff < hpanel->vmasize) { + hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, e.GetId() == + wxID_SEARCH_NEXT) - hpanel->vmabase; + rescroll_panel(); + hpanel->request_paint(); + } + } + void on_bookmark(wxCommandEvent& e) + { + int id = e.GetId(); + if(id < wxID_BOOKMARKS_FIRST || id > wxID_BOOKMARKS_LAST) + return; + bookmark_entry ent = bookmarks[id - wxID_BOOKMARKS_FIRST]; + int r = vma_index_for_name(ent.vma); + uint64_t base = 0, size = 0; + auto i = lsnes_memory.get_regions(); + for(auto j : i) { + if(j->readonly || j->special) + continue; + if(j->name == ent.vma) { + base = j->base; + size = j->size; + } + } + if(ent.sel >= size || ent.scroll >= (size + 15) / 16) + goto invalid_bookmark; + current_vma = r; + regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma)->Check(); + update_vma(base, size); + hpanel->offset = ent.scroll; + hpanel->seloff = ent.sel; + scroll->set_position(hpanel->offset); + hpanel->request_paint(); + return; +invalid_bookmark: + show_message_ok(this, "Error jumping to bookmark", "Bookmark refers to nonexistent location", + wxICON_EXCLAMATION); + return; + } + void on_vmasel(wxCommandEvent& e) + { + if(destructing) + return; + int selected = e.GetId(); + if(selected < wxID_REGIONS_FIRST || selected > wxID_REGIONS_LAST) + return; + selected -= wxID_REGIONS_FIRST; + auto i = lsnes_memory.get_regions(); + int index = 0; + for(auto j : i) { + if(j->readonly || j->special) + continue; + if(index == selected) { + if(j->base != hpanel->vmabase || j->size != hpanel->vmasize); + update_vma(j->base, j->size); + current_vma = index; + if(vma_endians.count(index)) { + littleendian = vma_endians[index]; + valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian); + } + return; + } + index++; + } + current_vma = index; + update_vma(0, 0); + } + bool is_endian_little(int endian) + { + if(endian < 0) return true; + if(endian > 0) return false; + uint16_t magic = 1; + return (*reinterpret_cast(&magic) == 1); + } + void update_vma(uint64_t base, uint64_t size) + { + hpanel->vmabase = base; + hpanel->vmasize = size; + hpanel->offset = 0; + hpanel->seloff = 0; + scroll->set_range((size + 15) / 16); + scroll->set_position(0); + hpanel->request_paint(); + } + void on_typechange(wxCommandEvent& e) + { + if(destructing) + return; + int id = e.GetId(); + if(id < wxID_DATATYPES_FIRST || id > wxID_DATATYPES_LAST) + return; + curtype = id - wxID_DATATYPES_FIRST; + hpanel->request_paint(); + } + void on_changeendian(wxCommandEvent& e) + { + if(destructing) + return; + littleendian = valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->IsChecked(); + if(vma_endians.count(current_vma)) + vma_endians[current_vma] = littleendian; + hpanel->request_paint(); + } + void updated() + { + if(destructing) + return; + hpanel->request_paint(); + } + void refresh_curvalue() + { + uint8_t buf[maxvaluelen]; + memcpy(buf, hpanel->value, maxvaluelen); + val_type vt = datatypes[curtype]; + if(littleendian != is_endian_little(vt.hard_bigendian ? 1 : 0)) + for(unsigned i = 0; i < vt.len / 2; i++) + std::swap(buf[i], buf[vt.len - i - 1]); + wxMenuItem* it = regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma); + std::string vma = "(none)"; + if(it) vma = tostdstring(it->GetItemLabelText()); + unsigned addrlen = 1; + while(hpanel->vmasize > (1 << (4 * addrlen))) + addrlen++; + std::string addr = (stringfmt() << std::hex << std::setw(addrlen) << std::setfill('0') << + hpanel->seloff).str(); + std::string vtext = vt.read(buf); + statusbar->SetStatusText(towxstring("Region: " + vma + " Address: " + addr + " Value: " + vtext)); + } + int vma_index_for_name(const std::string& x) + { + for(size_t i = 0; i < vma_names.size(); i++) + if(vma_names[i] == x) + return i; + return -1; + } + std::string get_current_vma_name() + { + if(current_vma >= vma_names.size()) + return ""; + return vma_names[current_vma]; + } + void on_core_changed(bool _hard) + { + if(destructing) + return; + bool hard = _hard; + runuifun([this, hard]() { + for(unsigned i = wxID_REGIONS_FIRST; i <= wxID_REGIONS_LAST; i++) { + auto p = regionmenu->FindItem(i); + if(p) + regionmenu->Delete(p); + } + std::string current_reg = get_current_vma_name(); + uint64_t nsbase = 0, nssize = 0; + auto i = lsnes_memory.get_regions(); + vma_names.clear(); + if(hard) + vma_endians.clear(); + int index = 0; + int curreg_index = 0; + for(auto j : i) { + if(j->readonly || j->special) + continue; + regionmenu->AppendRadioItem(wxID_REGIONS_FIRST + index, towxstring(j->name)); + vma_names.push_back(j->name); + if(j->name == current_reg || index == 0) { + curreg_index = index; + nsbase = j->base; + nssize = j->size; + } + if(!vma_endians.count(index)) + vma_endians[index] = is_endian_little(j->endian); + index++; + } + if(!index) { + update_vma(0, 0); + return; + } + regionmenu->FindItem(wxID_REGIONS_FIRST + curreg_index)->Check(); + current_vma = curreg_index; + if(vma_endians.count(current_vma)) { + littleendian = vma_endians[current_vma]; + typemenu->FindItem(wxID_DATATYPES_FIRST)->Check(littleendian); + } + if(nsbase != hpanel->vmabase || nssize != hpanel->vmasize) + update_vma(nsbase, nssize); + }); + hpanel->request_paint(); + } + void do_hex(int hex) + { + if(hpanel->seloff > hpanel->vmasize) + return; + if(hex_input_state < 0) + hex_input_state = hex; + else { + uint8_t byte = hex_input_state * 16 + hex; + uint64_t addr = hpanel->vmabase + hpanel->seloff; + hex_input_state = -1; + if(hpanel->seloff + 1 < hpanel->vmasize) + hpanel->seloff++; + runemufn([addr, byte]() {lsnes_memory.write(addr, byte); }); + } + hpanel->request_paint(); + } + class _panel : public text_framebuffer_panel + { + public: + _panel(wxeditor_hexedit* parent) + : text_framebuffer_panel(parent, 59, lines = 28, wxID_ANY, NULL) + { + rparent = parent; + vmabase = 0; + vmasize = 0; + offset = 0; + seloff = 0; + memset(value, 0, maxvaluelen); + clear(); + Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent); + Connect(wxEVT_LEFT_UP, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent); + Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent); + Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, parent); + request_paint(); + } + void prepare_paint() + { + uint64_t paint_offset = static_cast(offset) * 16; + uint64_t _vmabase = vmabase; + uint64_t _vmasize = vmasize; + uint64_t _seloff = seloff; + int _lines = lines; + uint8_t* _value = value; + runemufn([_vmabase, _vmasize, paint_offset, _seloff, _value, _lines, this]() { + memory_search* memsearch = wxwindow_memorysearch_active(); + //Paint the stuff + for(size_t j = 0; j < _lines; j++) { + uint64_t addr = paint_offset + j * 16; + if(addr >= _vmasize) { + //Past-the-end. + for(size_t i = 0; i < get_characters().first; i++) + write(" ", 1, i, j, 0, 0xFFFFFF); + continue; + } + for(size_t i = 0; i < sizeof(separators)/sizeof(separators[0]); i++) { + write(sepchars[i], 1, separators[i], j, 0, 0xFFFFFF); + } + for(size_t i = 0; i < hexaddr; i++) { + write(hexes[(addr >> 4 * (hexaddr - i - 1)) & 15], 1, i, j, 0, + 0xFFFFFF); + } + size_t bytes = 16; + if(_vmasize - addr < 16) + bytes = _vmasize - addr; + uint64_t laddr = addr + _vmabase; + for(size_t i = 0; i < bytes; i++) { + uint32_t fg = 0; + uint32_t bg = 0xFFFFFF; + bool candidate = (memsearch && memsearch->is_candidate(laddr + i)); + if(candidate) bg = bg & 0xC0C0C0 | 0x3F0000; + if(addr + i == _seloff) + std::swap(fg, bg); + uint8_t b = lsnes_memory.read(laddr + i); + if(rparent->hex_input_state < 0 || addr + i != seloff + ) + write(hexes[(b >> 4) & 15], 1, hexcol[i], j, fg, bg); + else + write(hexes[rparent->hex_input_state], 1, hexcol[i], j, 0xFF, + 0); + write(hexes[b & 15], 1, hexcol[i] + 1, j, fg, bg); + char32_t buf[2] = {0, 0}; + buf[0] = byte_to_char(b); + write(buf, 1, charcol[i], j, fg, bg); + } + for(size_t i = bytes; i < 16; i++) { + write(" ", 2, hexcol[i], j, 0, 0xFFFFFF); + write(" ", 1, hexcol[i] + 1, j, 0, 0xFFFFFF); + } + } + memset(_value, 0, maxvaluelen); + lsnes_memory.read_range(_vmabase + _seloff, _value, maxvaluelen); + }); + rparent->refresh_curvalue(); + rparent->set_search_status(); + } + char32_t byte_to_char(uint8_t ch) + { + if(ch == 160) + return U' '; + if((ch & 0x60) == 0 || ch == 127 || ch == 0xad) + return U'.'; + return ch; + } + void on_mouse0(int x, int y, bool polarity) + { + if(!polarity) + return; + uint64_t rowaddr = 16 * (static_cast(offset) + y); + int coladdr = 16; + for(unsigned i = 0; i < 16; i++) + if(x == hexcol[i] || x == hexcol[i] + 1 || x == charcol[i]) + coladdr = i; + if(rowaddr + coladdr >= vmasize || coladdr > 15) + return; + seloff = rowaddr + coladdr; + request_paint(); + } + wxeditor_hexedit* rparent; + int offset; + uint64_t vmabase; + uint64_t vmasize; + uint64_t seloff; + uint8_t value[maxvaluelen]; + int lines; + }; +private: + struct bookmark_entry + { + std::string name; + std::string vma; + int scroll; + uint64_t sel; + }; + wxMenu* regionmenu; + wxMenu* bookmarkmenu; + wxMenu* searchmenu; + wxComboBox* datatype; + wxMenuItem* oendian; + wxStatusBar* statusbar; + wxMenuBar* menubar; + scroll_bar* scroll; + _panel* hpanel; + wxMenu* valuemenu; + wxMenu* typemenu; + struct dispatch_target corechange; + unsigned current_vma; + std::vector vma_names; + std::map vma_endians; + std::vector bookmarks; + bool destructing; + unsigned curtype; + bool littleendian; + int hex_input_state; +}; + +void wxeditor_hexedit_display(wxWindow* parent) +{ + if(editor) + return; + try { + editor = new wxeditor_hexedit(parent); + editor->Show(); + } catch(...) { + } +} + +void wxeditor_hexeditor_update() +{ + if(editor) + editor->updated(); +} diff --git a/src/platform/wxwidgets/loadsave.cpp b/src/platform/wxwidgets/loadsave.cpp index e6f2cf4f..9b13943f 100644 --- a/src/platform/wxwidgets/loadsave.cpp +++ b/src/platform/wxwidgets/loadsave.cpp @@ -98,4 +98,5 @@ single_type filetype_commentary("lsvs", "Commentary track"); 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"); diff --git a/src/platform/wxwidgets/main.cpp b/src/platform/wxwidgets/main.cpp index cb219ae3..666ada8c 100644 --- a/src/platform/wxwidgets/main.cpp +++ b/src/platform/wxwidgets/main.cpp @@ -123,6 +123,7 @@ namespace if(main_window) main_window->notify_update_status(); wxeditor_movie_update(); + wxeditor_hexeditor_update(); } else if(c == UISERV_UPDATE_SCREEN) { if(main_window) main_window->notify_update(); diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index 444d0e9b..e668c3be 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -121,6 +121,7 @@ enum wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256, wxID_SETTINGS_FIRST, wxID_SETTINGS_LAST = wxID_SETTINGS_FIRST + 256, + wxID_HEXEDITOR, }; @@ -1021,6 +1022,7 @@ wxwin_mainwindow::wxwin_mainwindow() menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch...")); menu_separator(); menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search...")); + menu_entry(wxID_HEXEDITOR, wxT("Memory editor...")); menu_separator(); menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie...")); menu_separator(); @@ -1572,6 +1574,9 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) case wxID_LOAD_ROM_IMAGE_FIRST: do_load_rom_image(NULL); return; + case wxID_HEXEDITOR: + wxeditor_hexedit_display(this); + return; }; } diff --git a/src/platform/wxwidgets/memorysearch.cpp b/src/platform/wxwidgets/memorysearch.cpp index 1739e8e6..605ca5c2 100644 --- a/src/platform/wxwidgets/memorysearch.cpp +++ b/src/platform/wxwidgets/memorysearch.cpp @@ -31,6 +31,7 @@ #define CANDIDATE_LIMIT 512 class wxwindow_memorysearch; +memory_search* wxwindow_memorysearch_active(); namespace { @@ -252,6 +253,7 @@ public: template void search_0(); template void search_1(); private: + friend memory_search* wxwindow_memorysearch_active(); friend class panel; template T promptvalue(bool& bad); void update(); @@ -801,6 +803,7 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e) 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()); + wxeditor_hexeditor_update(); } else if(id == wxID_UPDATE) { update(); } else if(id == wxID_TYPESELECT) { @@ -848,6 +851,7 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e) runemufn([addr, ms]() { ms->dq_range(addr, addr); }); } matches->set_selection(0, 0); + wxeditor_hexeditor_update(); } else if(id == wxID_SET_REGIONS) { wxwindow_memorysearch_vmasel* d = new wxwindow_memorysearch_vmasel(this, vmas_enabled); if(d->ShowModal() == wxID_OK) @@ -856,9 +860,11 @@ void wxwindow_memorysearch::on_button_click(wxCommandEvent& e) 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()); + wxeditor_hexeditor_update(); } else if(id >= wxID_BUTTONS_BASE && id < wxID_BUTTONS_BASE + (sizeof(searchtbl)/sizeof(searchtbl[0]))) { int button = id - wxID_BUTTONS_BASE; (this->*(searchtbl[button].searches[typecode]))(); + wxeditor_hexeditor_update(); } update(); } @@ -914,3 +920,11 @@ void wxwindow_memorysearch_update() if(mwatch) mwatch->auto_update(); } + +memory_search* wxwindow_memorysearch_active() +{ + if(mwatch) + return mwatch->msearch; + else + return NULL; +} \ No newline at end of file diff --git a/src/platform/wxwidgets/scrollbar.cpp b/src/platform/wxwidgets/scrollbar.cpp index 4cfc94df..2ea3664d 100644 --- a/src/platform/wxwidgets/scrollbar.cpp +++ b/src/platform/wxwidgets/scrollbar.cpp @@ -67,6 +67,8 @@ void scroll_bar::apply_delta(int delta) if(newscroll > range) newscroll = (delta < 0) ? 0 : maxscroll; position = newscroll; + if(position > maxscroll) + position = maxscroll; if(range > pagesize) SetScrollbar(position, pagesize, range, max(pagesize - 1, 1U)); else -- 2.11.4.GIT