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"
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"
28 class wxeditor_hexedit
;
32 const size_t maxvaluelen
= 8; //The length of longest value type.
33 wxeditor_hexedit
* editor
;
41 int type
; //0 => Unsigned, 1 => Signed, 2 => Float
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();
50 {"1 byte (unsigned)", 1, false, "", 0, 0, [](const uint8_t* x
) -> std::string
{
51 return (stringfmt() << (int)x
[0]).str();
53 {"1 byte (hex)", 1, false, "%02x", 0, 0, [](const uint8_t* x
) -> std::string
{
56 {"2 bytes (signed)", 2, false, "", 1, 0, [](const uint8_t* x
) -> std::string
{
57 return (stringfmt() << *(int16_t*)x
).str();
59 {"2 bytes (unsigned)", 2, false, "", 0, 0, [](const uint8_t* x
) -> std::string
{
60 return (stringfmt() << *(uint16_t*)x
).str();
62 {"2 bytes (hex)", 2, false, "%04x", 0, 0, [](const uint8_t* x
) -> std::string
{
63 return hex::to(*(uint16_t*)x
);
65 {"3 bytes (signed)", 3, true, "", 1, 0, [](const uint8_t* x
) -> std::string
{
67 a
|= (uint32_t)x
[0] << 16;
68 a
|= (uint32_t)x
[1] << 8;
72 return (stringfmt() << a
).str();
74 {"3 bytes (unsigned)", 3, true, "", 0, 0, [](const uint8_t* x
) -> std::string
{
76 a
|= (uint32_t)x
[0] << 16;
77 a
|= (uint32_t)x
[1] << 8;
79 return (stringfmt() << a
).str();
81 {"3 bytes (hex)", 3, true, "%06x", 0, 0, [](const uint8_t* x
) -> std::string
{
83 a
|= (uint32_t)x
[0] << 16;
84 a
|= (uint32_t)x
[1] << 8;
88 {"4 bytes (signed)", 4, false, "", 1, 0, [](const uint8_t* x
) -> std::string
{
89 return (stringfmt() << *(int32_t*)x
).str();
91 {"4 bytes (unsigned)", 4, false, "", 0, 0, [](const uint8_t* x
) -> std::string
{
92 return (stringfmt() << *(uint32_t*)x
).str();
94 {"4 bytes (hex)", 4, false, "%08x", 0, 0, [](const uint8_t* x
) -> std::string
{
95 return hex::to(*(uint32_t*)x
);
97 {"4 bytes (float)", 4, false, "", 2, 0, [](const uint8_t* x
) -> std::string
{
98 return (stringfmt() << *(float*)x
).str();
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
{
126 a
|= (uint32_t)x
[0] << 16;
127 a
|= (uint32_t)x
[1] << 8;
131 return (stringfmt() << a
/ 256.0).str();
133 {"Q16.8 (unsigned)", 3, true, "", 0, 8, [](const uint8_t* x
) -> std::string
{
135 a
|= (uint32_t)x
[0] << 16;
136 a
|= (uint32_t)x
[1] << 8;
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",
169 wxID_OPPOSITE_ENDIAN
= wxID_HIGHEST
+ 1,
170 wxID_DATATYPES_FIRST
,
171 wxID_DATATYPES_LAST
= wxID_DATATYPES_FIRST
+ 255,
173 wxID_REGIONS_LAST
= wxID_REGIONS_FIRST
+ 255,
175 wxID_DELETE_BOOKMARK
,
178 wxID_BOOKMARKS_FIRST
,
179 wxID_BOOKMARKS_LAST
= wxID_BOOKMARKS_FIRST
+ 255,
180 wxID_SEARCH_DISQUALIFY
,
187 class wxeditor_hexedit
: public wxFrame
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
)
195 wxBoxSizer
* top
= new wxBoxSizer(wxVERTICAL
);
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
);
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"));
241 valuemenu
->FindItem(wxID_OPPOSITE_ENDIAN
)->Check(littleendian
);
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);
284 bool ShouldPreventAppExit() const
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();
303 if(c
== WXK_LEFT
&& hex_input_state
< 0) {
304 if(hpanel
->seloff
> 0) hpanel
->seloff
--;
305 hpanel
->request_paint();
308 if(c
== WXK_RIGHT
&& hex_input_state
< 0) {
309 if(hpanel
->seloff
+ 1 < hpanel
->vmasize
) hpanel
->seloff
++;
310 hpanel
->request_paint();
313 if(c
== WXK_UP
&& hex_input_state
< 0) {
314 if(hpanel
->seloff
>= 16) hpanel
->seloff
-= 16;
315 hpanel
->request_paint();
318 if(c
== WXK_DOWN
&& hex_input_state
< 0) {
319 if(hpanel
->seloff
+ 16 < hpanel
->vmasize
) hpanel
->seloff
+= 16;
320 hpanel
->request_paint();
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();
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();
335 if(c
>= '0' && c
<= '9') {
339 if(c
>= 'A' && c
<= 'F') {
340 do_hex(c
- 'A' + 10);
343 if(c
>= 'a' && c
<= 'f') {
344 do_hex(c
- 'a' + 10);
349 void on_mouse(wxMouseEvent
& e
)
351 auto cell
= hpanel
->get_cell();
353 hpanel
->on_mouse0(e
.GetX() / cell
.first
, e
.GetY() / cell
.second
, true);
355 hpanel
->on_mouse0(e
.GetX() / cell
.first
, e
.GetY() / cell
.second
, false);
359 if(e
.ShiftDown() && e
.ControlDown())
361 scroll
->apply_wheel(e
.GetWheelRotation(), e
.GetWheelDelta(), speed
);
362 hpanel
->offset
= scroll
->get_position();
364 void on_loadbookmarks(wxCommandEvent
& e
)
367 std::string filename
= choose_file_load(this, "Load bookmarks from file", project_otherpath(),
368 filetype_hexbookmarks
);
369 auto _in
= zip::readrel(filename
, "");
370 std::string
in(_in
.begin(), _in
.end());
372 std::vector
<bookmark_entry
> newbookmarks
;
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
);
385 bookmarkmenu
->Delete(p
);
388 for(auto i
: bookmarks
) {
389 if(wxID_BOOKMARKS_FIRST
+ idx
> wxID_BOOKMARKS_LAST
)
391 bookmarkmenu
->Append(wxID_BOOKMARKS_FIRST
+ idx
, towxstring(i
.name
));
394 } catch(canceled_exception
& e
) {
395 } catch(std::exception
& e
) {
396 show_message_ok(this, "Error", std::string("Can't load bookmarks: ") + e
.what(),
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
);
412 std::string doc
= root
.serialize();
414 std::string filename
= choose_file_save(this, "Save bookmarks to file", project_otherpath(),
415 filetype_hexbookmarks
);
416 std::ofstream
out(filename
.c_str());
417 out
<< doc
<< std::endl
;
419 } catch(canceled_exception
& e
) {
420 } catch(std::exception
& e
) {
421 show_message_ok(this, "Error", std::string("Can't save bookmarks: ") + e
.what(),
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);
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
));
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]);
450 if(d2
->ShowModal() == wxID_CANCEL
) {
454 int sel
= d2
->GetSelection();
457 bookmarks
.erase(bookmarks
.begin() + sel
);
458 for(unsigned i
= wxID_BOOKMARKS_FIRST
; i
<= wxID_BOOKMARKS_LAST
; i
++) {
459 auto p
= bookmarkmenu
->FindItem(i
);
461 bookmarkmenu
->Delete(p
);
464 for(auto i
: bookmarks
) {
465 bookmarkmenu
->Append(wxID_BOOKMARKS_FIRST
+ idx
, towxstring(i
.name
));
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();
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) -
492 hpanel
->request_paint();
495 void on_search_watch(wxCommandEvent
& e
)
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());
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;
515 if(addr
>= j
->base
&& addr
< j
->base
+ j
->size
&& !j
->endian
)
518 e
.endianess
= hostendian
? 0 : (littleendian
? -1 : 1);
519 e
.scale_div
= 1ULL << datatypes
[curtype
].scale
;
520 lsnes_instance
.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();
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
;
533 hpanel
->request_paint();
536 void on_bookmark(wxCommandEvent
& e
)
539 if(id
< wxID_BOOKMARKS_FIRST
|| id
> wxID_BOOKMARKS_LAST
)
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();
546 if(j
->readonly
|| j
->special
)
548 if(j
->name
== ent
.vma
) {
553 if(ent
.sel
>= size
|| ent
.scroll
>= (ssize_t
)((size
+ 15) / 16))
554 goto invalid_bookmark
;
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();
564 show_message_ok(this, "Error jumping to bookmark", "Bookmark refers to nonexistent location",
568 void on_vmasel(wxCommandEvent
& e
)
572 int selected
= e
.GetId();
573 if(selected
< wxID_REGIONS_FIRST
|| selected
> wxID_REGIONS_LAST
)
575 selected
-= wxID_REGIONS_FIRST
;
576 auto i
= lsnes_instance
.memory
.get_regions();
579 if(j
->readonly
|| j
->special
)
581 if(index
== selected
) {
582 if(j
->base
!= hpanel
->vmabase
|| j
->size
!= hpanel
->vmasize
)
583 update_vma(j
->base
, j
->size
);
585 if(vma_endians
.count(index
)) {
586 littleendian
= vma_endians
[index
];
587 valuemenu
->FindItem(wxID_OPPOSITE_ENDIAN
)->Check(littleendian
);
596 bool is_endian_little(int endian
)
598 if(endian
< 0) return true;
599 if(endian
> 0) return false;
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
;
609 scroll
->set_range((size
+ 15) / 16);
610 scroll
->set_position(0);
611 hpanel
->request_paint();
613 void on_typechange(wxCommandEvent
& e
)
618 if(id
< wxID_DATATYPES_FIRST
|| id
> wxID_DATATYPES_LAST
)
620 curtype
= id
- wxID_DATATYPES_FIRST
;
621 hpanel
->request_paint();
623 void on_changeendian(wxCommandEvent
& e
)
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();
636 hpanel
->request_paint();
638 void jumpto(uint64_t addr
)
642 //Switch to correct VMA.
643 auto i
= lsnes_instance
.memory
.get_regions();
646 if(j
->readonly
|| j
->special
)
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
);
652 if(vma_endians
.count(index
)) {
653 littleendian
= vma_endians
[index
];
654 valuemenu
->FindItem(wxID_OPPOSITE_ENDIAN
)->Check(littleendian
);
660 if(addr
< hpanel
->vmabase
|| addr
>= hpanel
->vmabase
+ hpanel
->vmasize
)
662 hpanel
->seloff
= addr
- hpanel
->vmabase
;
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
)))
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
)
692 std::string
get_current_vma_name()
694 if(current_vma
>= vma_names
.size())
696 return vma_names
[current_vma
];
698 void on_core_changed(bool _hard
)
703 runuifun([this, hard
]() {
704 for(unsigned i
= wxID_REGIONS_FIRST
; i
<= wxID_REGIONS_LAST
; i
++) {
705 auto p
= regionmenu
->FindItem(i
);
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();
716 int curreg_index
= 0;
718 if(j
->readonly
|| j
->special
)
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
;
727 if(!vma_endians
.count(index
))
728 vma_endians
[index
] = is_endian_little(j
->endian
);
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();
748 if(hpanel
->seloff
> hpanel
->vmasize
)
750 if(hex_input_state
< 0)
751 hex_input_state
= hex
;
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
)
758 lsnes_instance
.run([addr
, byte
]() {lsnes_instance
.memory
.write
<uint8_t>(addr
, byte
); });
760 hpanel
->request_paint();
762 class _panel
: public text_framebuffer_panel
765 _panel(wxeditor_hexedit
* parent
)
766 : text_framebuffer_panel(parent
, 59, lines
= 28, wxID_ANY
, NULL
)
773 memset(value
, 0, maxvaluelen
);
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
);
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
;
788 uint8_t* _value
= value
;
789 lsnes_instance
.run([_vmabase
, _vmasize
, paint_offset
, _seloff
, _value
, _lines
, this]() {
790 memory_search
* memsearch
= wxwindow_memorysearch_active();
792 for(ssize_t j
= 0; j
< _lines
; j
++) {
793 uint64_t addr
= paint_offset
+ j
* 16;
794 if(addr
>= _vmasize
) {
796 for(size_t i
= 0; i
< get_characters().first
; i
++)
797 write(" ", 1, i
, j
, 0, 0xFFFFFF);
800 for(size_t i
= 0; i
< sizeof(separators
)/sizeof(separators
[0]); i
++) {
801 write(sepchars
[i
], 1, separators
[i
], j
, 0, 0xFFFFFF);
803 for(size_t i
= 0; i
< hexaddr
; i
++) {
804 write(hexes
[(addr
>> 4 * (hexaddr
- i
- 1)) & 15], 1, i
, j
, 0,
808 if(_vmasize
- addr
< 16)
809 bytes
= _vmasize
- addr
;
810 uint64_t laddr
= addr
+ _vmabase
;
811 for(size_t i
= 0; i
< bytes
; i
++) {
813 uint32_t bg
= 0xFFFFFF;
814 bool candidate
= (memsearch
&& memsearch
->is_candidate(laddr
+ i
));
815 if(candidate
) bg
= (bg
& 0xC0C0C0) | 0x3F0000;
816 if(addr
+ i
== _seloff
)
818 uint8_t b
= lsnes_instance
.memory
.read
<uint8_t>(laddr
+ i
);
819 if(rparent
->hex_input_state
< 0 || addr
+ i
!= seloff
821 write(hexes
[(b
>> 4) & 15], 1, hexcol
[i
], j
, fg
, bg
);
823 write(hexes
[rparent
->hex_input_state
], 1, hexcol
[i
], j
, 0xFF,
825 write(hexes
[b
& 15], 1, hexcol
[i
] + 1, j
, fg
, bg
);
826 char32_t buf
[2] = {0, 0};
827 buf
[0] = byte_to_char(b
);
828 write(buf
, 1, charcol
[i
], j
, fg
, bg
);
830 for(size_t i
= bytes
; i
< 16; i
++) {
831 write(" ", 2, hexcol
[i
], j
, 0, 0xFFFFFF);
832 write(" ", 1, charcol
[i
] + 1, j
, 0, 0xFFFFFF);
835 memset(_value
, 0, maxvaluelen
);
836 lsnes_instance
.memory
.read_range(_vmabase
+ _seloff
, _value
, maxvaluelen
);
838 rparent
->refresh_curvalue();
839 rparent
->set_search_status();
841 char32_t
byte_to_char(uint8_t ch
)
845 if((ch
& 0x60) == 0 || ch
== 127 || ch
== 0xad)
849 void on_mouse0(int x
, int y
, bool polarity
)
853 uint64_t rowaddr
= 16 * (static_cast<uint64_t>(offset
) + y
);
855 for(unsigned i
= 0; i
< 16; i
++)
856 if(x
== hexcol
[i
] || x
== hexcol
[i
] + 1 || x
== charcol
[i
])
858 if(rowaddr
+ coladdr
>= vmasize
|| coladdr
> 15)
860 seloff
= rowaddr
+ coladdr
;
863 wxeditor_hexedit
* rparent
;
868 uint8_t value
[maxvaluelen
];
872 struct bookmark_entry
880 wxMenu
* bookmarkmenu
;
882 wxComboBox
* datatype
;
884 wxStatusBar
* statusbar
;
890 struct dispatch::target
<bool> corechange
;
891 unsigned current_vma
;
892 std::vector
<std::string
> vma_names
;
893 std::map
<unsigned, bool> vma_endians
;
894 std::vector
<bookmark_entry
> bookmarks
;
901 void wxeditor_hexedit_display(wxWindow
* parent
)
906 editor
= new wxeditor_hexedit(parent
);
912 void wxeditor_hexeditor_update()
918 bool wxeditor_hexeditor_available()
923 bool wxeditor_hexeditor_jumpto(uint64_t addr
)
926 editor
->jumpto(addr
);