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"
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"
26 class wxeditor_hexedit
;
30 const size_t maxvaluelen
= 8; //The length of longest value type.
31 wxeditor_hexedit
* editor
;
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();
46 {"1 byte (unsigned)", 1, false, "B", [](const uint8_t* x
) -> std::string
{
47 return (stringfmt() << (int)x
[0]).str();
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();
52 {"2 bytes (signed)", 2, false, "w", [](const uint8_t* x
) -> std::string
{
53 return (stringfmt() << *(int16_t*)x
).str();
55 {"2 bytes (unsigned)", 2, false, "W", [](const uint8_t* x
) -> std::string
{
56 return (stringfmt() << *(uint16_t*)x
).str();
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();
61 {"3 bytes (signed)", 3, true, "o", [](const uint8_t* x
) -> std::string
{
63 a
|= (uint32_t)x
[0] << 16;
64 a
|= (uint32_t)x
[1] << 8;
68 return (stringfmt() << a
).str();
70 {"3 bytes (unsigned)", 3, true, "O", [](const uint8_t* x
) -> std::string
{
72 a
|= (uint32_t)x
[0] << 16;
73 a
|= (uint32_t)x
[1] << 8;
75 return (stringfmt() << a
).str();
77 {"3 bytes (hex)", 3, true, "OH6", [](const uint8_t* x
) -> std::string
{
79 a
|= (uint32_t)x
[0] << 16;
80 a
|= (uint32_t)x
[1] << 8;
82 return (stringfmt() << std::hex
<< std::setw(6) << std::setfill('0') << a
).str();
84 {"4 bytes (signed)", 4, false, "d", [](const uint8_t* x
) -> std::string
{
85 return (stringfmt() << *(int32_t*)x
).str();
87 {"4 bytes (unsigned)", 4, false, "D", [](const uint8_t* x
) -> std::string
{
88 return (stringfmt() << *(uint32_t*)x
).str();
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();
93 {"4 bytes (float)", 4, false, "f", [](const uint8_t* x
) -> std::string
{
94 return (stringfmt() << *(float*)x
).str();
96 {"8 bytes (signed)", 8, false, "q", [](const uint8_t* x
) -> std::string
{
97 return (stringfmt() << *(int64_t*)x
).str();
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
{
122 a
|= (uint32_t)x
[0] << 16;
123 a
|= (uint32_t)x
[1] << 8;
127 return (stringfmt() << a
/ 256.0).str();
129 {"Q16.8 (unsigned)", 3, false, "C256z$O/", [](const uint8_t* x
) -> std::string
{
131 a
|= (uint32_t)x
[0] << 16;
132 a
|= (uint32_t)x
[1] << 8;
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",
165 wxID_OPPOSITE_ENDIAN
= wxID_HIGHEST
+ 1,
166 wxID_DATATYPES_FIRST
,
167 wxID_DATATYPES_LAST
= wxID_DATATYPES_FIRST
+ 255,
169 wxID_REGIONS_LAST
= wxID_REGIONS_FIRST
+ 255,
171 wxID_DELETE_BOOKMARK
,
174 wxID_BOOKMARKS_FIRST
,
175 wxID_BOOKMARKS_LAST
= wxID_BOOKMARKS_FIRST
+ 255,
176 wxID_SEARCH_DISQUALIFY
,
183 class wxeditor_hexedit
: public wxFrame
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
)
191 wxBoxSizer
* top
= new wxBoxSizer(wxVERTICAL
);
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
);
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"));
237 valuemenu
->FindItem(wxID_OPPOSITE_ENDIAN
)->Check(littleendian
);
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);
280 bool ShouldPreventAppExit() const
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();
299 if(c
== WXK_LEFT
&& hex_input_state
< 0) {
300 if(hpanel
->seloff
> 0) hpanel
->seloff
--;
301 hpanel
->request_paint();
304 if(c
== WXK_RIGHT
&& hex_input_state
< 0) {
305 if(hpanel
->seloff
+ 1 < hpanel
->vmasize
) hpanel
->seloff
++;
306 hpanel
->request_paint();
309 if(c
== WXK_UP
&& hex_input_state
< 0) {
310 if(hpanel
->seloff
>= 16) hpanel
->seloff
-= 16;
311 hpanel
->request_paint();
314 if(c
== WXK_DOWN
&& hex_input_state
< 0) {
315 if(hpanel
->seloff
+ 16 < hpanel
->vmasize
) hpanel
->seloff
+= 16;
316 hpanel
->request_paint();
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();
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();
331 if(c
>= '0' && c
<= '9') {
335 if(c
>= 'A' && c
<= 'F') {
336 do_hex(c
- 'A' + 10);
339 if(c
>= 'a' && c
<= 'f') {
340 do_hex(c
- 'a' + 10);
345 void on_mouse(wxMouseEvent
& e
)
347 auto cell
= hpanel
->get_cell();
349 hpanel
->on_mouse0(e
.GetX() / cell
.first
, e
.GetY() / cell
.second
, true);
351 hpanel
->on_mouse0(e
.GetX() / cell
.first
, e
.GetY() / cell
.second
, false);
355 if(e
.ShiftDown() && e
.ControlDown())
357 scroll
->apply_wheel(e
.GetWheelRotation(), e
.GetWheelDelta(), speed
);
358 hpanel
->offset
= scroll
->get_position();
360 void on_loadbookmarks(wxCommandEvent
& e
)
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());
368 std::vector
<bookmark_entry
> newbookmarks
;
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
);
381 bookmarkmenu
->Delete(p
);
384 for(auto i
: bookmarks
) {
385 if(wxID_BOOKMARKS_FIRST
+ idx
> wxID_BOOKMARKS_LAST
)
387 bookmarkmenu
->Append(wxID_BOOKMARKS_FIRST
+ idx
, towxstring(i
.name
));
390 } catch(canceled_exception
& e
) {
391 } catch(std::exception
& e
) {
392 show_message_ok(this, "Error", std::string("Can't load bookmarks: ") + e
.what(),
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
);
408 std::string doc
= root
.serialize();
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
;
415 } catch(canceled_exception
& e
) {
416 } catch(std::exception
& e
) {
417 show_message_ok(this, "Error", std::string("Can't save bookmarks: ") + e
.what(),
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);
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
));
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]);
446 if(d2
->ShowModal() == wxID_CANCEL
) {
450 int sel
= d2
->GetSelection();
453 bookmarks
.erase(bookmarks
.begin() + sel
);
454 for(unsigned i
= wxID_BOOKMARKS_FIRST
; i
<= wxID_BOOKMARKS_LAST
; i
++) {
455 auto p
= bookmarkmenu
->FindItem(i
);
457 bookmarkmenu
->Delete(p
);
460 for(auto i
: bookmarks
) {
461 bookmarkmenu
->Append(wxID_BOOKMARKS_FIRST
+ idx
, towxstring(i
.name
));
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();
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) -
488 hpanel
->request_paint();
491 void on_search_watch(wxCommandEvent
& e
)
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());
501 std::string wch
= datatypes
[curtype
].watch
;
502 size_t sz
= wch
.find_first_of("$");
504 if(sz
< wch
.length())
505 e
= (stringfmt() << wch
.substr(0, sz
) << "C0x" << std::hex
<< addr
<< "z"
506 << wch
.substr(sz
+ 1)).str();
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();
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
;
522 hpanel
->request_paint();
525 void on_bookmark(wxCommandEvent
& e
)
528 if(id
< wxID_BOOKMARKS_FIRST
|| id
> wxID_BOOKMARKS_LAST
)
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();
535 if(j
->readonly
|| j
->special
)
537 if(j
->name
== ent
.vma
) {
542 if(ent
.sel
>= size
|| ent
.scroll
>= (size
+ 15) / 16)
543 goto invalid_bookmark
;
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();
553 show_message_ok(this, "Error jumping to bookmark", "Bookmark refers to nonexistent location",
557 void on_vmasel(wxCommandEvent
& e
)
561 int selected
= e
.GetId();
562 if(selected
< wxID_REGIONS_FIRST
|| selected
> wxID_REGIONS_LAST
)
564 selected
-= wxID_REGIONS_FIRST
;
565 auto i
= lsnes_memory
.get_regions();
568 if(j
->readonly
|| j
->special
)
570 if(index
== selected
) {
571 if(j
->base
!= hpanel
->vmabase
|| j
->size
!= hpanel
->vmasize
)
572 update_vma(j
->base
, j
->size
);
574 if(vma_endians
.count(index
)) {
575 littleendian
= vma_endians
[index
];
576 valuemenu
->FindItem(wxID_OPPOSITE_ENDIAN
)->Check(littleendian
);
585 bool is_endian_little(int endian
)
587 if(endian
< 0) return true;
588 if(endian
> 0) return false;
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
;
598 scroll
->set_range((size
+ 15) / 16);
599 scroll
->set_position(0);
600 hpanel
->request_paint();
602 void on_typechange(wxCommandEvent
& e
)
607 if(id
< wxID_DATATYPES_FIRST
|| id
> wxID_DATATYPES_LAST
)
609 curtype
= id
- wxID_DATATYPES_FIRST
;
610 hpanel
->request_paint();
612 void on_changeendian(wxCommandEvent
& e
)
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();
625 hpanel
->request_paint();
627 void jumpto(uint64_t addr
)
631 //Switch to correct VMA.
632 auto i
= lsnes_memory
.get_regions();
635 if(j
->readonly
|| j
->special
)
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
);
641 if(vma_endians
.count(index
)) {
642 littleendian
= vma_endians
[index
];
643 valuemenu
->FindItem(wxID_OPPOSITE_ENDIAN
)->Check(littleendian
);
649 if(addr
< hpanel
->vmabase
|| addr
>= hpanel
->vmabase
+ hpanel
->vmasize
)
651 hpanel
->seloff
= addr
- hpanel
->vmabase
;
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
)))
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
)
681 std::string
get_current_vma_name()
683 if(current_vma
>= vma_names
.size())
685 return vma_names
[current_vma
];
687 void on_core_changed(bool _hard
)
692 runuifun([this, hard
]() {
693 for(unsigned i
= wxID_REGIONS_FIRST
; i
<= wxID_REGIONS_LAST
; i
++) {
694 auto p
= regionmenu
->FindItem(i
);
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();
705 int curreg_index
= 0;
707 if(j
->readonly
|| j
->special
)
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
;
716 if(!vma_endians
.count(index
))
717 vma_endians
[index
] = is_endian_little(j
->endian
);
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();
737 if(hpanel
->seloff
> hpanel
->vmasize
)
739 if(hex_input_state
< 0)
740 hex_input_state
= hex
;
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
)
747 runemufn([addr
, byte
]() {lsnes_memory
.write
<uint8_t>(addr
, byte
); });
749 hpanel
->request_paint();
751 class _panel
: public text_framebuffer_panel
754 _panel(wxeditor_hexedit
* parent
)
755 : text_framebuffer_panel(parent
, 59, lines
= 28, wxID_ANY
, NULL
)
762 memset(value
, 0, maxvaluelen
);
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
);
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
;
777 uint8_t* _value
= value
;
778 runemufn([_vmabase
, _vmasize
, paint_offset
, _seloff
, _value
, _lines
, this]() {
779 memory_search
* memsearch
= wxwindow_memorysearch_active();
781 for(size_t j
= 0; j
< _lines
; j
++) {
782 uint64_t addr
= paint_offset
+ j
* 16;
783 if(addr
>= _vmasize
) {
785 for(size_t i
= 0; i
< get_characters().first
; i
++)
786 write(" ", 1, i
, j
, 0, 0xFFFFFF);
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,
797 if(_vmasize
- addr
< 16)
798 bytes
= _vmasize
- addr
;
799 uint64_t laddr
= addr
+ _vmabase
;
800 for(size_t i
= 0; i
< bytes
; i
++) {
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
)
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
);
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
)
834 if((ch
& 0x60) == 0 || ch
== 127 || ch
== 0xad)
838 void on_mouse0(int x
, int y
, bool polarity
)
842 uint64_t rowaddr
= 16 * (static_cast<uint64_t>(offset
) + y
);
844 for(unsigned i
= 0; i
< 16; i
++)
845 if(x
== hexcol
[i
] || x
== hexcol
[i
] + 1 || x
== charcol
[i
])
847 if(rowaddr
+ coladdr
>= vmasize
|| coladdr
> 15)
849 seloff
= rowaddr
+ coladdr
;
852 wxeditor_hexedit
* rparent
;
857 uint8_t value
[maxvaluelen
];
861 struct bookmark_entry
869 wxMenu
* bookmarkmenu
;
871 wxComboBox
* datatype
;
873 wxStatusBar
* statusbar
;
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
;
890 void wxeditor_hexedit_display(wxWindow
* parent
)
895 editor
= new wxeditor_hexedit(parent
);
901 void wxeditor_hexeditor_update()
907 bool wxeditor_hexeditor_available()
912 bool wxeditor_hexeditor_jumpto(uint64_t addr
)
915 editor
->jumpto(addr
);