Make various instance stuff to take references to other instance objs
[lsnes.git] / src / platform / wxwidgets / editor-hexedit.cpp
blob79f91a7b6fb2262dc96d6b41738438c57d6307b1
1 #include "core/moviedata.hpp"
2 #include "core/memorywatch.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/instance.hpp"
5 #include "core/project.hpp"
6 #include "core/memorymanip.hpp"
7 #include "library/memorysearch.hpp"
8 #include "library/hex.hpp"
10 #include "platform/wxwidgets/platform.hpp"
11 #include "platform/wxwidgets/textrender.hpp"
12 #include "platform/wxwidgets/loadsave.hpp"
13 #include "platform/wxwidgets/scrollbar.hpp"
15 #include <wx/wx.h>
16 #include <wx/event.h>
17 #include <wx/control.h>
18 #include <wx/combobox.h>
19 #include <wx/radiobut.h>
21 #include "library/string.hpp"
22 #include "library/json.hpp"
23 #include "library/zip.hpp"
24 #include "interface/romtype.hpp"
26 #include <iomanip>
28 class wxeditor_hexedit;
30 namespace
32 const size_t maxvaluelen = 8; //The length of longest value type.
33 wxeditor_hexedit* editor;
35 struct val_type
37 const char* name;
38 unsigned len;
39 bool hard_bigendian;
40 const char* format;
41 int type; //0 => Unsigned, 1 => Signed, 2 => Float
42 int scale;
43 std::string (*read)(const uint8_t* x);
46 val_type datatypes[] = {
47 {"1 byte (signed)", 1, false, "", 1, 0, [](const uint8_t* x) -> std::string {
48 return (stringfmt() << (int)(char)x[0]).str();
49 }},
50 {"1 byte (unsigned)", 1, false, "", 0, 0, [](const uint8_t* x) -> std::string {
51 return (stringfmt() << (int)x[0]).str();
52 }},
53 {"1 byte (hex)", 1, false, "%02x", 0, 0, [](const uint8_t* x) -> std::string {
54 return hex::to(x[0]);
55 }},
56 {"2 bytes (signed)", 2, false, "", 1, 0, [](const uint8_t* x) -> std::string {
57 return (stringfmt() << *(int16_t*)x).str();
58 }},
59 {"2 bytes (unsigned)", 2, false, "", 0, 0, [](const uint8_t* x) -> std::string {
60 return (stringfmt() << *(uint16_t*)x).str();
61 }},
62 {"2 bytes (hex)", 2, false, "%04x", 0, 0, [](const uint8_t* x) -> std::string {
63 return hex::to(*(uint16_t*)x);
64 }},
65 {"3 bytes (signed)", 3, true, "", 1, 0, [](const uint8_t* x) -> std::string {
66 int32_t a = 0;
67 a |= (uint32_t)x[0] << 16;
68 a |= (uint32_t)x[1] << 8;
69 a |= (uint32_t)x[2];
70 if(a & 0x800000)
71 a -= 0x1000000;
72 return (stringfmt() << a).str();
73 }},
74 {"3 bytes (unsigned)", 3, true, "", 0, 0, [](const uint8_t* x) -> std::string {
75 int32_t a = 0;
76 a |= (uint32_t)x[0] << 16;
77 a |= (uint32_t)x[1] << 8;
78 a |= (uint32_t)x[2];
79 return (stringfmt() << a).str();
80 }},
81 {"3 bytes (hex)", 3, true, "%06x", 0, 0, [](const uint8_t* x) -> std::string {
82 int32_t a = 0;
83 a |= (uint32_t)x[0] << 16;
84 a |= (uint32_t)x[1] << 8;
85 a |= (uint32_t)x[2];
86 return hex::to24(a);
87 }},
88 {"4 bytes (signed)", 4, false, "", 1, 0, [](const uint8_t* x) -> std::string {
89 return (stringfmt() << *(int32_t*)x).str();
90 }},
91 {"4 bytes (unsigned)", 4, false, "", 0, 0, [](const uint8_t* x) -> std::string {
92 return (stringfmt() << *(uint32_t*)x).str();
93 }},
94 {"4 bytes (hex)", 4, false, "%08x", 0, 0, [](const uint8_t* x) -> std::string {
95 return hex::to(*(uint32_t*)x);
96 }},
97 {"4 bytes (float)", 4, false, "", 2, 0, [](const uint8_t* x) -> std::string {
98 return (stringfmt() << *(float*)x).str();
99 }},
100 {"8 bytes (signed)", 8, false, "", 1, 0, [](const uint8_t* x) -> std::string {
101 return (stringfmt() << *(int64_t*)x).str();
103 {"8 bytes (unsigned)", 8, false, "", 0, 0, [](const uint8_t* x) -> std::string {
104 return (stringfmt() << *(uint64_t*)x).str();
106 {"8 bytes (hex)", 8, false, "%016x", 0, 0, [](const uint8_t* x) -> std::string {
107 return hex::to(*(uint64_t*)x);
109 {"8 bytes (float)", 8, false, "", 2, 0, [](const uint8_t* x) -> std::string {
110 return (stringfmt() << *(double*)x).str();
112 {"Q8.8 (signed)", 2, false, "", 1, 8, [](const uint8_t* x) -> std::string {
113 return (stringfmt() << *(int16_t*)x / 256.0).str();
115 {"Q8.8 (unsigned)", 2, false, "", 0, 8, [](const uint8_t* x) -> std::string {
116 return (stringfmt() << *(uint16_t*)x / 256.0).str();
118 {"Q12.4 (signed)", 2, false, "", 1, 4, [](const uint8_t* x) -> std::string {
119 return (stringfmt() << *(int16_t*)x / 16.0).str();
121 {"Q12.4 (unsigned)", 2, false, "", 0, 4, [](const uint8_t* x) -> std::string {
122 return (stringfmt() << *(uint16_t*)x / 16.0).str();
124 {"Q16.8 (signed)", 3, true, "", 1, 8, [](const uint8_t* x) -> std::string {
125 int32_t a = 0;
126 a |= (uint32_t)x[0] << 16;
127 a |= (uint32_t)x[1] << 8;
128 a |= (uint32_t)x[2];
129 if(a & 0x800000)
130 a -= 0x1000000;
131 return (stringfmt() << a / 256.0).str();
133 {"Q16.8 (unsigned)", 3, true, "", 0, 8, [](const uint8_t* x) -> std::string {
134 int32_t a = 0;
135 a |= (uint32_t)x[0] << 16;
136 a |= (uint32_t)x[1] << 8;
137 a |= (uint32_t)x[2];
138 return (stringfmt() << a / 256.0).str();
140 {"Q24.8 (signed)", 4, false, "", 1, 8, [](const uint8_t* x) -> std::string {
141 return (stringfmt() << *(int32_t*)x / 256.0).str();
143 {"Q24.8 (unsigned)", 4, false, "", 0, 8, [](const uint8_t* x) -> std::string {
144 return (stringfmt() << *(uint32_t*)x / 256.0).str();
146 {"Q20.12 (signed)", 4, false, "", 1, 12, [](const uint8_t* x) -> std::string {
147 return (stringfmt() << *(int32_t*)x / 4096.0).str();
149 {"Q20.12 (unsigned)", 4, false, "", 0, 12, [](const uint8_t* x) -> std::string {
150 return (stringfmt() << *(uint32_t*)x / 4096.0).str();
152 {"Q16.16 (signed)", 4, false, "", 1, 16, [](const uint8_t* x) -> std::string {
153 return (stringfmt() << *(int32_t*)x / 65536.0).str();
155 {"Q16.16 (unsigned)", 4, false, "", 0, 16, [](const uint8_t* x) -> std::string {
156 return (stringfmt() << *(uint32_t*)x / 65536.0).str();
160 unsigned hexaddr = 6;
161 int separators[5] = {6, 15, 24, 28, 42};
162 const char32_t* sepchars[5] = {U"\u2502", U" ", U".", U" ", U"\u2502"};
163 int hexcol[16] = {7, 9, 11, 13, 16, 18, 20, 22, 25, 27, 29, 31, 34, 36, 38, 40};
164 int charcol[16] = {43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58};
165 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",
166 U"D", U"E", U"F"};
168 enum {
169 wxID_OPPOSITE_ENDIAN = wxID_HIGHEST + 1,
170 wxID_DATATYPES_FIRST,
171 wxID_DATATYPES_LAST = wxID_DATATYPES_FIRST + 255,
172 wxID_REGIONS_FIRST,
173 wxID_REGIONS_LAST = wxID_REGIONS_FIRST + 255,
174 wxID_ADD_BOOKMARK,
175 wxID_DELETE_BOOKMARK,
176 wxID_LOAD_BOOKMARKS,
177 wxID_SAVE_BOOKMARKS,
178 wxID_BOOKMARKS_FIRST,
179 wxID_BOOKMARKS_LAST = wxID_BOOKMARKS_FIRST + 255,
180 wxID_SEARCH_DISQUALIFY,
181 wxID_SEARCH_PREV,
182 wxID_SEARCH_NEXT,
183 wxID_SEARCH_WATCH,
187 class wxeditor_hexedit : public wxFrame
189 public:
190 wxeditor_hexedit(wxWindow* parent)
191 : wxFrame(parent, wxID_ANY, wxT("lsnes: Memory editor"), wxDefaultPosition, wxSize(-1, -1),
192 wxCAPTION | wxMINIMIZE_BOX | wxCLOSE_BOX | wxSYSTEM_MENU)
194 Centre();
195 wxBoxSizer* top = new wxBoxSizer(wxVERTICAL);
196 SetSizer(top);
198 destructing = false;
199 hex_input_state = -1;
201 Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this);
203 wxBoxSizer* parea = new wxBoxSizer(wxHORIZONTAL);
204 parea->Add(hpanel = new _panel(this), 1, wxGROW);
205 hpanel->SetFocus();
206 parea->Add(scroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
207 top->Add(parea, 1, wxGROW);
208 scroll->Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this);
210 SetStatusBar(statusbar = new wxStatusBar(this));
211 SetMenuBar(menubar = new wxMenuBar);
213 valuemenu = new wxMenu();
214 menubar->Append(valuemenu, wxT("Value"));
215 regionmenu = new wxMenu();
216 menubar->Append(regionmenu, wxT("Region"));
217 typemenu = new wxMenu();
218 bookmarkmenu = new wxMenu();
219 bookmarkmenu->Append(wxID_ADD_BOOKMARK, wxT("Add bookmark..."));
220 bookmarkmenu->Append(wxID_DELETE_BOOKMARK, wxT("Delete bookmark..."));
221 bookmarkmenu->AppendSeparator();
222 bookmarkmenu->Append(wxID_LOAD_BOOKMARKS, wxT("Load bookmarks..."));
223 bookmarkmenu->Append(wxID_SAVE_BOOKMARKS, wxT("Save bookmarks..."));
224 bookmarkmenu->AppendSeparator();
225 menubar->Append(bookmarkmenu, wxT("Bookmarks"));
226 valuemenu->AppendSubMenu(typemenu, wxT("Type"));
227 oendian = valuemenu->AppendCheckItem(wxID_OPPOSITE_ENDIAN, wxT("Little endian"));
228 for(size_t i = 0; i < sizeof(datatypes) / sizeof(datatypes[0]); i++)
229 typemenu->AppendRadioItem(wxID_DATATYPES_FIRST + i, towxstring(datatypes[i].name));
230 typemenu->FindItem(wxID_DATATYPES_FIRST)->Check();
231 searchmenu = new wxMenu();
232 menubar->Append(searchmenu, wxT("Search"));
233 searchmenu->Append(wxID_SEARCH_PREV, wxT("Previous...\tCtrl+P"));
234 searchmenu->Append(wxID_SEARCH_NEXT, wxT("Next...\tCtrl+N"));
235 searchmenu->Append(wxID_SEARCH_WATCH, wxT("Add watch...\tCtrl+W"));
236 searchmenu->AppendSeparator();
237 searchmenu->Append(wxID_SEARCH_DISQUALIFY, wxT("Disqualify...\tCtrl+D"));
238 set_search_status();
240 littleendian = true;
241 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
242 curtype = 0;
243 Connect(wxID_ADD_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED,
244 wxCommandEventHandler(wxeditor_hexedit::on_addbookmark));
245 Connect(wxID_DELETE_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED,
246 wxCommandEventHandler(wxeditor_hexedit::on_deletebookmark));
247 Connect(wxID_LOAD_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED,
248 wxCommandEventHandler(wxeditor_hexedit::on_loadbookmarks));
249 Connect(wxID_SAVE_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED,
250 wxCommandEventHandler(wxeditor_hexedit::on_savebookmarks));
251 Connect(wxID_OPPOSITE_ENDIAN, wxEVT_COMMAND_MENU_SELECTED,
252 wxCommandEventHandler(wxeditor_hexedit::on_changeendian));
253 Connect(wxID_DATATYPES_FIRST, wxID_DATATYPES_LAST, wxEVT_COMMAND_MENU_SELECTED,
254 wxCommandEventHandler(wxeditor_hexedit::on_typechange));
255 Connect(wxID_REGIONS_FIRST, wxID_REGIONS_LAST, wxEVT_COMMAND_MENU_SELECTED,
256 wxCommandEventHandler(wxeditor_hexedit::on_vmasel));
257 Connect(wxID_BOOKMARKS_FIRST, wxID_BOOKMARKS_LAST, wxEVT_COMMAND_MENU_SELECTED,
258 wxCommandEventHandler(wxeditor_hexedit::on_bookmark));
259 Connect(wxID_SEARCH_DISQUALIFY, wxEVT_COMMAND_MENU_SELECTED,
260 wxCommandEventHandler(wxeditor_hexedit::on_search_discard));
261 Connect(wxID_SEARCH_PREV, wxEVT_COMMAND_MENU_SELECTED,
262 wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext));
263 Connect(wxID_SEARCH_NEXT, wxEVT_COMMAND_MENU_SELECTED,
264 wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext));
265 Connect(wxID_SEARCH_WATCH, wxEVT_COMMAND_MENU_SELECTED,
266 wxCommandEventHandler(wxeditor_hexedit::on_search_watch));
268 scroll->set_page_size(hpanel->lines);
269 scroll->set_handler([this](scroll_bar& s) {
270 this->hpanel->offset = s.get_position();
271 this->hpanel->request_paint();
274 corechange.set(notify_core_changed, [this](bool hard) { this->on_core_changed(hard); });
275 on_core_changed(true);
276 top->SetSizeHints(this);
277 Fit();
279 ~wxeditor_hexedit()
281 destructing = true;
282 editor = NULL;
284 bool ShouldPreventAppExit() const
286 return false;
288 void set_search_status()
290 bool e = wxwindow_memorysearch_active();
291 searchmenu->FindItem(wxID_SEARCH_DISQUALIFY)->Enable(e);
292 searchmenu->FindItem(wxID_SEARCH_PREV)->Enable(e);
293 searchmenu->FindItem(wxID_SEARCH_NEXT)->Enable(e);
295 void on_keyboard(wxKeyEvent& e)
297 int c = e.GetKeyCode();
298 if(c == WXK_ESCAPE) {
299 hex_input_state = -1;
300 hpanel->request_paint();
301 return;
303 if(c == WXK_LEFT && hex_input_state < 0) {
304 if(hpanel->seloff > 0) hpanel->seloff--;
305 hpanel->request_paint();
306 return;
308 if(c == WXK_RIGHT && hex_input_state < 0) {
309 if(hpanel->seloff + 1 < hpanel->vmasize) hpanel->seloff++;
310 hpanel->request_paint();
311 return;
313 if(c == WXK_UP && hex_input_state < 0) {
314 if(hpanel->seloff >= 16) hpanel->seloff -= 16;
315 hpanel->request_paint();
316 return;
318 if(c == WXK_DOWN && hex_input_state < 0) {
319 if(hpanel->seloff + 16 < hpanel->vmasize) hpanel->seloff += 16;
320 hpanel->request_paint();
321 return;
323 if(c == WXK_PAGEUP && hex_input_state < 0) {
324 scroll->apply_delta(-static_cast<int>(hpanel->lines));
325 hpanel->offset = scroll->get_position();
326 hpanel->request_paint();
327 return;
329 if(c == WXK_PAGEDOWN && hex_input_state < 0) {
330 scroll->apply_delta(static_cast<int>(hpanel->lines));
331 hpanel->offset = scroll->get_position();
332 hpanel->request_paint();
333 return;
335 if(c >= '0' && c <= '9') {
336 do_hex(c - '0');
337 return;
339 if(c >= 'A' && c <= 'F') {
340 do_hex(c - 'A' + 10);
341 return;
343 if(c >= 'a' && c <= 'f') {
344 do_hex(c - 'a' + 10);
345 return;
347 e.Skip();
349 void on_mouse(wxMouseEvent& e)
351 auto cell = hpanel->get_cell();
352 if(e.LeftDown())
353 hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true);
354 if(e.LeftUp())
355 hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false);
356 unsigned speed = 1;
357 if(e.ShiftDown())
358 speed = 10;
359 if(e.ShiftDown() && e.ControlDown())
360 speed = 50;
361 scroll->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
362 hpanel->offset = scroll->get_position();
364 void on_loadbookmarks(wxCommandEvent& e)
366 try {
367 std::string filename = choose_file_load(this, "Load bookmarks from file",
368 lsnes_instance.project.otherpath(), filetype_hexbookmarks);
369 auto _in = zip::readrel(filename, "");
370 std::string in(_in.begin(), _in.end());
371 JSON::node root(in);
372 std::vector<bookmark_entry> newbookmarks;
373 for(auto i : root) {
374 bookmark_entry e;
375 e.name = i["name"].as_string8();
376 e.vma = i["vma"].as_string8();
377 e.scroll = i["offset"].as_int();
378 e.sel = i["selected"].as_uint();
379 newbookmarks.push_back(e);
381 std::swap(bookmarks, newbookmarks);
382 for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) {
383 auto p = bookmarkmenu->FindItem(i);
384 if(p)
385 bookmarkmenu->Delete(p);
387 int idx = 0;
388 for(auto i : bookmarks) {
389 if(wxID_BOOKMARKS_FIRST + idx > wxID_BOOKMARKS_LAST)
390 break;
391 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name));
392 idx++;
394 } catch(canceled_exception& e) {
395 } catch(std::exception& e) {
396 show_message_ok(this, "Error", std::string("Can't load bookmarks: ") + e.what(),
397 wxICON_EXCLAMATION);
398 return;
401 void on_savebookmarks(wxCommandEvent& e)
403 JSON::node root(JSON::array);
404 for(auto i : bookmarks) {
405 JSON::node n(JSON::object);
406 n["name"] = JSON::string(i.name);
407 n["vma"] = JSON::string(i.vma);
408 n["offset"] = JSON::number((int64_t)i.scroll);
409 n["selected"] = JSON::number(i.sel);
410 root.append(n);
412 std::string doc = root.serialize();
413 try {
414 std::string filename = choose_file_save(this, "Save bookmarks to file",
415 lsnes_instance.project.otherpath(), filetype_hexbookmarks);
416 std::ofstream out(filename.c_str());
417 out << doc << std::endl;
418 out.close();
419 } catch(canceled_exception& e) {
420 } catch(std::exception& e) {
421 show_message_ok(this, "Error", std::string("Can't save bookmarks: ") + e.what(),
422 wxICON_EXCLAMATION);
425 void on_addbookmark(wxCommandEvent& e)
427 if(bookmarks.size() <= wxID_BOOKMARKS_LAST - wxID_BOOKMARKS_FIRST) {
428 std::string name = pick_text(this, "Add bookmark", "Enter name for bookmark", "", false);
429 bookmark_entry ent;
430 ent.name = name;
431 ent.vma = get_current_vma_name();
432 ent.scroll = hpanel->offset;
433 ent.sel = hpanel->seloff;
434 int idx = bookmarks.size();
435 bookmarks.push_back(ent);
436 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(name));
437 } else {
438 show_message_ok(this, "Error adding bookmark", "Too many bookmarks", wxICON_EXCLAMATION);
441 void on_deletebookmark(wxCommandEvent& e)
443 if(bookmarks.size() > 0) {
444 std::vector<wxString> _choices;
445 for(auto i : bookmarks)
446 _choices.push_back(towxstring(i.name));
447 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(this, towxstring("Select bookmark "
448 "to delete"), towxstring("Delete bookmark"), _choices.size(), &_choices[0]);
449 d2->SetSelection(0);
450 if(d2->ShowModal() == wxID_CANCEL) {
451 d2->Destroy();
452 return;
454 int sel = d2->GetSelection();
455 d2->Destroy();
456 if(sel >= 0)
457 bookmarks.erase(bookmarks.begin() + sel);
458 for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) {
459 auto p = bookmarkmenu->FindItem(i);
460 if(p)
461 bookmarkmenu->Delete(p);
463 int idx = 0;
464 for(auto i : bookmarks) {
465 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name));
466 idx++;
470 void rescroll_panel()
472 uint64_t vfirst = static_cast<uint64_t>(hpanel->offset) * 16;
473 uint64_t vlast = static_cast<uint64_t>(hpanel->offset + hpanel->lines) * 16;
474 if(hpanel->seloff < vfirst || hpanel->seloff >= vlast) {
475 int l = hpanel->seloff / 16;
476 int r = hpanel->lines / 4;
477 hpanel->offset = (l > r) ? (l - r) : 0;
478 scroll->set_position(hpanel->offset);
481 void on_search_discard(wxCommandEvent& e)
483 auto p = wxwindow_memorysearch_active();
484 if(!p)
485 return;
486 if(hpanel->seloff < hpanel->vmasize) {
487 p->dq_range(hpanel->vmabase + hpanel->seloff, hpanel->vmabase + hpanel->seloff);
488 wxwindow_memorysearch_update();
489 hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, true) -
490 hpanel->vmabase;
491 rescroll_panel();
492 hpanel->request_paint();
495 void on_search_watch(wxCommandEvent& e)
497 try {
498 if(!hpanel->vmasize)
499 return;
500 uint64_t addr = hpanel->vmabase + hpanel->seloff;
501 std::string n = pick_text(this, "Name for watch", (stringfmt()
502 << "Enter name for watch at 0x" << std::hex << addr << ":").str());
503 if(n == "")
504 return;
505 memwatch_item e(lsnes_instance.memory);
506 e.expr = (stringfmt() << addr).str();
507 e.format = datatypes[curtype].format;
508 e.bytes = datatypes[curtype].len;
509 e.signed_flag = (datatypes[curtype].type == 1);
510 e.float_flag = (datatypes[curtype].type == 2);
511 //Handle hostendian VMAs.
512 auto i = lsnes_instance.memory.get_regions();
513 bool hostendian = false;
514 for(auto& j : i) {
515 if(addr >= j->base && addr < j->base + j->size && !j->endian)
516 hostendian = true;
518 e.endianess = hostendian ? 0 : (littleendian ? -1 : 1);
519 e.scale_div = 1ULL << datatypes[curtype].scale;
520 lsnes_instance.iqueue.run([n, &e]() { lsnes_instance.mwatch.set(n, e); });
521 } catch(canceled_exception& e) {
524 void on_search_prevnext(wxCommandEvent& e)
526 auto p = wxwindow_memorysearch_active();
527 if(!p)
528 return;
529 if(hpanel->seloff < hpanel->vmasize) {
530 hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, e.GetId() ==
531 wxID_SEARCH_NEXT) - hpanel->vmabase;
532 rescroll_panel();
533 hpanel->request_paint();
536 void on_bookmark(wxCommandEvent& e)
538 int id = e.GetId();
539 if(id < wxID_BOOKMARKS_FIRST || id > wxID_BOOKMARKS_LAST)
540 return;
541 bookmark_entry ent = bookmarks[id - wxID_BOOKMARKS_FIRST];
542 int r = vma_index_for_name(ent.vma);
543 uint64_t base = 0, size = 0;
544 auto i = lsnes_instance.memory.get_regions();
545 for(auto j : i) {
546 if(j->readonly || j->special)
547 continue;
548 if(j->name == ent.vma) {
549 base = j->base;
550 size = j->size;
553 if(ent.sel >= size || ent.scroll >= (ssize_t)((size + 15) / 16))
554 goto invalid_bookmark;
555 current_vma = r;
556 regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma)->Check();
557 update_vma(base, size);
558 hpanel->offset = ent.scroll;
559 hpanel->seloff = ent.sel;
560 scroll->set_position(hpanel->offset);
561 hpanel->request_paint();
562 return;
563 invalid_bookmark:
564 show_message_ok(this, "Error jumping to bookmark", "Bookmark refers to nonexistent location",
565 wxICON_EXCLAMATION);
566 return;
568 void on_vmasel(wxCommandEvent& e)
570 if(destructing)
571 return;
572 int selected = e.GetId();
573 if(selected < wxID_REGIONS_FIRST || selected > wxID_REGIONS_LAST)
574 return;
575 selected -= wxID_REGIONS_FIRST;
576 auto i = lsnes_instance.memory.get_regions();
577 int index = 0;
578 for(auto j : i) {
579 if(j->readonly || j->special)
580 continue;
581 if(index == selected) {
582 if(j->base != hpanel->vmabase || j->size != hpanel->vmasize)
583 update_vma(j->base, j->size);
584 current_vma = index;
585 if(vma_endians.count(index)) {
586 littleendian = vma_endians[index];
587 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
589 return;
591 index++;
593 current_vma = index;
594 update_vma(0, 0);
596 bool is_endian_little(int endian)
598 if(endian < 0) return true;
599 if(endian > 0) return false;
600 uint16_t magic = 1;
601 return (*reinterpret_cast<uint8_t*>(&magic) == 1);
603 void update_vma(uint64_t base, uint64_t size)
605 hpanel->vmabase = base;
606 hpanel->vmasize = size;
607 hpanel->offset = 0;
608 hpanel->seloff = 0;
609 scroll->set_range((size + 15) / 16);
610 scroll->set_position(0);
611 hpanel->request_paint();
613 void on_typechange(wxCommandEvent& e)
615 if(destructing)
616 return;
617 int id = e.GetId();
618 if(id < wxID_DATATYPES_FIRST || id > wxID_DATATYPES_LAST)
619 return;
620 curtype = id - wxID_DATATYPES_FIRST;
621 hpanel->request_paint();
623 void on_changeendian(wxCommandEvent& e)
625 if(destructing)
626 return;
627 littleendian = valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->IsChecked();
628 if(vma_endians.count(current_vma))
629 vma_endians[current_vma] = littleendian;
630 hpanel->request_paint();
632 void updated()
634 if(destructing)
635 return;
636 hpanel->request_paint();
638 void jumpto(uint64_t addr)
640 if(destructing)
641 return;
642 //Switch to correct VMA.
643 auto i = lsnes_instance.memory.get_regions();
644 int index = 0;
645 for(auto j : i) {
646 if(j->readonly || j->special)
647 continue;
648 if(addr >= j->base && addr < j->base + j->size) {
649 if(j->base != hpanel->vmabase || j->size != hpanel->vmasize)
650 update_vma(j->base, j->size);
651 current_vma = index;
652 if(vma_endians.count(index)) {
653 littleendian = vma_endians[index];
654 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
656 break;
658 index++;
660 if(addr < hpanel->vmabase || addr >= hpanel->vmabase + hpanel->vmasize)
661 return;
662 hpanel->seloff = addr - hpanel->vmabase;
663 rescroll_panel();
664 hpanel->request_paint();
666 void refresh_curvalue()
668 uint8_t buf[maxvaluelen];
669 memcpy(buf, hpanel->value, maxvaluelen);
670 val_type vt = datatypes[curtype];
671 if(littleendian != is_endian_little(vt.hard_bigendian ? 1 : 0))
672 for(unsigned i = 0; i < vt.len / 2; i++)
673 std::swap(buf[i], buf[vt.len - i - 1]);
674 wxMenuItem* it = regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma);
675 std::string vma = "(none)";
676 if(it) vma = tostdstring(it->GetItemLabelText());
677 unsigned addrlen = 1;
678 while(hpanel->vmasize > (1ULL << (4 * addrlen)))
679 addrlen++;
680 std::string addr = (stringfmt() << std::hex << std::setw(addrlen) << std::setfill('0') <<
681 hpanel->seloff).str();
682 std::string vtext = vt.read(buf);
683 statusbar->SetStatusText(towxstring("Region: " + vma + " Address: " + addr + " Value: " + vtext));
685 int vma_index_for_name(const std::string& x)
687 for(size_t i = 0; i < vma_names.size(); i++)
688 if(vma_names[i] == x)
689 return i;
690 return -1;
692 std::string get_current_vma_name()
694 if(current_vma >= vma_names.size())
695 return "";
696 return vma_names[current_vma];
698 void on_core_changed(bool _hard)
700 if(destructing)
701 return;
702 bool hard = _hard;
703 runuifun([this, hard]() {
704 for(unsigned i = wxID_REGIONS_FIRST; i <= wxID_REGIONS_LAST; i++) {
705 auto p = regionmenu->FindItem(i);
706 if(p)
707 regionmenu->Delete(p);
709 std::string current_reg = get_current_vma_name();
710 uint64_t nsbase = 0, nssize = 0;
711 auto i = lsnes_instance.memory.get_regions();
712 vma_names.clear();
713 if(hard)
714 vma_endians.clear();
715 int index = 0;
716 int curreg_index = 0;
717 for(auto j : i) {
718 if(j->readonly || j->special)
719 continue;
720 regionmenu->AppendRadioItem(wxID_REGIONS_FIRST + index, towxstring(j->name));
721 vma_names.push_back(j->name);
722 if(j->name == current_reg || index == 0) {
723 curreg_index = index;
724 nsbase = j->base;
725 nssize = j->size;
727 if(!vma_endians.count(index))
728 vma_endians[index] = is_endian_little(j->endian);
729 index++;
731 if(!index) {
732 update_vma(0, 0);
733 return;
735 regionmenu->FindItem(wxID_REGIONS_FIRST + curreg_index)->Check();
736 current_vma = curreg_index;
737 if(vma_endians.count(current_vma)) {
738 littleendian = vma_endians[current_vma];
739 typemenu->FindItem(wxID_DATATYPES_FIRST)->Check(littleendian);
741 if(nsbase != hpanel->vmabase || nssize != hpanel->vmasize)
742 update_vma(nsbase, nssize);
744 hpanel->request_paint();
746 void do_hex(int hex)
748 if(hpanel->seloff > hpanel->vmasize)
749 return;
750 if(hex_input_state < 0)
751 hex_input_state = hex;
752 else {
753 uint8_t byte = hex_input_state * 16 + hex;
754 uint64_t addr = hpanel->vmabase + hpanel->seloff;
755 hex_input_state = -1;
756 if(hpanel->seloff + 1 < hpanel->vmasize)
757 hpanel->seloff++;
758 lsnes_instance.iqueue.run([addr, byte]() {lsnes_instance.memory.write<uint8_t>(addr, byte); });
760 hpanel->request_paint();
762 class _panel : public text_framebuffer_panel
764 public:
765 _panel(wxeditor_hexedit* parent)
766 : text_framebuffer_panel(parent, 59, lines = 28, wxID_ANY, NULL)
768 rparent = parent;
769 vmabase = 0;
770 vmasize = 0;
771 offset = 0;
772 seloff = 0;
773 memset(value, 0, maxvaluelen);
774 clear();
775 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
776 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
777 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
778 Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, parent);
779 request_paint();
781 void prepare_paint()
783 uint64_t paint_offset = static_cast<uint64_t>(offset) * 16;
784 uint64_t _vmabase = vmabase;
785 uint64_t _vmasize = vmasize;
786 uint64_t _seloff = seloff;
787 int _lines = lines;
788 uint8_t* _value = value;
789 lsnes_instance.iqueue.run([_vmabase, _vmasize, paint_offset, _seloff, _value, _lines,
790 this]() {
791 memory_search* memsearch = wxwindow_memorysearch_active();
792 //Paint the stuff
793 for(ssize_t j = 0; j < _lines; j++) {
794 uint64_t addr = paint_offset + j * 16;
795 if(addr >= _vmasize) {
796 //Past-the-end.
797 for(size_t i = 0; i < get_characters().first; i++)
798 write(" ", 1, i, j, 0, 0xFFFFFF);
799 continue;
801 for(size_t i = 0; i < sizeof(separators)/sizeof(separators[0]); i++) {
802 write(sepchars[i], 1, separators[i], j, 0, 0xFFFFFF);
804 for(size_t i = 0; i < hexaddr; i++) {
805 write(hexes[(addr >> 4 * (hexaddr - i - 1)) & 15], 1, i, j, 0,
806 0xFFFFFF);
808 size_t bytes = 16;
809 if(_vmasize - addr < 16)
810 bytes = _vmasize - addr;
811 uint64_t laddr = addr + _vmabase;
812 for(size_t i = 0; i < bytes; i++) {
813 uint32_t fg = 0;
814 uint32_t bg = 0xFFFFFF;
815 bool candidate = (memsearch && memsearch->is_candidate(laddr + i));
816 if(candidate) bg = (bg & 0xC0C0C0) | 0x3F0000;
817 if(addr + i == _seloff)
818 std::swap(fg, bg);
819 uint8_t b = lsnes_instance.memory.read<uint8_t>(laddr + i);
820 if(rparent->hex_input_state < 0 || addr + i != seloff
822 write(hexes[(b >> 4) & 15], 1, hexcol[i], j, fg, bg);
823 else
824 write(hexes[rparent->hex_input_state], 1, hexcol[i], j, 0xFF,
826 write(hexes[b & 15], 1, hexcol[i] + 1, j, fg, bg);
827 char32_t buf[2] = {0, 0};
828 buf[0] = byte_to_char(b);
829 write(buf, 1, charcol[i], j, fg, bg);
831 for(size_t i = bytes; i < 16; i++) {
832 write(" ", 2, hexcol[i], j, 0, 0xFFFFFF);
833 write(" ", 1, charcol[i] + 1, j, 0, 0xFFFFFF);
836 memset(_value, 0, maxvaluelen);
837 lsnes_instance.memory.read_range(_vmabase + _seloff, _value, maxvaluelen);
839 rparent->refresh_curvalue();
840 rparent->set_search_status();
842 char32_t byte_to_char(uint8_t ch)
844 if(ch == 160)
845 return U' ';
846 if((ch & 0x60) == 0 || ch == 127 || ch == 0xad)
847 return U'.';
848 return ch;
850 void on_mouse0(int x, int y, bool polarity)
852 if(!polarity)
853 return;
854 uint64_t rowaddr = 16 * (static_cast<uint64_t>(offset) + y);
855 int coladdr = 16;
856 for(unsigned i = 0; i < 16; i++)
857 if(x == hexcol[i] || x == hexcol[i] + 1 || x == charcol[i])
858 coladdr = i;
859 if(rowaddr + coladdr >= vmasize || coladdr > 15)
860 return;
861 seloff = rowaddr + coladdr;
862 request_paint();
864 wxeditor_hexedit* rparent;
865 int offset;
866 uint64_t vmabase;
867 uint64_t vmasize;
868 uint64_t seloff;
869 uint8_t value[maxvaluelen];
870 int lines;
872 private:
873 struct bookmark_entry
875 std::string name;
876 std::string vma;
877 int scroll;
878 uint64_t sel;
880 wxMenu* regionmenu;
881 wxMenu* bookmarkmenu;
882 wxMenu* searchmenu;
883 wxComboBox* datatype;
884 wxMenuItem* oendian;
885 wxStatusBar* statusbar;
886 wxMenuBar* menubar;
887 scroll_bar* scroll;
888 _panel* hpanel;
889 wxMenu* valuemenu;
890 wxMenu* typemenu;
891 struct dispatch::target<bool> corechange;
892 unsigned current_vma;
893 std::vector<std::string> vma_names;
894 std::map<unsigned, bool> vma_endians;
895 std::vector<bookmark_entry> bookmarks;
896 bool destructing;
897 unsigned curtype;
898 bool littleendian;
899 int hex_input_state;
902 void wxeditor_hexedit_display(wxWindow* parent)
904 if(editor)
905 return;
906 try {
907 editor = new wxeditor_hexedit(parent);
908 editor->Show();
909 } catch(...) {
913 void wxeditor_hexeditor_update()
915 if(editor)
916 editor->updated();
919 bool wxeditor_hexeditor_available()
921 return editor;
924 bool wxeditor_hexeditor_jumpto(uint64_t addr)
926 if(editor)
927 editor->jumpto(addr);
928 return editor;