Refactor library dispatch stuff to dedicated namespace
[lsnes.git] / src / platform / wxwidgets / editor-hexedit.cpp
blob372ef66f0e6bb0800592479b6ccde3723f13420e
1 #include "core/moviedata.hpp"
2 #include "core/memorywatch.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/project.hpp"
5 #include "core/memorymanip.hpp"
6 #include "library/memorysearch.hpp"
8 #include "platform/wxwidgets/platform.hpp"
9 #include "platform/wxwidgets/textrender.hpp"
10 #include "platform/wxwidgets/loadsave.hpp"
11 #include "platform/wxwidgets/scrollbar.hpp"
13 #include <wx/wx.h>
14 #include <wx/event.h>
15 #include <wx/control.h>
16 #include <wx/combobox.h>
17 #include <wx/radiobut.h>
19 #include "library/string.hpp"
20 #include "library/json.hpp"
21 #include "library/zip.hpp"
22 #include "interface/romtype.hpp"
24 #include <iomanip>
26 class wxeditor_hexedit;
28 namespace
30 const size_t maxvaluelen = 8; //The length of longest value type.
31 wxeditor_hexedit* editor;
33 struct val_type
35 const char* name;
36 unsigned len;
37 bool hard_bigendian;
38 const char* watch;
39 std::string (*read)(const uint8_t* x);
42 val_type datatypes[] = {
43 {"1 byte (signed)", 1, false, "b", [](const uint8_t* x) -> std::string {
44 return (stringfmt() << (int)(char)x[0]).str();
45 }},
46 {"1 byte (unsigned)", 1, false, "B", [](const uint8_t* x) -> std::string {
47 return (stringfmt() << (int)x[0]).str();
48 }},
49 {"1 byte (hex)", 1, false, "BH2", [](const uint8_t* x) -> std::string {
50 return (stringfmt() << std::hex << std::setw(2) << std::setfill('0') << (int)x[0]).str();
51 }},
52 {"2 bytes (signed)", 2, false, "w", [](const uint8_t* x) -> std::string {
53 return (stringfmt() << *(int16_t*)x).str();
54 }},
55 {"2 bytes (unsigned)", 2, false, "W", [](const uint8_t* x) -> std::string {
56 return (stringfmt() << *(uint16_t*)x).str();
57 }},
58 {"2 bytes (hex)", 2, false, "WH4", [](const uint8_t* x) -> std::string {
59 return (stringfmt() << std::hex << std::setw(4) << std::setfill('0') << *(uint16_t*)x).str();
60 }},
61 {"3 bytes (signed)", 3, true, "o", [](const uint8_t* x) -> std::string {
62 int32_t a = 0;
63 a |= (uint32_t)x[0] << 16;
64 a |= (uint32_t)x[1] << 8;
65 a |= (uint32_t)x[2];
66 if(a & 0x800000)
67 a -= 0x1000000;
68 return (stringfmt() << a).str();
69 }},
70 {"3 bytes (unsigned)", 3, true, "O", [](const uint8_t* x) -> std::string {
71 int32_t a = 0;
72 a |= (uint32_t)x[0] << 16;
73 a |= (uint32_t)x[1] << 8;
74 a |= (uint32_t)x[2];
75 return (stringfmt() << a).str();
76 }},
77 {"3 bytes (hex)", 3, true, "OH6", [](const uint8_t* x) -> std::string {
78 int32_t a = 0;
79 a |= (uint32_t)x[0] << 16;
80 a |= (uint32_t)x[1] << 8;
81 a |= (uint32_t)x[2];
82 return (stringfmt() << std::hex << std::setw(6) << std::setfill('0') << a).str();
83 }},
84 {"4 bytes (signed)", 4, false, "d", [](const uint8_t* x) -> std::string {
85 return (stringfmt() << *(int32_t*)x).str();
86 }},
87 {"4 bytes (unsigned)", 4, false, "D", [](const uint8_t* x) -> std::string {
88 return (stringfmt() << *(uint32_t*)x).str();
89 }},
90 {"4 bytes (hex)", 4, false, "DH8", [](const uint8_t* x) -> std::string {
91 return (stringfmt() << std::hex << std::setw(8) << std::setfill('0') << *(uint32_t*)x).str();
92 }},
93 {"4 bytes (float)", 4, false, "f", [](const uint8_t* x) -> std::string {
94 return (stringfmt() << *(float*)x).str();
95 }},
96 {"8 bytes (signed)", 8, false, "q", [](const uint8_t* x) -> std::string {
97 return (stringfmt() << *(int64_t*)x).str();
98 }},
99 {"8 bytes (unsigned)", 8, false, "Q", [](const uint8_t* x) -> std::string {
100 return (stringfmt() << *(uint64_t*)x).str();
102 {"8 bytes (hex)", 8, false, "QHG", [](const uint8_t* x) -> std::string {
103 return (stringfmt() << std::hex << std::setw(16) << std::setfill('0') << *(uint64_t*)x).str();
105 {"8 bytes (float)", 8, false, "F", [](const uint8_t* x) -> std::string {
106 return (stringfmt() << *(double*)x).str();
108 {"Q8.8 (signed)", 2, false, "C256z$w/", [](const uint8_t* x) -> std::string {
109 return (stringfmt() << *(int16_t*)x / 256.0).str();
111 {"Q8.8 (unsigned)", 2, false, "C256z$W/", [](const uint8_t* x) -> std::string {
112 return (stringfmt() << *(uint16_t*)x / 256.0).str();
114 {"Q12.4 (signed)", 2, false, "C16z$w/", [](const uint8_t* x) -> std::string {
115 return (stringfmt() << *(int16_t*)x / 16.0).str();
117 {"Q12.4 (unsigned)", 2, false, "C16z$W/", [](const uint8_t* x) -> std::string {
118 return (stringfmt() << *(uint16_t*)x / 16.0).str();
120 {"Q16.8 (signed)", 3, false, "C256z$o/", [](const uint8_t* x) -> std::string {
121 int32_t a = 0;
122 a |= (uint32_t)x[0] << 16;
123 a |= (uint32_t)x[1] << 8;
124 a |= (uint32_t)x[2];
125 if(a & 0x800000)
126 a -= 0x1000000;
127 return (stringfmt() << a / 256.0).str();
129 {"Q16.8 (unsigned)", 3, false, "C256z$O/", [](const uint8_t* x) -> std::string {
130 int32_t a = 0;
131 a |= (uint32_t)x[0] << 16;
132 a |= (uint32_t)x[1] << 8;
133 a |= (uint32_t)x[2];
134 return (stringfmt() << a / 256.0).str();
136 {"Q24.8 (signed)", 4, false, "C256z$d/", [](const uint8_t* x) -> std::string {
137 return (stringfmt() << *(int32_t*)x / 256.0).str();
139 {"Q24.8 (unsigned)", 4, false, "C256z$D/", [](const uint8_t* x) -> std::string {
140 return (stringfmt() << *(uint32_t*)x / 256.0).str();
142 {"Q20.12 (signed)", 4, false, "C4096z$d/", [](const uint8_t* x) -> std::string {
143 return (stringfmt() << *(int32_t*)x / 4096.0).str();
145 {"Q20.12 (unsigned)", 4, false, "C4096z$D/", [](const uint8_t* x) -> std::string {
146 return (stringfmt() << *(uint32_t*)x / 4096.0).str();
148 {"Q16.16 (signed)", 4, false, "C65536z$d/", [](const uint8_t* x) -> std::string {
149 return (stringfmt() << *(int32_t*)x / 65536.0).str();
151 {"Q16.16 (unsigned)", 4, false, "C65536z$D/", [](const uint8_t* x) -> std::string {
152 return (stringfmt() << *(uint32_t*)x / 65536.0).str();
156 unsigned hexaddr = 6;
157 int separators[5] = {6, 15, 24, 28, 42};
158 const char32_t* sepchars[5] = {U"\u2502", U" ", U".", U" ", U"\u2502"};
159 int hexcol[16] = {7, 9, 11, 13, 16, 18, 20, 22, 25, 27, 29, 31, 34, 36, 38, 40};
160 int charcol[16] = {43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58};
161 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",
162 U"D", U"E", U"F"};
164 enum {
165 wxID_OPPOSITE_ENDIAN = wxID_HIGHEST + 1,
166 wxID_DATATYPES_FIRST,
167 wxID_DATATYPES_LAST = wxID_DATATYPES_FIRST + 255,
168 wxID_REGIONS_FIRST,
169 wxID_REGIONS_LAST = wxID_REGIONS_FIRST + 255,
170 wxID_ADD_BOOKMARK,
171 wxID_DELETE_BOOKMARK,
172 wxID_LOAD_BOOKMARKS,
173 wxID_SAVE_BOOKMARKS,
174 wxID_BOOKMARKS_FIRST,
175 wxID_BOOKMARKS_LAST = wxID_BOOKMARKS_FIRST + 255,
176 wxID_SEARCH_DISQUALIFY,
177 wxID_SEARCH_PREV,
178 wxID_SEARCH_NEXT,
179 wxID_SEARCH_WATCH,
183 class wxeditor_hexedit : public wxFrame
185 public:
186 wxeditor_hexedit(wxWindow* parent)
187 : wxFrame(parent, wxID_ANY, wxT("lsnes: Memory editor"), wxDefaultPosition, wxSize(-1, -1),
188 wxCAPTION | wxMINIMIZE_BOX | wxCLOSE_BOX | wxSYSTEM_MENU)
190 Centre();
191 wxBoxSizer* top = new wxBoxSizer(wxVERTICAL);
192 SetSizer(top);
194 destructing = false;
195 hex_input_state = -1;
197 Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this);
199 wxBoxSizer* parea = new wxBoxSizer(wxHORIZONTAL);
200 parea->Add(hpanel = new _panel(this), 1, wxGROW);
201 hpanel->SetFocus();
202 parea->Add(scroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
203 top->Add(parea, 1, wxGROW);
204 scroll->Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this);
206 SetStatusBar(statusbar = new wxStatusBar(this));
207 SetMenuBar(menubar = new wxMenuBar);
209 valuemenu = new wxMenu();
210 menubar->Append(valuemenu, wxT("Value"));
211 regionmenu = new wxMenu();
212 menubar->Append(regionmenu, wxT("Region"));
213 typemenu = new wxMenu();
214 bookmarkmenu = new wxMenu();
215 bookmarkmenu->Append(wxID_ADD_BOOKMARK, wxT("Add bookmark..."));
216 bookmarkmenu->Append(wxID_DELETE_BOOKMARK, wxT("Delete bookmark..."));
217 bookmarkmenu->AppendSeparator();
218 bookmarkmenu->Append(wxID_LOAD_BOOKMARKS, wxT("Load bookmarks..."));
219 bookmarkmenu->Append(wxID_SAVE_BOOKMARKS, wxT("Save bookmarks..."));
220 bookmarkmenu->AppendSeparator();
221 menubar->Append(bookmarkmenu, wxT("Bookmarks"));
222 valuemenu->AppendSubMenu(typemenu, wxT("Type"));
223 oendian = valuemenu->AppendCheckItem(wxID_OPPOSITE_ENDIAN, wxT("Little endian"));
224 for(size_t i = 0; i < sizeof(datatypes) / sizeof(datatypes[0]); i++)
225 typemenu->AppendRadioItem(wxID_DATATYPES_FIRST + i, towxstring(datatypes[i].name));
226 typemenu->FindItem(wxID_DATATYPES_FIRST)->Check();
227 searchmenu = new wxMenu();
228 menubar->Append(searchmenu, wxT("Search"));
229 searchmenu->Append(wxID_SEARCH_PREV, wxT("Previous...\tCtrl+P"));
230 searchmenu->Append(wxID_SEARCH_NEXT, wxT("Next...\tCtrl+N"));
231 searchmenu->Append(wxID_SEARCH_WATCH, wxT("Add watch...\tCtrl+W"));
232 searchmenu->AppendSeparator();
233 searchmenu->Append(wxID_SEARCH_DISQUALIFY, wxT("Disqualify...\tCtrl+D"));
234 set_search_status();
236 littleendian = true;
237 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
238 curtype = 0;
239 Connect(wxID_ADD_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED,
240 wxCommandEventHandler(wxeditor_hexedit::on_addbookmark));
241 Connect(wxID_DELETE_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED,
242 wxCommandEventHandler(wxeditor_hexedit::on_deletebookmark));
243 Connect(wxID_LOAD_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED,
244 wxCommandEventHandler(wxeditor_hexedit::on_loadbookmarks));
245 Connect(wxID_SAVE_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED,
246 wxCommandEventHandler(wxeditor_hexedit::on_savebookmarks));
247 Connect(wxID_OPPOSITE_ENDIAN, wxEVT_COMMAND_MENU_SELECTED,
248 wxCommandEventHandler(wxeditor_hexedit::on_changeendian));
249 Connect(wxID_DATATYPES_FIRST, wxID_DATATYPES_LAST, wxEVT_COMMAND_MENU_SELECTED,
250 wxCommandEventHandler(wxeditor_hexedit::on_typechange));
251 Connect(wxID_REGIONS_FIRST, wxID_REGIONS_LAST, wxEVT_COMMAND_MENU_SELECTED,
252 wxCommandEventHandler(wxeditor_hexedit::on_vmasel));
253 Connect(wxID_BOOKMARKS_FIRST, wxID_BOOKMARKS_LAST, wxEVT_COMMAND_MENU_SELECTED,
254 wxCommandEventHandler(wxeditor_hexedit::on_bookmark));
255 Connect(wxID_SEARCH_DISQUALIFY, wxEVT_COMMAND_MENU_SELECTED,
256 wxCommandEventHandler(wxeditor_hexedit::on_search_discard));
257 Connect(wxID_SEARCH_PREV, wxEVT_COMMAND_MENU_SELECTED,
258 wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext));
259 Connect(wxID_SEARCH_NEXT, wxEVT_COMMAND_MENU_SELECTED,
260 wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext));
261 Connect(wxID_SEARCH_WATCH, wxEVT_COMMAND_MENU_SELECTED,
262 wxCommandEventHandler(wxeditor_hexedit::on_search_watch));
264 scroll->set_page_size(hpanel->lines);
265 scroll->set_handler([this](scroll_bar& s) {
266 this->hpanel->offset = s.get_position();
267 this->hpanel->request_paint();
270 corechange.set(notify_core_changed, [this](bool hard) { this->on_core_changed(hard); });
271 on_core_changed(true);
272 top->SetSizeHints(this);
273 Fit();
275 ~wxeditor_hexedit()
277 destructing = true;
278 editor = NULL;
280 bool ShouldPreventAppExit() const
282 return false;
284 void set_search_status()
286 bool e = wxwindow_memorysearch_active();
287 searchmenu->FindItem(wxID_SEARCH_DISQUALIFY)->Enable(e);
288 searchmenu->FindItem(wxID_SEARCH_PREV)->Enable(e);
289 searchmenu->FindItem(wxID_SEARCH_NEXT)->Enable(e);
291 void on_keyboard(wxKeyEvent& e)
293 int c = e.GetKeyCode();
294 if(c == WXK_ESCAPE) {
295 hex_input_state = -1;
296 hpanel->request_paint();
297 return;
299 if(c == WXK_LEFT && hex_input_state < 0) {
300 if(hpanel->seloff > 0) hpanel->seloff--;
301 hpanel->request_paint();
302 return;
304 if(c == WXK_RIGHT && hex_input_state < 0) {
305 if(hpanel->seloff + 1 < hpanel->vmasize) hpanel->seloff++;
306 hpanel->request_paint();
307 return;
309 if(c == WXK_UP && hex_input_state < 0) {
310 if(hpanel->seloff >= 16) hpanel->seloff -= 16;
311 hpanel->request_paint();
312 return;
314 if(c == WXK_DOWN && hex_input_state < 0) {
315 if(hpanel->seloff + 16 < hpanel->vmasize) hpanel->seloff += 16;
316 hpanel->request_paint();
317 return;
319 if(c == WXK_PAGEUP && hex_input_state < 0) {
320 scroll->apply_delta(-static_cast<int>(hpanel->lines));
321 hpanel->offset = scroll->get_position();
322 hpanel->request_paint();
323 return;
325 if(c == WXK_PAGEDOWN && hex_input_state < 0) {
326 scroll->apply_delta(static_cast<int>(hpanel->lines));
327 hpanel->offset = scroll->get_position();
328 hpanel->request_paint();
329 return;
331 if(c >= '0' && c <= '9') {
332 do_hex(c - '0');
333 return;
335 if(c >= 'A' && c <= 'F') {
336 do_hex(c - 'A' + 10);
337 return;
339 if(c >= 'a' && c <= 'f') {
340 do_hex(c - 'a' + 10);
341 return;
343 e.Skip();
345 void on_mouse(wxMouseEvent& e)
347 auto cell = hpanel->get_cell();
348 if(e.LeftDown())
349 hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true);
350 if(e.LeftUp())
351 hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false);
352 unsigned speed = 1;
353 if(e.ShiftDown())
354 speed = 10;
355 if(e.ShiftDown() && e.ControlDown())
356 speed = 50;
357 scroll->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
358 hpanel->offset = scroll->get_position();
360 void on_loadbookmarks(wxCommandEvent& e)
362 try {
363 std::string filename = choose_file_load(this, "Load bookmarks from file", project_otherpath(),
364 filetype_hexbookmarks);
365 auto _in = read_file_relative(filename, "");
366 std::string in(_in.begin(), _in.end());
367 JSON::node root(in);
368 std::vector<bookmark_entry> newbookmarks;
369 for(auto i : root) {
370 bookmark_entry e;
371 e.name = i["name"].as_string8();
372 e.vma = i["vma"].as_string8();
373 e.scroll = i["offset"].as_int();
374 e.sel = i["selected"].as_uint();
375 newbookmarks.push_back(e);
377 std::swap(bookmarks, newbookmarks);
378 for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) {
379 auto p = bookmarkmenu->FindItem(i);
380 if(p)
381 bookmarkmenu->Delete(p);
383 int idx = 0;
384 for(auto i : bookmarks) {
385 if(wxID_BOOKMARKS_FIRST + idx > wxID_BOOKMARKS_LAST)
386 break;
387 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name));
388 idx++;
390 } catch(canceled_exception& e) {
391 } catch(std::exception& e) {
392 show_message_ok(this, "Error", std::string("Can't load bookmarks: ") + e.what(),
393 wxICON_EXCLAMATION);
394 return;
397 void on_savebookmarks(wxCommandEvent& e)
399 JSON::node root(JSON::array);
400 for(auto i : bookmarks) {
401 JSON::node n(JSON::object);
402 n["name"] = JSON::string(i.name);
403 n["vma"] = JSON::string(i.vma);
404 n["offset"] = JSON::number((int64_t)i.scroll);
405 n["selected"] = JSON::number(i.sel);
406 root.append(n);
408 std::string doc = root.serialize();
409 try {
410 std::string filename = choose_file_save(this, "Save bookmarks to file", project_otherpath(),
411 filetype_hexbookmarks);
412 std::ofstream out(filename.c_str());
413 out << doc << std::endl;
414 out.close();
415 } catch(canceled_exception& e) {
416 } catch(std::exception& e) {
417 show_message_ok(this, "Error", std::string("Can't save bookmarks: ") + e.what(),
418 wxICON_EXCLAMATION);
421 void on_addbookmark(wxCommandEvent& e)
423 if(bookmarks.size() <= wxID_BOOKMARKS_LAST - wxID_BOOKMARKS_FIRST) {
424 std::string name = pick_text(this, "Add bookmark", "Enter name for bookmark", "", false);
425 bookmark_entry ent;
426 ent.name = name;
427 ent.vma = get_current_vma_name();
428 ent.scroll = hpanel->offset;
429 ent.sel = hpanel->seloff;
430 int idx = bookmarks.size();
431 bookmarks.push_back(ent);
432 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(name));
433 } else {
434 show_message_ok(this, "Error adding bookmark", "Too many bookmarks", wxICON_EXCLAMATION);
437 void on_deletebookmark(wxCommandEvent& e)
439 if(bookmarks.size() > 0) {
440 std::vector<wxString> _choices;
441 for(auto i : bookmarks)
442 _choices.push_back(towxstring(i.name));
443 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(this, towxstring("Select bookmark "
444 "to delete"), towxstring("Delete bookmark"), _choices.size(), &_choices[0]);
445 d2->SetSelection(0);
446 if(d2->ShowModal() == wxID_CANCEL) {
447 d2->Destroy();
448 return;
450 int sel = d2->GetSelection();
451 d2->Destroy();
452 if(sel >= 0)
453 bookmarks.erase(bookmarks.begin() + sel);
454 for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) {
455 auto p = bookmarkmenu->FindItem(i);
456 if(p)
457 bookmarkmenu->Delete(p);
459 int idx = 0;
460 for(auto i : bookmarks) {
461 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name));
462 idx++;
466 void rescroll_panel()
468 uint64_t vfirst = static_cast<uint64_t>(hpanel->offset) * 16;
469 uint64_t vlast = static_cast<uint64_t>(hpanel->offset + hpanel->lines) * 16;
470 if(hpanel->seloff < vfirst || hpanel->seloff >= vlast) {
471 int l = hpanel->seloff / 16;
472 int r = hpanel->lines / 4;
473 hpanel->offset = (l > r) ? (l - r) : 0;
474 scroll->set_position(hpanel->offset);
477 void on_search_discard(wxCommandEvent& e)
479 auto p = wxwindow_memorysearch_active();
480 if(!p)
481 return;
482 if(hpanel->seloff < hpanel->vmasize) {
483 p->dq_range(hpanel->vmabase + hpanel->seloff, hpanel->vmabase + hpanel->seloff);
484 wxwindow_memorysearch_update();
485 hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, true) -
486 hpanel->vmabase;
487 rescroll_panel();
488 hpanel->request_paint();
491 void on_search_watch(wxCommandEvent& e)
493 try {
494 if(!hpanel->vmasize)
495 return;
496 uint64_t addr = hpanel->vmabase + hpanel->seloff;
497 std::string n = pick_text(this, "Name for watch", (stringfmt()
498 << "Enter name for watch at 0x" << std::hex << addr << ":").str());
499 if(n == "")
500 return;
501 std::string wch = datatypes[curtype].watch;
502 size_t sz = wch.find_first_of("$");
503 std::string e;
504 if(sz < wch.length())
505 e = (stringfmt() << wch.substr(0, sz) << "C0x" << std::hex << addr << "z"
506 << wch.substr(sz + 1)).str();
507 else
508 e = (stringfmt() << "C0x" << std::hex << addr << "z" << wch).str();
509 runemufn([n, e]() { set_watchexpr_for(n, e); });
510 } catch(canceled_exception& e) {
513 void on_search_prevnext(wxCommandEvent& e)
515 auto p = wxwindow_memorysearch_active();
516 if(!p)
517 return;
518 if(hpanel->seloff < hpanel->vmasize) {
519 hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, e.GetId() ==
520 wxID_SEARCH_NEXT) - hpanel->vmabase;
521 rescroll_panel();
522 hpanel->request_paint();
525 void on_bookmark(wxCommandEvent& e)
527 int id = e.GetId();
528 if(id < wxID_BOOKMARKS_FIRST || id > wxID_BOOKMARKS_LAST)
529 return;
530 bookmark_entry ent = bookmarks[id - wxID_BOOKMARKS_FIRST];
531 int r = vma_index_for_name(ent.vma);
532 uint64_t base = 0, size = 0;
533 auto i = lsnes_memory.get_regions();
534 for(auto j : i) {
535 if(j->readonly || j->special)
536 continue;
537 if(j->name == ent.vma) {
538 base = j->base;
539 size = j->size;
542 if(ent.sel >= size || ent.scroll >= (size + 15) / 16)
543 goto invalid_bookmark;
544 current_vma = r;
545 regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma)->Check();
546 update_vma(base, size);
547 hpanel->offset = ent.scroll;
548 hpanel->seloff = ent.sel;
549 scroll->set_position(hpanel->offset);
550 hpanel->request_paint();
551 return;
552 invalid_bookmark:
553 show_message_ok(this, "Error jumping to bookmark", "Bookmark refers to nonexistent location",
554 wxICON_EXCLAMATION);
555 return;
557 void on_vmasel(wxCommandEvent& e)
559 if(destructing)
560 return;
561 int selected = e.GetId();
562 if(selected < wxID_REGIONS_FIRST || selected > wxID_REGIONS_LAST)
563 return;
564 selected -= wxID_REGIONS_FIRST;
565 auto i = lsnes_memory.get_regions();
566 int index = 0;
567 for(auto j : i) {
568 if(j->readonly || j->special)
569 continue;
570 if(index == selected) {
571 if(j->base != hpanel->vmabase || j->size != hpanel->vmasize)
572 update_vma(j->base, j->size);
573 current_vma = index;
574 if(vma_endians.count(index)) {
575 littleendian = vma_endians[index];
576 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
578 return;
580 index++;
582 current_vma = index;
583 update_vma(0, 0);
585 bool is_endian_little(int endian)
587 if(endian < 0) return true;
588 if(endian > 0) return false;
589 uint16_t magic = 1;
590 return (*reinterpret_cast<uint8_t*>(&magic) == 1);
592 void update_vma(uint64_t base, uint64_t size)
594 hpanel->vmabase = base;
595 hpanel->vmasize = size;
596 hpanel->offset = 0;
597 hpanel->seloff = 0;
598 scroll->set_range((size + 15) / 16);
599 scroll->set_position(0);
600 hpanel->request_paint();
602 void on_typechange(wxCommandEvent& e)
604 if(destructing)
605 return;
606 int id = e.GetId();
607 if(id < wxID_DATATYPES_FIRST || id > wxID_DATATYPES_LAST)
608 return;
609 curtype = id - wxID_DATATYPES_FIRST;
610 hpanel->request_paint();
612 void on_changeendian(wxCommandEvent& e)
614 if(destructing)
615 return;
616 littleendian = valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->IsChecked();
617 if(vma_endians.count(current_vma))
618 vma_endians[current_vma] = littleendian;
619 hpanel->request_paint();
621 void updated()
623 if(destructing)
624 return;
625 hpanel->request_paint();
627 void jumpto(uint64_t addr)
629 if(destructing)
630 return;
631 //Switch to correct VMA.
632 auto i = lsnes_memory.get_regions();
633 int index = 0;
634 for(auto j : i) {
635 if(j->readonly || j->special)
636 continue;
637 if(addr >= j->base && addr < j->base + j->size) {
638 if(j->base != hpanel->vmabase || j->size != hpanel->vmasize)
639 update_vma(j->base, j->size);
640 current_vma = index;
641 if(vma_endians.count(index)) {
642 littleendian = vma_endians[index];
643 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
645 break;
647 index++;
649 if(addr < hpanel->vmabase || addr >= hpanel->vmabase + hpanel->vmasize)
650 return;
651 hpanel->seloff = addr - hpanel->vmabase;
652 rescroll_panel();
653 hpanel->request_paint();
655 void refresh_curvalue()
657 uint8_t buf[maxvaluelen];
658 memcpy(buf, hpanel->value, maxvaluelen);
659 val_type vt = datatypes[curtype];
660 if(littleendian != is_endian_little(vt.hard_bigendian ? 1 : 0))
661 for(unsigned i = 0; i < vt.len / 2; i++)
662 std::swap(buf[i], buf[vt.len - i - 1]);
663 wxMenuItem* it = regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma);
664 std::string vma = "(none)";
665 if(it) vma = tostdstring(it->GetItemLabelText());
666 unsigned addrlen = 1;
667 while(hpanel->vmasize > (1 << (4 * addrlen)))
668 addrlen++;
669 std::string addr = (stringfmt() << std::hex << std::setw(addrlen) << std::setfill('0') <<
670 hpanel->seloff).str();
671 std::string vtext = vt.read(buf);
672 statusbar->SetStatusText(towxstring("Region: " + vma + " Address: " + addr + " Value: " + vtext));
674 int vma_index_for_name(const std::string& x)
676 for(size_t i = 0; i < vma_names.size(); i++)
677 if(vma_names[i] == x)
678 return i;
679 return -1;
681 std::string get_current_vma_name()
683 if(current_vma >= vma_names.size())
684 return "";
685 return vma_names[current_vma];
687 void on_core_changed(bool _hard)
689 if(destructing)
690 return;
691 bool hard = _hard;
692 runuifun([this, hard]() {
693 for(unsigned i = wxID_REGIONS_FIRST; i <= wxID_REGIONS_LAST; i++) {
694 auto p = regionmenu->FindItem(i);
695 if(p)
696 regionmenu->Delete(p);
698 std::string current_reg = get_current_vma_name();
699 uint64_t nsbase = 0, nssize = 0;
700 auto i = lsnes_memory.get_regions();
701 vma_names.clear();
702 if(hard)
703 vma_endians.clear();
704 int index = 0;
705 int curreg_index = 0;
706 for(auto j : i) {
707 if(j->readonly || j->special)
708 continue;
709 regionmenu->AppendRadioItem(wxID_REGIONS_FIRST + index, towxstring(j->name));
710 vma_names.push_back(j->name);
711 if(j->name == current_reg || index == 0) {
712 curreg_index = index;
713 nsbase = j->base;
714 nssize = j->size;
716 if(!vma_endians.count(index))
717 vma_endians[index] = is_endian_little(j->endian);
718 index++;
720 if(!index) {
721 update_vma(0, 0);
722 return;
724 regionmenu->FindItem(wxID_REGIONS_FIRST + curreg_index)->Check();
725 current_vma = curreg_index;
726 if(vma_endians.count(current_vma)) {
727 littleendian = vma_endians[current_vma];
728 typemenu->FindItem(wxID_DATATYPES_FIRST)->Check(littleendian);
730 if(nsbase != hpanel->vmabase || nssize != hpanel->vmasize)
731 update_vma(nsbase, nssize);
733 hpanel->request_paint();
735 void do_hex(int hex)
737 if(hpanel->seloff > hpanel->vmasize)
738 return;
739 if(hex_input_state < 0)
740 hex_input_state = hex;
741 else {
742 uint8_t byte = hex_input_state * 16 + hex;
743 uint64_t addr = hpanel->vmabase + hpanel->seloff;
744 hex_input_state = -1;
745 if(hpanel->seloff + 1 < hpanel->vmasize)
746 hpanel->seloff++;
747 runemufn([addr, byte]() {lsnes_memory.write<uint8_t>(addr, byte); });
749 hpanel->request_paint();
751 class _panel : public text_framebuffer_panel
753 public:
754 _panel(wxeditor_hexedit* parent)
755 : text_framebuffer_panel(parent, 59, lines = 28, wxID_ANY, NULL)
757 rparent = parent;
758 vmabase = 0;
759 vmasize = 0;
760 offset = 0;
761 seloff = 0;
762 memset(value, 0, maxvaluelen);
763 clear();
764 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
765 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
766 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
767 Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, parent);
768 request_paint();
770 void prepare_paint()
772 uint64_t paint_offset = static_cast<uint64_t>(offset) * 16;
773 uint64_t _vmabase = vmabase;
774 uint64_t _vmasize = vmasize;
775 uint64_t _seloff = seloff;
776 int _lines = lines;
777 uint8_t* _value = value;
778 runemufn([_vmabase, _vmasize, paint_offset, _seloff, _value, _lines, this]() {
779 memory_search* memsearch = wxwindow_memorysearch_active();
780 //Paint the stuff
781 for(size_t j = 0; j < _lines; j++) {
782 uint64_t addr = paint_offset + j * 16;
783 if(addr >= _vmasize) {
784 //Past-the-end.
785 for(size_t i = 0; i < get_characters().first; i++)
786 write(" ", 1, i, j, 0, 0xFFFFFF);
787 continue;
789 for(size_t i = 0; i < sizeof(separators)/sizeof(separators[0]); i++) {
790 write(sepchars[i], 1, separators[i], j, 0, 0xFFFFFF);
792 for(size_t i = 0; i < hexaddr; i++) {
793 write(hexes[(addr >> 4 * (hexaddr - i - 1)) & 15], 1, i, j, 0,
794 0xFFFFFF);
796 size_t bytes = 16;
797 if(_vmasize - addr < 16)
798 bytes = _vmasize - addr;
799 uint64_t laddr = addr + _vmabase;
800 for(size_t i = 0; i < bytes; i++) {
801 uint32_t fg = 0;
802 uint32_t bg = 0xFFFFFF;
803 bool candidate = (memsearch && memsearch->is_candidate(laddr + i));
804 if(candidate) bg = bg & 0xC0C0C0 | 0x3F0000;
805 if(addr + i == _seloff)
806 std::swap(fg, bg);
807 uint8_t b = lsnes_memory.read<uint8_t>(laddr + i);
808 if(rparent->hex_input_state < 0 || addr + i != seloff
810 write(hexes[(b >> 4) & 15], 1, hexcol[i], j, fg, bg);
811 else
812 write(hexes[rparent->hex_input_state], 1, hexcol[i], j, 0xFF,
814 write(hexes[b & 15], 1, hexcol[i] + 1, j, fg, bg);
815 char32_t buf[2] = {0, 0};
816 buf[0] = byte_to_char(b);
817 write(buf, 1, charcol[i], j, fg, bg);
819 for(size_t i = bytes; i < 16; i++) {
820 write(" ", 2, hexcol[i], j, 0, 0xFFFFFF);
821 write(" ", 1, hexcol[i] + 1, j, 0, 0xFFFFFF);
824 memset(_value, 0, maxvaluelen);
825 lsnes_memory.read_range(_vmabase + _seloff, _value, maxvaluelen);
827 rparent->refresh_curvalue();
828 rparent->set_search_status();
830 char32_t byte_to_char(uint8_t ch)
832 if(ch == 160)
833 return U' ';
834 if((ch & 0x60) == 0 || ch == 127 || ch == 0xad)
835 return U'.';
836 return ch;
838 void on_mouse0(int x, int y, bool polarity)
840 if(!polarity)
841 return;
842 uint64_t rowaddr = 16 * (static_cast<uint64_t>(offset) + y);
843 int coladdr = 16;
844 for(unsigned i = 0; i < 16; i++)
845 if(x == hexcol[i] || x == hexcol[i] + 1 || x == charcol[i])
846 coladdr = i;
847 if(rowaddr + coladdr >= vmasize || coladdr > 15)
848 return;
849 seloff = rowaddr + coladdr;
850 request_paint();
852 wxeditor_hexedit* rparent;
853 int offset;
854 uint64_t vmabase;
855 uint64_t vmasize;
856 uint64_t seloff;
857 uint8_t value[maxvaluelen];
858 int lines;
860 private:
861 struct bookmark_entry
863 std::string name;
864 std::string vma;
865 int scroll;
866 uint64_t sel;
868 wxMenu* regionmenu;
869 wxMenu* bookmarkmenu;
870 wxMenu* searchmenu;
871 wxComboBox* datatype;
872 wxMenuItem* oendian;
873 wxStatusBar* statusbar;
874 wxMenuBar* menubar;
875 scroll_bar* scroll;
876 _panel* hpanel;
877 wxMenu* valuemenu;
878 wxMenu* typemenu;
879 struct dispatch::target<bool> corechange;
880 unsigned current_vma;
881 std::vector<std::string> vma_names;
882 std::map<unsigned, bool> vma_endians;
883 std::vector<bookmark_entry> bookmarks;
884 bool destructing;
885 unsigned curtype;
886 bool littleendian;
887 int hex_input_state;
890 void wxeditor_hexedit_display(wxWindow* parent)
892 if(editor)
893 return;
894 try {
895 editor = new wxeditor_hexedit(parent);
896 editor->Show();
897 } catch(...) {
901 void wxeditor_hexeditor_update()
903 if(editor)
904 editor->updated();
907 bool wxeditor_hexeditor_available()
909 return editor;
912 bool wxeditor_hexeditor_jumpto(uint64_t addr)
914 if(editor)
915 editor->jumpto(addr);