Fix Win32 build
[lsnes.git] / src / platform / wxwidgets / editor-hexedit.cpp
blob996d4f13df0218d27a018221169796d72802e281
1 #include <wx/wx.h>
2 #include <wx/event.h>
3 #include <wx/control.h>
4 #include <wx/combobox.h>
5 #include <wx/radiobut.h>
7 #include "core/moviedata.hpp"
8 #include "core/memorywatch.hpp"
9 #include "core/dispatch.hpp"
10 #include "core/instance.hpp"
11 #include "core/instance-map.hpp"
12 #include "core/project.hpp"
13 #include "core/memorymanip.hpp"
14 #include "core/ui-services.hpp"
15 #include "library/memorysearch.hpp"
16 #include "library/hex.hpp"
18 #include "platform/wxwidgets/platform.hpp"
19 #include "platform/wxwidgets/textrender.hpp"
20 #include "platform/wxwidgets/loadsave.hpp"
21 #include "platform/wxwidgets/scrollbar.hpp"
23 #include "library/string.hpp"
24 #include "library/json.hpp"
25 #include "library/zip.hpp"
26 #include "interface/romtype.hpp"
28 #include <iomanip>
30 class wxeditor_hexedit;
32 namespace
34 const size_t maxvaluelen = 8; //The length of longest value type.
35 instance_map<wxeditor_hexedit> editor;
37 struct val_type
39 const char* name;
40 unsigned len;
41 bool hard_bigendian;
42 const char* format;
43 int type; //0 => Unsigned, 1 => Signed, 2 => Float
44 int scale;
45 std::string (*read)(const uint8_t* x);
48 val_type datatypes[] = {
49 {"1 byte (signed)", 1, false, "", 1, 0, [](const uint8_t* x) -> std::string {
50 return (stringfmt() << (int)(char)x[0]).str();
51 }},
52 {"1 byte (unsigned)", 1, false, "", 0, 0, [](const uint8_t* x) -> std::string {
53 return (stringfmt() << (int)x[0]).str();
54 }},
55 {"1 byte (hex)", 1, false, "%02x", 0, 0, [](const uint8_t* x) -> std::string {
56 return hex::to(x[0]);
57 }},
58 {"2 bytes (signed)", 2, false, "", 1, 0, [](const uint8_t* x) -> std::string {
59 return (stringfmt() << *(int16_t*)x).str();
60 }},
61 {"2 bytes (unsigned)", 2, false, "", 0, 0, [](const uint8_t* x) -> std::string {
62 return (stringfmt() << *(uint16_t*)x).str();
63 }},
64 {"2 bytes (hex)", 2, false, "%04x", 0, 0, [](const uint8_t* x) -> std::string {
65 return hex::to(*(uint16_t*)x);
66 }},
67 {"3 bytes (signed)", 3, true, "", 1, 0, [](const uint8_t* x) -> std::string {
68 int32_t a = 0;
69 a |= (uint32_t)x[0] << 16;
70 a |= (uint32_t)x[1] << 8;
71 a |= (uint32_t)x[2];
72 if(a & 0x800000)
73 a -= 0x1000000;
74 return (stringfmt() << a).str();
75 }},
76 {"3 bytes (unsigned)", 3, true, "", 0, 0, [](const uint8_t* x) -> std::string {
77 int32_t a = 0;
78 a |= (uint32_t)x[0] << 16;
79 a |= (uint32_t)x[1] << 8;
80 a |= (uint32_t)x[2];
81 return (stringfmt() << a).str();
82 }},
83 {"3 bytes (hex)", 3, true, "%06x", 0, 0, [](const uint8_t* x) -> std::string {
84 int32_t a = 0;
85 a |= (uint32_t)x[0] << 16;
86 a |= (uint32_t)x[1] << 8;
87 a |= (uint32_t)x[2];
88 return hex::to24(a);
89 }},
90 {"4 bytes (signed)", 4, false, "", 1, 0, [](const uint8_t* x) -> std::string {
91 return (stringfmt() << *(int32_t*)x).str();
92 }},
93 {"4 bytes (unsigned)", 4, false, "", 0, 0, [](const uint8_t* x) -> std::string {
94 return (stringfmt() << *(uint32_t*)x).str();
95 }},
96 {"4 bytes (hex)", 4, false, "%08x", 0, 0, [](const uint8_t* x) -> std::string {
97 return hex::to(*(uint32_t*)x);
98 }},
99 {"4 bytes (float)", 4, false, "", 2, 0, [](const uint8_t* x) -> std::string {
100 return (stringfmt() << *(float*)x).str();
102 {"8 bytes (signed)", 8, false, "", 1, 0, [](const uint8_t* x) -> std::string {
103 return (stringfmt() << *(int64_t*)x).str();
105 {"8 bytes (unsigned)", 8, false, "", 0, 0, [](const uint8_t* x) -> std::string {
106 return (stringfmt() << *(uint64_t*)x).str();
108 {"8 bytes (hex)", 8, false, "%016x", 0, 0, [](const uint8_t* x) -> std::string {
109 return hex::to(*(uint64_t*)x);
111 {"8 bytes (float)", 8, false, "", 2, 0, [](const uint8_t* x) -> std::string {
112 return (stringfmt() << *(double*)x).str();
114 {"Q8.8 (signed)", 2, false, "", 1, 8, [](const uint8_t* x) -> std::string {
115 return (stringfmt() << *(int16_t*)x / 256.0).str();
117 {"Q8.8 (unsigned)", 2, false, "", 0, 8, [](const uint8_t* x) -> std::string {
118 return (stringfmt() << *(uint16_t*)x / 256.0).str();
120 {"Q12.4 (signed)", 2, false, "", 1, 4, [](const uint8_t* x) -> std::string {
121 return (stringfmt() << *(int16_t*)x / 16.0).str();
123 {"Q12.4 (unsigned)", 2, false, "", 0, 4, [](const uint8_t* x) -> std::string {
124 return (stringfmt() << *(uint16_t*)x / 16.0).str();
126 {"Q16.8 (signed)", 3, true, "", 1, 8, [](const uint8_t* x) -> std::string {
127 int32_t a = 0;
128 a |= (uint32_t)x[0] << 16;
129 a |= (uint32_t)x[1] << 8;
130 a |= (uint32_t)x[2];
131 if(a & 0x800000)
132 a -= 0x1000000;
133 return (stringfmt() << a / 256.0).str();
135 {"Q16.8 (unsigned)", 3, true, "", 0, 8, [](const uint8_t* x) -> std::string {
136 int32_t a = 0;
137 a |= (uint32_t)x[0] << 16;
138 a |= (uint32_t)x[1] << 8;
139 a |= (uint32_t)x[2];
140 return (stringfmt() << a / 256.0).str();
142 {"Q24.8 (signed)", 4, false, "", 1, 8, [](const uint8_t* x) -> std::string {
143 return (stringfmt() << *(int32_t*)x / 256.0).str();
145 {"Q24.8 (unsigned)", 4, false, "", 0, 8, [](const uint8_t* x) -> std::string {
146 return (stringfmt() << *(uint32_t*)x / 256.0).str();
148 {"Q20.12 (signed)", 4, false, "", 1, 12, [](const uint8_t* x) -> std::string {
149 return (stringfmt() << *(int32_t*)x / 4096.0).str();
151 {"Q20.12 (unsigned)", 4, false, "", 0, 12, [](const uint8_t* x) -> std::string {
152 return (stringfmt() << *(uint32_t*)x / 4096.0).str();
154 {"Q16.16 (signed)", 4, false, "", 1, 16, [](const uint8_t* x) -> std::string {
155 return (stringfmt() << *(int32_t*)x / 65536.0).str();
157 {"Q16.16 (unsigned)", 4, false, "", 0, 16, [](const uint8_t* x) -> std::string {
158 return (stringfmt() << *(uint32_t*)x / 65536.0).str();
162 unsigned hexaddr = 6;
163 int separators[5] = {6, 15, 24, 28, 42};
164 const char32_t* sepchars[5] = {U"\u2502", U" ", U".", U" ", U"\u2502"};
165 int hexcol[16] = {7, 9, 11, 13, 16, 18, 20, 22, 25, 27, 29, 31, 34, 36, 38, 40};
166 int charcol[16] = {43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58};
167 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",
168 U"D", U"E", U"F"};
170 enum {
171 wxID_OPPOSITE_ENDIAN = wxID_HIGHEST + 1,
172 wxID_DATATYPES_FIRST,
173 wxID_DATATYPES_LAST = wxID_DATATYPES_FIRST + 255,
174 wxID_REGIONS_FIRST,
175 wxID_REGIONS_LAST = wxID_REGIONS_FIRST + 255,
176 wxID_ADD_BOOKMARK,
177 wxID_DELETE_BOOKMARK,
178 wxID_LOAD_BOOKMARKS,
179 wxID_SAVE_BOOKMARKS,
180 wxID_BOOKMARKS_FIRST,
181 wxID_BOOKMARKS_LAST = wxID_BOOKMARKS_FIRST + 255,
182 wxID_SEARCH_DISQUALIFY,
183 wxID_SEARCH_PREV,
184 wxID_SEARCH_NEXT,
185 wxID_SEARCH_WATCH,
189 class wxeditor_hexedit : public wxFrame
191 public:
192 wxeditor_hexedit(emulator_instance& _inst, wxWindow* parent)
193 : wxFrame(parent, wxID_ANY, wxT("lsnes: Memory editor"), wxDefaultPosition, wxSize(-1, -1),
194 wxCAPTION | wxMINIMIZE_BOX | wxCLOSE_BOX | wxSYSTEM_MENU), inst(_inst)
196 CHECK_UI_THREAD;
197 Centre();
198 wxBoxSizer* top = new wxBoxSizer(wxVERTICAL);
199 SetSizer(top);
201 destructing = false;
202 hex_input_state = -1;
203 current_vma = 0;
205 Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this);
207 wxBoxSizer* parea = new wxBoxSizer(wxHORIZONTAL);
208 parea->Add(hpanel = new _panel(this, inst), 1, wxGROW);
209 hpanel->SetFocus();
210 parea->Add(scroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
211 top->Add(parea, 1, wxGROW);
212 scroll->Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, this);
214 SetStatusBar(statusbar = new wxStatusBar(this));
215 SetMenuBar(menubar = new wxMenuBar);
217 valuemenu = new wxMenu();
218 menubar->Append(valuemenu, wxT("Value"));
219 regionmenu = new wxMenu();
220 menubar->Append(regionmenu, wxT("Region"));
221 typemenu = new wxMenu();
222 bookmarkmenu = new wxMenu();
223 bookmarkmenu->Append(wxID_ADD_BOOKMARK, wxT("Add bookmark..."));
224 bookmarkmenu->Append(wxID_DELETE_BOOKMARK, wxT("Delete bookmark..."));
225 bookmarkmenu->AppendSeparator();
226 bookmarkmenu->Append(wxID_LOAD_BOOKMARKS, wxT("Load bookmarks..."));
227 bookmarkmenu->Append(wxID_SAVE_BOOKMARKS, wxT("Save bookmarks..."));
228 bookmarkmenu->AppendSeparator();
229 menubar->Append(bookmarkmenu, wxT("Bookmarks"));
230 valuemenu->AppendSubMenu(typemenu, wxT("Type"));
231 oendian = valuemenu->AppendCheckItem(wxID_OPPOSITE_ENDIAN, wxT("Little endian"));
232 for(size_t i = 0; i < sizeof(datatypes) / sizeof(datatypes[0]); i++)
233 typemenu->AppendRadioItem(wxID_DATATYPES_FIRST + i, towxstring(datatypes[i].name));
234 typemenu->FindItem(wxID_DATATYPES_FIRST)->Check();
235 searchmenu = new wxMenu();
236 menubar->Append(searchmenu, wxT("Search"));
237 searchmenu->Append(wxID_SEARCH_PREV, wxT("Previous...\tCtrl+P"));
238 searchmenu->Append(wxID_SEARCH_NEXT, wxT("Next...\tCtrl+N"));
239 searchmenu->Append(wxID_SEARCH_WATCH, wxT("Add watch...\tCtrl+W"));
240 searchmenu->AppendSeparator();
241 searchmenu->Append(wxID_SEARCH_DISQUALIFY, wxT("Disqualify...\tCtrl+D"));
242 set_search_status();
244 littleendian = true;
245 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
246 curtype = 0;
247 Connect(wxID_ADD_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED,
248 wxCommandEventHandler(wxeditor_hexedit::on_addbookmark));
249 Connect(wxID_DELETE_BOOKMARK, wxEVT_COMMAND_MENU_SELECTED,
250 wxCommandEventHandler(wxeditor_hexedit::on_deletebookmark));
251 Connect(wxID_LOAD_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED,
252 wxCommandEventHandler(wxeditor_hexedit::on_loadbookmarks));
253 Connect(wxID_SAVE_BOOKMARKS, wxEVT_COMMAND_MENU_SELECTED,
254 wxCommandEventHandler(wxeditor_hexedit::on_savebookmarks));
255 Connect(wxID_OPPOSITE_ENDIAN, wxEVT_COMMAND_MENU_SELECTED,
256 wxCommandEventHandler(wxeditor_hexedit::on_changeendian));
257 Connect(wxID_DATATYPES_FIRST, wxID_DATATYPES_LAST, wxEVT_COMMAND_MENU_SELECTED,
258 wxCommandEventHandler(wxeditor_hexedit::on_typechange));
259 Connect(wxID_REGIONS_FIRST, wxID_REGIONS_LAST, wxEVT_COMMAND_MENU_SELECTED,
260 wxCommandEventHandler(wxeditor_hexedit::on_vmasel));
261 Connect(wxID_BOOKMARKS_FIRST, wxID_BOOKMARKS_LAST, wxEVT_COMMAND_MENU_SELECTED,
262 wxCommandEventHandler(wxeditor_hexedit::on_bookmark));
263 Connect(wxID_SEARCH_DISQUALIFY, wxEVT_COMMAND_MENU_SELECTED,
264 wxCommandEventHandler(wxeditor_hexedit::on_search_discard));
265 Connect(wxID_SEARCH_PREV, wxEVT_COMMAND_MENU_SELECTED,
266 wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext));
267 Connect(wxID_SEARCH_NEXT, wxEVT_COMMAND_MENU_SELECTED,
268 wxCommandEventHandler(wxeditor_hexedit::on_search_prevnext));
269 Connect(wxID_SEARCH_WATCH, wxEVT_COMMAND_MENU_SELECTED,
270 wxCommandEventHandler(wxeditor_hexedit::on_search_watch));
272 scroll->set_page_size(hpanel->lines);
273 scroll->set_handler([this](scroll_bar& s) {
274 this->hpanel->offset = s.get_position();
275 this->hpanel->request_paint();
278 corechange.set(inst.dispatch->core_changed, [this](bool hard) {
279 this->on_core_changed(hard); });
280 on_core_changed(true);
281 top->SetSizeHints(this);
282 Fit();
284 ~wxeditor_hexedit()
286 destructing = true;
287 editor.remove(inst);
289 bool ShouldPreventAppExit() const
291 return false;
293 void set_search_status()
295 CHECK_UI_THREAD;
296 bool e = wxwindow_memorysearch_active(inst);
297 searchmenu->FindItem(wxID_SEARCH_DISQUALIFY)->Enable(e);
298 searchmenu->FindItem(wxID_SEARCH_PREV)->Enable(e);
299 searchmenu->FindItem(wxID_SEARCH_NEXT)->Enable(e);
301 void on_keyboard(wxKeyEvent& e)
303 CHECK_UI_THREAD;
304 int c = e.GetKeyCode();
305 if(c == WXK_ESCAPE) {
306 hex_input_state = -1;
307 hpanel->request_paint();
308 return;
310 if(c == WXK_LEFT && hex_input_state < 0) {
311 if(hpanel->seloff > 0) hpanel->seloff--;
312 hpanel->request_paint();
313 return;
315 if(c == WXK_RIGHT && hex_input_state < 0) {
316 if(hpanel->seloff + 1 < hpanel->vmasize) hpanel->seloff++;
317 hpanel->request_paint();
318 return;
320 if(c == WXK_UP && hex_input_state < 0) {
321 if(hpanel->seloff >= 16) hpanel->seloff -= 16;
322 hpanel->request_paint();
323 return;
325 if(c == WXK_DOWN && hex_input_state < 0) {
326 if(hpanel->seloff + 16 < hpanel->vmasize) hpanel->seloff += 16;
327 hpanel->request_paint();
328 return;
330 if(c == WXK_PAGEUP && hex_input_state < 0) {
331 scroll->apply_delta(-static_cast<int>(hpanel->lines));
332 hpanel->offset = scroll->get_position();
333 hpanel->request_paint();
334 return;
336 if(c == WXK_PAGEDOWN && hex_input_state < 0) {
337 scroll->apply_delta(static_cast<int>(hpanel->lines));
338 hpanel->offset = scroll->get_position();
339 hpanel->request_paint();
340 return;
342 if(c >= '0' && c <= '9') {
343 do_hex(c - '0');
344 return;
346 if(c >= 'A' && c <= 'F') {
347 do_hex(c - 'A' + 10);
348 return;
350 if(c >= 'a' && c <= 'f') {
351 do_hex(c - 'a' + 10);
352 return;
354 e.Skip();
356 void on_mouse(wxMouseEvent& e)
358 CHECK_UI_THREAD;
359 auto cell = hpanel->get_cell();
360 if(e.LeftDown())
361 hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true);
362 if(e.LeftUp())
363 hpanel->on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false);
364 unsigned speed = 1;
365 if(e.ShiftDown())
366 speed = 10;
367 if(e.ShiftDown() && e.ControlDown())
368 speed = 50;
369 scroll->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
370 hpanel->offset = scroll->get_position();
372 void on_loadbookmarks(wxCommandEvent& e)
374 CHECK_UI_THREAD;
375 try {
376 std::string filename = choose_file_load(this, "Load bookmarks from file",
377 UI_get_project_otherpath(inst), filetype_hexbookmarks);
378 auto _in = zip::readrel(filename, "");
379 std::string in(_in.begin(), _in.end());
380 JSON::node root(in);
381 std::vector<bookmark_entry> newbookmarks;
382 for(auto i : root) {
383 bookmark_entry e;
384 e.name = i["name"].as_string8();
385 e.vma = i["vma"].as_string8();
386 e.scroll = i["offset"].as_int();
387 e.sel = i["selected"].as_uint();
388 newbookmarks.push_back(e);
390 std::swap(bookmarks, newbookmarks);
391 for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) {
392 auto p = bookmarkmenu->FindItem(i);
393 if(p)
394 bookmarkmenu->Delete(p);
396 int idx = 0;
397 for(auto i : bookmarks) {
398 if(wxID_BOOKMARKS_FIRST + idx > wxID_BOOKMARKS_LAST)
399 break;
400 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name));
401 idx++;
403 } catch(canceled_exception& e) {
404 } catch(std::exception& e) {
405 show_message_ok(this, "Error", std::string("Can't load bookmarks: ") + e.what(),
406 wxICON_EXCLAMATION);
407 return;
410 void on_savebookmarks(wxCommandEvent& e)
412 CHECK_UI_THREAD;
413 JSON::node root(JSON::array);
414 for(auto i : bookmarks) {
415 JSON::node n(JSON::object);
416 n["name"] = JSON::string(i.name);
417 n["vma"] = JSON::string(i.vma);
418 n["offset"] = JSON::number((int64_t)i.scroll);
419 n["selected"] = JSON::number(i.sel);
420 root.append(n);
422 std::string doc = root.serialize();
423 try {
424 std::string filename = choose_file_save(this, "Save bookmarks to file",
425 UI_get_project_otherpath(inst), filetype_hexbookmarks);
426 std::ofstream out(filename.c_str());
427 out << doc << std::endl;
428 out.close();
429 } catch(canceled_exception& e) {
430 } catch(std::exception& e) {
431 show_message_ok(this, "Error", std::string("Can't save bookmarks: ") + e.what(),
432 wxICON_EXCLAMATION);
435 void on_addbookmark(wxCommandEvent& e)
437 CHECK_UI_THREAD;
438 if(bookmarks.size() <= wxID_BOOKMARKS_LAST - wxID_BOOKMARKS_FIRST) {
439 std::string name = pick_text(this, "Add bookmark", "Enter name for bookmark", "", false);
440 bookmark_entry ent;
441 ent.name = name;
442 ent.vma = get_current_vma_name();
443 ent.scroll = hpanel->offset;
444 ent.sel = hpanel->seloff;
445 int idx = bookmarks.size();
446 bookmarks.push_back(ent);
447 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(name));
448 } else {
449 show_message_ok(this, "Error adding bookmark", "Too many bookmarks", wxICON_EXCLAMATION);
452 void on_deletebookmark(wxCommandEvent& e)
454 CHECK_UI_THREAD;
455 if(bookmarks.size() > 0) {
456 std::vector<wxString> _choices;
457 for(auto i : bookmarks)
458 _choices.push_back(towxstring(i.name));
459 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(this, towxstring("Select bookmark "
460 "to delete"), towxstring("Delete bookmark"), _choices.size(), &_choices[0]);
461 d2->SetSelection(0);
462 if(d2->ShowModal() == wxID_CANCEL) {
463 d2->Destroy();
464 return;
466 int sel = d2->GetSelection();
467 d2->Destroy();
468 if(sel >= 0)
469 bookmarks.erase(bookmarks.begin() + sel);
470 for(unsigned i = wxID_BOOKMARKS_FIRST; i <= wxID_BOOKMARKS_LAST; i++) {
471 auto p = bookmarkmenu->FindItem(i);
472 if(p)
473 bookmarkmenu->Delete(p);
475 int idx = 0;
476 for(auto i : bookmarks) {
477 bookmarkmenu->Append(wxID_BOOKMARKS_FIRST + idx, towxstring(i.name));
478 idx++;
482 void rescroll_panel()
484 CHECK_UI_THREAD;
485 uint64_t vfirst = static_cast<uint64_t>(hpanel->offset) * 16;
486 uint64_t vlast = static_cast<uint64_t>(hpanel->offset + hpanel->lines) * 16;
487 if(hpanel->seloff < vfirst || hpanel->seloff >= vlast) {
488 int l = hpanel->seloff / 16;
489 int r = hpanel->lines / 4;
490 hpanel->offset = (l > r) ? (l - r) : 0;
491 scroll->set_position(hpanel->offset);
494 void on_search_discard(wxCommandEvent& e)
496 CHECK_UI_THREAD;
497 auto p = wxwindow_memorysearch_active(inst);
498 if(!p)
499 return;
500 if(hpanel->seloff < hpanel->vmasize) {
501 p->dq_range(hpanel->vmabase + hpanel->seloff, hpanel->vmabase + hpanel->seloff);
502 wxwindow_memorysearch_update(inst);
503 hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, true) -
504 hpanel->vmabase;
505 rescroll_panel();
506 hpanel->request_paint();
509 void on_search_watch(wxCommandEvent& e)
511 CHECK_UI_THREAD;
512 try {
513 if(!hpanel->vmasize)
514 return;
515 uint64_t addr = hpanel->vmabase + hpanel->seloff;
516 std::string n = pick_text(this, "Name for watch", (stringfmt()
517 << "Enter name for watch at 0x" << std::hex << addr << ":").str());
518 if(n == "")
519 return;
520 memwatch_item e;
521 e.expr = (stringfmt() << addr).str();
522 e.format = datatypes[curtype].format;
523 e.bytes = datatypes[curtype].len;
524 e.signed_flag = (datatypes[curtype].type == 1);
525 e.float_flag = (datatypes[curtype].type == 2);
526 //Handle hostendian VMAs.
527 auto i = inst.memory->get_regions();
528 bool hostendian = false;
529 for(auto& j : i) {
530 if(addr >= j->base && addr < j->base + j->size && !j->endian)
531 hostendian = true;
533 e.endianess = hostendian ? 0 : (littleendian ? -1 : 1);
534 e.scale_div = 1ULL << datatypes[curtype].scale;
535 inst.iqueue->run([n, &e]() { CORE().mwatch->set(n, e); });
536 } catch(canceled_exception& e) {
539 void on_search_prevnext(wxCommandEvent& e)
541 CHECK_UI_THREAD;
542 auto p = wxwindow_memorysearch_active(inst);
543 if(!p)
544 return;
545 if(hpanel->seloff < hpanel->vmasize) {
546 hpanel->seloff = p->cycle_candidate_vma(hpanel->vmabase + hpanel->seloff, e.GetId() ==
547 wxID_SEARCH_NEXT) - hpanel->vmabase;
548 rescroll_panel();
549 hpanel->request_paint();
552 void on_bookmark(wxCommandEvent& e)
554 CHECK_UI_THREAD;
555 int id = e.GetId();
556 if(id < wxID_BOOKMARKS_FIRST || id > wxID_BOOKMARKS_LAST)
557 return;
558 bookmark_entry ent = bookmarks[id - wxID_BOOKMARKS_FIRST];
559 int r = vma_index_for_name(ent.vma);
560 uint64_t base = 0, size = 0;
561 auto i = inst.memory->get_regions();
562 for(auto j : i) {
563 if(j->readonly || j->special)
564 continue;
565 if(j->name == ent.vma) {
566 base = j->base;
567 size = j->size;
570 if(ent.sel >= size || ent.scroll >= (ssize_t)((size + 15) / 16))
571 goto invalid_bookmark;
572 current_vma = r;
573 regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma)->Check();
574 update_vma(base, size);
575 hpanel->offset = ent.scroll;
576 hpanel->seloff = ent.sel;
577 scroll->set_position(hpanel->offset);
578 hpanel->request_paint();
579 return;
580 invalid_bookmark:
581 show_message_ok(this, "Error jumping to bookmark", "Bookmark refers to nonexistent location",
582 wxICON_EXCLAMATION);
583 return;
585 void on_vmasel(wxCommandEvent& e)
587 CHECK_UI_THREAD;
588 if(destructing)
589 return;
590 int selected = e.GetId();
591 if(selected < wxID_REGIONS_FIRST || selected > wxID_REGIONS_LAST)
592 return;
593 selected -= wxID_REGIONS_FIRST;
594 auto i = inst.memory->get_regions();
595 int index = 0;
596 for(auto j : i) {
597 if(j->readonly || j->special)
598 continue;
599 if(index == selected) {
600 if(j->base != hpanel->vmabase || j->size != hpanel->vmasize)
601 update_vma(j->base, j->size);
602 current_vma = index;
603 if(vma_endians.count(index)) {
604 littleendian = vma_endians[index];
605 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
607 return;
609 index++;
611 current_vma = index;
612 update_vma(0, 0);
614 bool is_endian_little(int endian)
616 if(endian < 0) return true;
617 if(endian > 0) return false;
618 uint16_t magic = 1;
619 return (*reinterpret_cast<uint8_t*>(&magic) == 1);
621 void update_vma(uint64_t base, uint64_t size)
623 CHECK_UI_THREAD;
624 hpanel->vmabase = base;
625 hpanel->vmasize = size;
626 hpanel->offset = 0;
627 hpanel->seloff = 0;
628 scroll->set_range((size + 15) / 16);
629 scroll->set_position(0);
630 hpanel->request_paint();
632 void on_typechange(wxCommandEvent& e)
634 CHECK_UI_THREAD;
635 if(destructing)
636 return;
637 int id = e.GetId();
638 if(id < wxID_DATATYPES_FIRST || id > wxID_DATATYPES_LAST)
639 return;
640 curtype = id - wxID_DATATYPES_FIRST;
641 hpanel->request_paint();
643 void on_changeendian(wxCommandEvent& e)
645 CHECK_UI_THREAD;
646 if(destructing)
647 return;
648 littleendian = valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->IsChecked();
649 if(vma_endians.count(current_vma))
650 vma_endians[current_vma] = littleendian;
651 hpanel->request_paint();
653 void updated()
655 CHECK_UI_THREAD;
656 if(destructing)
657 return;
658 hpanel->request_paint();
660 void jumpto(uint64_t addr)
662 CHECK_UI_THREAD;
663 if(destructing)
664 return;
665 //Switch to correct VMA.
666 auto i = inst.memory->get_regions();
667 int index = 0;
668 for(auto j : i) {
669 if(j->readonly || j->special)
670 continue;
671 if(addr >= j->base && addr < j->base + j->size) {
672 if(j->base != hpanel->vmabase || j->size != hpanel->vmasize)
673 update_vma(j->base, j->size);
674 current_vma = index;
675 if(vma_endians.count(index)) {
676 littleendian = vma_endians[index];
677 valuemenu->FindItem(wxID_OPPOSITE_ENDIAN)->Check(littleendian);
679 break;
681 index++;
683 if(addr < hpanel->vmabase || addr >= hpanel->vmabase + hpanel->vmasize)
684 return;
685 hpanel->seloff = addr - hpanel->vmabase;
686 rescroll_panel();
687 hpanel->request_paint();
689 void refresh_curvalue()
691 CHECK_UI_THREAD;
692 uint8_t buf[maxvaluelen];
693 memcpy(buf, hpanel->value, maxvaluelen);
694 val_type vt = datatypes[curtype];
695 if(littleendian != is_endian_little(vt.hard_bigendian ? 1 : 0))
696 for(unsigned i = 0; i < vt.len / 2; i++)
697 std::swap(buf[i], buf[vt.len - i - 1]);
698 wxMenuItem* it = regionmenu->FindItem(wxID_REGIONS_FIRST + current_vma);
699 std::string vma = "(none)";
700 if(it) vma = tostdstring(it->GetItemLabelText());
701 unsigned addrlen = 1;
702 while(hpanel->vmasize > (1ULL << (4 * addrlen)))
703 addrlen++;
704 std::string addr = (stringfmt() << std::hex << std::setw(addrlen) << std::setfill('0') <<
705 hpanel->seloff).str();
706 std::string vtext = vt.read(buf);
707 statusbar->SetStatusText(towxstring("Region: " + vma + " Address: " + addr + " Value: " + vtext));
709 int vma_index_for_name(const std::string& x)
711 for(size_t i = 0; i < vma_names.size(); i++)
712 if(vma_names[i] == x)
713 return i;
714 return -1;
716 std::string get_current_vma_name()
718 if(current_vma >= vma_names.size())
719 return "";
720 return vma_names[current_vma];
722 void on_core_changed(bool _hard)
724 if(destructing)
725 return;
726 bool hard = _hard;
727 runuifun([this, hard]() {
728 for(unsigned i = wxID_REGIONS_FIRST; i <= wxID_REGIONS_LAST; i++) {
729 auto p = regionmenu->FindItem(i);
730 if(p)
731 regionmenu->Delete(p);
733 std::string current_reg = get_current_vma_name();
734 uint64_t nsbase = 0, nssize = 0;
735 auto i = inst.memory->get_regions();
736 vma_names.clear();
737 if(hard)
738 vma_endians.clear();
739 int index = 0;
740 int curreg_index = 0;
741 for(auto j : i) {
742 if(j->readonly || j->special)
743 continue;
744 regionmenu->AppendRadioItem(wxID_REGIONS_FIRST + index, towxstring(j->name));
745 vma_names.push_back(j->name);
746 if(j->name == current_reg || index == 0) {
747 curreg_index = index;
748 nsbase = j->base;
749 nssize = j->size;
751 if(!vma_endians.count(index))
752 vma_endians[index] = is_endian_little(j->endian);
753 index++;
755 if(!index) {
756 update_vma(0, 0);
757 return;
759 regionmenu->FindItem(wxID_REGIONS_FIRST + curreg_index)->Check();
760 current_vma = curreg_index;
761 if(vma_endians.count(current_vma)) {
762 littleendian = vma_endians[current_vma];
763 typemenu->FindItem(wxID_DATATYPES_FIRST)->Check(littleendian);
765 if(nsbase != hpanel->vmabase || nssize != hpanel->vmasize)
766 update_vma(nsbase, nssize);
767 hpanel->request_paint();
770 void do_hex(int hex)
772 if(hpanel->seloff > hpanel->vmasize)
773 return;
774 if(hex_input_state < 0)
775 hex_input_state = hex;
776 else {
777 uint8_t byte = hex_input_state * 16 + hex;
778 uint64_t addr = hpanel->vmabase + hpanel->seloff;
779 hex_input_state = -1;
780 if(hpanel->seloff + 1 < hpanel->vmasize)
781 hpanel->seloff++;
782 inst.iqueue->run([addr, byte]() {
783 CORE().memory->write<uint8_t>(addr, byte);
786 hpanel->request_paint();
788 class _panel : public text_framebuffer_panel
790 public:
791 _panel(wxeditor_hexedit* parent, emulator_instance& _inst)
792 : text_framebuffer_panel(parent, 59, lines = 28, wxID_ANY, NULL), inst(_inst)
794 CHECK_UI_THREAD;
795 rparent = parent;
796 vmabase = 0;
797 vmasize = 0;
798 offset = 0;
799 seloff = 0;
800 memset(value, 0, maxvaluelen);
801 clear();
802 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
803 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
804 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(wxeditor_hexedit::on_mouse), NULL, parent);
805 Connect(wxEVT_CHAR, wxKeyEventHandler(wxeditor_hexedit::on_keyboard), NULL, parent);
806 request_paint();
808 void prepare_paint()
810 CHECK_UI_THREAD;
811 uint64_t paint_offset = static_cast<uint64_t>(offset) * 16;
812 uint64_t _vmabase = vmabase;
813 uint64_t _vmasize = vmasize;
814 uint64_t _seloff = seloff;
815 int _lines = lines;
816 uint8_t* _value = value;
817 inst.iqueue->run([_vmabase, _vmasize, paint_offset, _seloff, _value, _lines,
818 this]() {
819 memory_search* memsearch = wxwindow_memorysearch_active(inst);
820 //Paint the stuff
821 for(ssize_t j = 0; j < _lines; j++) {
822 uint64_t addr = paint_offset + j * 16;
823 if(addr >= _vmasize) {
824 //Past-the-end.
825 for(size_t i = 0; i < get_characters().first; i++)
826 write(" ", 1, i, j, 0, 0xFFFFFF);
827 continue;
829 for(size_t i = 0; i < sizeof(separators)/sizeof(separators[0]); i++) {
830 write(sepchars[i], 1, separators[i], j, 0, 0xFFFFFF);
832 for(size_t i = 0; i < hexaddr; i++) {
833 write(hexes[(addr >> 4 * (hexaddr - i - 1)) & 15], 1, i, j, 0,
834 0xFFFFFF);
836 size_t bytes = 16;
837 if(_vmasize - addr < 16)
838 bytes = _vmasize - addr;
839 uint64_t laddr = addr + _vmabase;
840 for(size_t i = 0; i < bytes; i++) {
841 uint32_t fg = 0;
842 uint32_t bg = 0xFFFFFF;
843 bool candidate = (memsearch && memsearch->is_candidate(laddr + i));
844 if(candidate) bg = (bg & 0xC0C0C0) | 0x3F0000;
845 if(addr + i == _seloff)
846 std::swap(fg, bg);
847 uint8_t b = inst.memory->read<uint8_t>(laddr + i);
848 if(rparent->hex_input_state < 0 || addr + i != seloff
850 write(hexes[(b >> 4) & 15], 1, hexcol[i], j, fg, bg);
851 else
852 write(hexes[rparent->hex_input_state], 1, hexcol[i], j, 0xFF,
854 write(hexes[b & 15], 1, hexcol[i] + 1, j, fg, bg);
855 char32_t buf[2] = {0, 0};
856 buf[0] = byte_to_char(b);
857 write(buf, 1, charcol[i], j, fg, bg);
859 for(size_t i = bytes; i < 16; i++) {
860 write(" ", 2, hexcol[i], j, 0, 0xFFFFFF);
861 write(" ", 1, charcol[i] + 1, j, 0, 0xFFFFFF);
864 memset(_value, 0, maxvaluelen);
865 inst.memory->read_range(_vmabase + _seloff, _value, maxvaluelen);
867 rparent->refresh_curvalue();
868 rparent->set_search_status();
870 char32_t byte_to_char(uint8_t ch)
872 if(ch == 160)
873 return U' ';
874 if((ch & 0x60) == 0 || ch == 127 || ch == 0xad)
875 return U'.';
876 return ch;
878 void on_mouse0(int x, int y, bool polarity)
880 CHECK_UI_THREAD;
881 if(!polarity)
882 return;
883 uint64_t rowaddr = 16 * (static_cast<uint64_t>(offset) + y);
884 int coladdr = 16;
885 for(unsigned i = 0; i < 16; i++)
886 if(x == hexcol[i] || x == hexcol[i] + 1 || x == charcol[i])
887 coladdr = i;
888 if(rowaddr + coladdr >= vmasize || coladdr > 15)
889 return;
890 seloff = rowaddr + coladdr;
891 request_paint();
893 emulator_instance& inst;
894 wxeditor_hexedit* rparent;
895 int offset;
896 uint64_t vmabase;
897 uint64_t vmasize;
898 uint64_t seloff;
899 uint8_t value[maxvaluelen];
900 int lines;
902 private:
903 struct bookmark_entry
905 std::string name;
906 std::string vma;
907 int scroll;
908 uint64_t sel;
910 emulator_instance& inst;
911 wxMenu* regionmenu;
912 wxMenu* bookmarkmenu;
913 wxMenu* searchmenu;
914 wxComboBox* datatype;
915 wxMenuItem* oendian;
916 wxStatusBar* statusbar;
917 wxMenuBar* menubar;
918 scroll_bar* scroll;
919 _panel* hpanel;
920 wxMenu* valuemenu;
921 wxMenu* typemenu;
922 struct dispatch::target<bool> corechange;
923 unsigned current_vma;
924 std::vector<std::string> vma_names;
925 std::map<unsigned, bool> vma_endians;
926 std::vector<bookmark_entry> bookmarks;
927 bool destructing;
928 unsigned curtype;
929 bool littleendian;
930 int hex_input_state;
933 void wxeditor_hexedit_display(wxWindow* parent, emulator_instance& inst)
935 CHECK_UI_THREAD;
936 auto e = editor.lookup(inst);
937 if(e) {
938 e->Raise();
939 return;
941 try {
942 editor.create(inst, parent)->Show();
943 } catch(...) {
947 void wxeditor_hexeditor_update(emulator_instance& inst)
949 CHECK_UI_THREAD;
950 auto e = editor.lookup(inst);
951 if(e) e->updated();
954 bool wxeditor_hexeditor_available(emulator_instance& inst)
956 return editor.exists(inst);
959 bool wxeditor_hexeditor_jumpto(emulator_instance& inst, uint64_t addr)
961 CHECK_UI_THREAD;
962 auto e = editor.lookup(inst);
963 if(e) {
964 e->jumpto(addr);
965 return true;
966 } else
967 return false;