Make various instance stuff to take references to other instance objs
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blobb30e9a3fe8697ab0a1d78a170eaca7013e654a05
1 #include "lsnes.hpp"
3 #include <wx/dnd.h>
4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/menu_upload.hpp"
6 #include "platform/wxwidgets/platform.hpp"
7 #include "platform/wxwidgets/loadsave.hpp"
8 #include "platform/wxwidgets/window_mainwindow.hpp"
9 #include "platform/wxwidgets/window_messages.hpp"
10 #include "platform/wxwidgets/window_status.hpp"
11 #include "platform/wxwidgets/window-romload.hpp"
12 #include "platform/wxwidgets/settings-common.hpp"
13 #include "platform/wxwidgets/menu_tracelog.hpp"
14 #include "platform/wxwidgets/menu_branches.hpp"
15 #include "platform/wxwidgets/menu_projects.hpp"
17 #include "core/audioapi.hpp"
18 #include "core/command.hpp"
19 #include "core/controller.hpp"
20 #include "core/controllerframe.hpp"
21 #include "core/dispatch.hpp"
22 #include "core/emustatus.hpp"
23 #include "core/framebuffer.hpp"
24 #include "core/framerate.hpp"
25 #include "core/instance.hpp"
26 #include "core/keymapper.hpp"
27 #include "interface/romtype.hpp"
28 #include "core/loadlib.hpp"
29 #include "lua/lua.hpp"
30 #include "core/mainloop.hpp"
31 #include "core/memorywatch.hpp"
32 #include "core/misc.hpp"
33 #include "core/moviedata.hpp"
34 #include "core/project.hpp"
35 #include "core/romloader.hpp"
36 #include "core/settings.hpp"
37 #include "core/window.hpp"
38 #include "library/directory.hpp"
39 #include "library/minmax.hpp"
40 #include "library/string.hpp"
41 #include "library/zip.hpp"
42 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
43 #define FUCKED_SYSTEM
44 #endif
46 #include <cmath>
47 #include <vector>
48 #include <string>
51 extern "C"
53 #ifndef UINT64_C
54 #define UINT64_C(val) val##ULL
55 #endif
56 #include <libswscale/swscale.h>
59 enum
61 wxID_PAUSE = wxID_HIGHEST + 1,
62 wxID_FRAMEADVANCE,
63 wxID_SUBFRAMEADVANCE,
64 wxID_NEXTPOLL,
65 wxID_AUDIO_ENABLED,
66 wxID_SAVE_STATE,
67 wxID_SAVE_MOVIE,
68 wxID_SAVE_SUBTITLES,
69 wxID_LOAD_STATE,
70 wxID_LOAD_MOVIE,
71 wxID_RUN_SCRIPT,
72 wxID_RUN_LUA,
73 wxID_RESET_LUA,
74 wxID_EVAL_LUA,
75 wxID_SAVE_SCREENSHOT,
76 wxID_READONLY_MODE,
77 wxID_EDIT_AUTHORS,
78 wxID_AUTOHOLD,
79 wxID_EDIT_MEMORYWATCH,
80 wxID_SAVE_MEMORYWATCH,
81 wxID_LOAD_MEMORYWATCH,
82 wxID_EDIT_SUBTITLES,
83 wxID_EDIT_VSUBTITLES,
84 wxID_DUMP_FIRST,
85 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
86 wxID_REWIND_MOVIE,
87 wxID_MEMORY_SEARCH,
88 wxID_CANCEL_SAVES,
89 wxID_SHOW_STATUS,
90 wxID_SET_SPEED,
91 wxID_SPEED_5,
92 wxID_SPEED_10,
93 wxID_SPEED_17,
94 wxID_SPEED_20,
95 wxID_SPEED_25,
96 wxID_SPEED_33,
97 wxID_SPEED_50,
98 wxID_SPEED_100,
99 wxID_SPEED_150,
100 wxID_SPEED_200,
101 wxID_SPEED_300,
102 wxID_SPEED_500,
103 wxID_SPEED_1000,
104 wxID_SPEED_TURBO,
105 wxID_LOAD_LIBRARY,
106 wxID_RELOAD_ROM_IMAGE,
107 wxID_LOAD_ROM_IMAGE_FIRST,
108 wxID_LOAD_ROM_IMAGE_LAST = wxID_LOAD_ROM_IMAGE_FIRST + 1023,
109 wxID_NEW_MOVIE,
110 wxID_SHOW_MESSAGES,
111 wxID_DEDICATED_MEMORY_WATCH,
112 wxID_RMOVIE_FIRST,
113 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
114 wxID_RROM_FIRST,
115 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
116 wxID_CONFLICTRESOLUTION,
117 wxID_VUDISPLAY,
118 wxID_MOVIE_EDIT,
119 wxID_TASINPUT,
120 wxID_NEW_PROJECT,
121 wxID_CLOSE_PROJECT,
122 wxID_CLOSE_ROM,
123 wxID_EDIT_MACROS,
124 wxID_ENTER_FULLSCREEN,
125 wxID_ACTIONS_FIRST,
126 wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256,
127 wxID_SETTINGS_FIRST,
128 wxID_SETTINGS_LAST = wxID_SETTINGS_FIRST + 256,
129 wxID_HEXEDITOR,
130 wxID_MULTITRACK,
131 wxID_CHDIR,
132 wxID_RLUA_FIRST,
133 wxID_RLUA_LAST = wxID_RLUA_FIRST + 16,
134 wxID_UPLOAD_FIRST,
135 wxID_UPLOAD_LAST = wxID_UPLOAD_FIRST + 256,
136 wxID_DOWNLOAD,
137 wxID_TRACELOG_FIRST,
138 wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256,
139 wxID_PLUGIN_MANAGER,
140 wxID_BRANCH_FIRST,
141 wxID_BRANCH_LAST = wxID_BRANCH_FIRST + 10240,
142 wxID_PROJECT_FIRST,
143 wxID_PROJECT_LAST = wxID_PROJECT_FIRST + 17,
144 wxID_DISASSEMBLER,
148 double video_scale_factor = 1.0;
149 int scaling_flags = SWS_POINT;
150 bool arcorrect_enabled = false;
151 bool hflip_enabled = false;
152 bool vflip_enabled = false;
153 bool rotate_enabled = false;
155 namespace
157 std::string last_volume = "0dB";
158 std::string last_volume_record = "0dB";
159 std::string last_volume_voice = "0dB";
160 unsigned char* screen_buffer;
161 struct SwsContext* sws_ctx;
162 uint32_t* rotate_buffer;
163 uint32_t old_width;
164 uint32_t old_height;
165 int old_flags = SWS_POINT;
166 bool old_hflip = false;
167 bool old_vflip = false;
168 bool old_rotate = false;
169 bool main_window_dirty;
170 bool is_fs = false;
171 bool hashing_in_progress = false;
172 uint64_t hashing_left = 0;
173 uint64_t hashing_total = 0;
174 int64_t last_update = 0;
175 threads::thread* emulation_thread;
176 bool status_updated = false;
178 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> background_audio(lsnes_instance.settings,
179 "background-audio", "GUIā€£Enable background audio", true);
181 class _status_timer : public wxTimer
183 public:
184 _status_timer()
186 Start(50);
188 void Notify()
190 if(status_updated) {
191 status_updated = false;
192 if(main_window) main_window->update_statusbar();
197 class _focus_timer : public wxTimer
199 public:
200 _focus_timer()
202 was_focused = (wxWindow::FindFocus() != NULL);
203 was_enabled = platform::is_sound_enabled();
204 Start(500);
206 void Notify()
208 bool is_focused = (wxWindow::FindFocus() != NULL);
209 if(is_focused && !was_focused) {
210 //Gained focus.
211 if(!background_audio)
212 platform::sound_enable(was_enabled);
213 } else if(!is_focused && was_focused) {
214 //Lost focus.
215 was_enabled = platform::is_sound_enabled();
216 if(!background_audio)
217 platform::sound_enable(false);
219 was_focused = is_focused;
221 private:
222 bool was_focused;
223 bool was_enabled;
226 class download_timer : public wxTimer
228 public:
229 download_timer(wxwin_mainwindow* main)
231 w = main;
232 Start(50);
234 void Notify()
236 if(w->download_in_progress->finished) {
237 w->update_statusbar();
238 auto old = w->download_in_progress;
239 w->download_in_progress = NULL;
240 if(old->errormsg != "") {
241 show_message_ok(w, "Error downloading movie", old->errormsg,
242 wxICON_EXCLAMATION);
243 } else {
244 lsnes_instance.iqueue.queue("load-movie $MEMORY:wxwidgets_download_tmp");
246 delete old;
247 Stop();
248 delete this;
249 } else {
250 w->update_statusbar();
253 private:
254 wxwin_mainwindow* w;
257 void hash_callback(uint64_t left, uint64_t total)
259 wxwin_mainwindow* mwin = main_window;
260 if(left == 0xFFFFFFFFFFFFFFFFULL) {
261 hashing_in_progress = false;
262 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
263 last_update = framerate_regulator::get_utime() - 2000000;
264 return;
266 hashing_in_progress = true;
267 hashing_left = left;
268 hashing_total = total;
269 int64_t this_update = framerate_regulator::get_utime();
270 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
271 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
272 last_update = this_update;
276 std::pair<std::string, std::string> lsplit(std::string l)
278 for(unsigned i = 0; i < l.length() - 3; i++)
279 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
280 return std::make_pair(l.substr(0, i), l.substr(i + 3));
281 return std::make_pair("", l);
284 recentfiles::multirom loadreq_to_multirom(const romload_request& req)
286 recentfiles::multirom r;
287 r.packfile = req.packfile;
288 r.singlefile = req.singlefile;
289 r.core = req.core;
290 r.system = req.system;
291 r.region = req.region;
292 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
293 if(req.files[i] != "") {
294 r.files.resize(i + 1);
295 r.files[i] = req.files[i];
297 return r;
300 class system_menu : public wxMenu
302 public:
303 system_menu(wxWindow* win);
304 ~system_menu();
305 void on_select(wxCommandEvent& e);
306 void update(bool light);
307 private:
308 wxWindow* pwin;
309 void insert_pass(int id, const std::string& label);
310 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
311 wxMenu* lookup_menu(const std::string& key);
312 wxMenuItem* sep;
313 std::map<int, unsigned> action_by_id;
314 std::map<unsigned, wxMenuItem*> item_by_action;
315 std::map<wxMenuItem*, wxMenu*> menu_by_item;
316 std::map<std::string, wxMenu*> submenu_by_name;
317 std::map<std::string, wxMenuItem*> submenui_by_name;
318 std::set<unsigned> toggles;
319 int next_id;
322 wxMenu* system_menu::lookup_menu(const std::string& key)
324 if(key == "")
325 return this;
326 if(submenu_by_name.count(key))
327 return submenu_by_name[key];
328 //Not found, create.
329 if(!sep)
330 sep = AppendSeparator();
331 auto p = lsplit(key);
332 wxMenu* into = lookup_menu(p.first);
333 submenu_by_name[key] = new wxMenu();
334 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
335 menu_by_item[submenui_by_name[key]] = into;
336 return submenu_by_name[key];
339 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
341 if(!sep)
342 sep = AppendSeparator();
344 auto p = lsplit(label);
345 wxMenu* into = lookup_menu(p.first);
347 action_by_id[next_id] = id;
348 std::string use_label = p.second + (dots ? "..." : "");
349 if(check) {
350 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
351 toggles.insert(id);
352 } else
353 item_by_action[id] = into->Append(next_id, towxstring(use_label));
354 menu_by_item[item_by_action[id]] = into;
355 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
356 wxCommandEventHandler(system_menu::on_select), NULL, this);
359 void system_menu::insert_pass(int id, const std::string& label)
361 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
362 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
363 Append(id, towxstring(label));
366 system_menu::system_menu(wxWindow* win)
368 pwin = win;
369 insert_pass(wxID_PAUSE, "Pause/Unpause");
370 insert_pass(wxID_FRAMEADVANCE, "Step frame");
371 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
372 insert_pass(wxID_NEXTPOLL, "Step poll");
373 sep = NULL;
376 system_menu::~system_menu()
380 void system_menu::on_select(wxCommandEvent& e)
382 if(!action_by_id.count(e.GetId()))
383 return;
384 unsigned act_id = action_by_id[e.GetId()];
385 const interface_action* act = NULL;
386 for(auto i : our_rom.rtype->get_actions())
387 if(i->id == act_id) {
388 act = i;
389 break;
391 if(!act)
392 return;
393 try {
394 auto p = prompt_action_params(pwin, act->get_title(), act->params);
395 lsnes_instance.iqueue.run([act_id,p]() { our_rom.rtype->execute_action(act_id, p); });
396 } catch(canceled_exception& e) {
397 } catch(std::bad_alloc& e) {
398 OOM_panic();
402 void system_menu::update(bool light)
404 if(!light) {
405 next_id = wxID_ACTIONS_FIRST;
406 if(sep) {
407 Destroy(sep);
408 sep = NULL;
410 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
411 menu_by_item[i->second]->Destroy(i->second);
412 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
413 menu_by_item[i->second]->Destroy(i->second);
414 action_by_id.clear();
415 item_by_action.clear();
416 menu_by_item.clear();
417 submenu_by_name.clear();
418 submenui_by_name.clear();
419 toggles.clear();
421 for(auto i : our_rom.rtype->get_actions())
422 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
424 for(auto i : item_by_action)
425 i.second->Enable(our_rom.rtype->action_flags(i.first) & 1);
426 for(auto i : toggles)
427 item_by_action[i]->Check(our_rom.rtype->action_flags(i) & 2);
430 std::string munge_name(const std::string& orig)
432 std::string newname;
433 regex_results r;
434 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
435 uint64_t sequence;
436 try {
437 sequence = parse_value<uint64_t>(r[2]);
438 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
439 } catch(...) {
440 newname = newname + "(2)";
442 } else {
443 newname = newname + "(2)";
445 return newname;
448 void handle_watch_load(std::map<std::string, std::string>& new_watches, std::set<std::string>& old_watches)
450 auto proj = lsnes_instance.project.get();
451 if(proj) {
452 for(auto i : new_watches) {
453 std::string name = i.first;
454 while(true) {
455 if(!old_watches.count(name)) {
456 try {
457 if(name != "" && i.second != "")
458 lsnes_instance.mwatch.set(name, i.second);
459 } catch(std::exception& e) {
460 messages << "Can't set memory watch '" << name << "': "
461 << e.what() << std::endl;
463 break;
464 } else if(lsnes_instance.mwatch.get_string(name) == i.second)
465 break;
466 else
467 name = munge_name(name);
470 } else {
471 for(auto i : new_watches)
472 try {
473 if(i.first != "" && i.second != "")
474 lsnes_instance.mwatch.set(i.first, i.second);
475 } catch(std::exception& e) {
476 messages << "Can't set memory watch '" << i.first << "': "
477 << e.what() << std::endl;
479 for(auto i : old_watches)
480 if(!new_watches.count(i))
481 try {
482 lsnes_instance.mwatch.clear(i);
483 } catch(std::exception& e) {
484 messages << "Can't clear memory watch '" << i << "': "
485 << e.what() << std::endl;
490 std::string get_default_screenshot_name()
492 auto p = lsnes_instance.project.get();
493 if(!p)
494 return "";
495 else {
496 auto files = directory::enumerate(p->directory, ".*-[0-9]+\\.png");
497 std::set<std::string> numbers;
498 for(auto i : files) {
499 size_t split;
500 #ifdef FUCKED_SYSTEM
501 split = i.find_last_of("\\/");
502 #else
503 split = i.find_last_of("/");
504 #endif
505 std::string name = i;
506 if(split < name.length())
507 name = name.substr(split + 1);
508 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
509 if(r[1] != p->prefix)
510 continue;
511 numbers.insert(r[2]);
513 for(uint64_t i = 1;; i++) {
514 std::string candidate = (stringfmt() << i).str();
515 if(!numbers.count(candidate))
516 return p->prefix + "-" + candidate + ".png";
521 std::string project_prefixname(const std::string ext)
523 auto p = lsnes_instance.project.get();
524 if(!p)
525 return "";
526 else
527 return p->prefix + "." + ext;
531 double pick_volume(wxWindow* win, const std::string& title, std::string& last)
533 std::string value;
534 regex_results r;
535 double parsed = 1;
536 value = pick_text(win, title, "Enter volume in absolute units, percentage (%) or dB:",
537 last);
538 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
539 parsed = strtod(r[1].c_str(), NULL);
540 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
541 parsed = strtod(r[1].c_str(), NULL) / 100;
542 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
543 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
544 else {
545 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, win);
546 return -1;
548 last = value;
549 return parsed;
552 void recent_rom_selected(const recentfiles::multirom& file)
554 romload_request req;
555 req.packfile = file.packfile;
556 req.singlefile = file.singlefile;
557 req.core = file.core;
558 req.system = file.system;
559 req.region = file.region;
560 for(unsigned i = 0; i < file.files.size() && i < ROM_SLOT_COUNT; i++)
561 req.files[i] = file.files[i];
562 lsnes_instance.iqueue.run_async([req]() {
563 lsnes_instance.command.invoke("unpause-emulator");
564 load_new_rom(req);
565 }, [](std::exception& e) {});
568 void recent_movie_selected(const recentfiles::path& file)
570 lsnes_instance.iqueue.queue("load-smart " + file.get_path());
573 void recent_script_selected(const recentfiles::path& file)
575 lsnes_instance.iqueue.queue("run-lua " + file.get_path());
578 wxString getname()
580 std::string windowname = "lsnes rr" + lsnes_version + " [";
581 auto p = lsnes_instance.project.get();
582 if(p)
583 windowname = windowname + p->name;
584 else
585 windowname = windowname + our_rom.rtype->get_core_identifier();
586 windowname = windowname + "]";
587 return towxstring(windowname);
590 struct emu_args
592 struct loaded_rom rom;
593 struct moviefile* initial;
594 bool load_has_to_succeed;
597 void* emulator_main(void* _args)
599 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
600 try {
601 our_rom = args->rom;
602 messages << "Using core: " << our_rom.rtype->get_core_identifier() << std::endl;
603 struct moviefile* movie = args->initial;
604 bool has_to_succeed = args->load_has_to_succeed;
605 platform::flush_command_queue();
606 main_loop(our_rom, *movie, has_to_succeed);
607 signal_program_exit();
608 } catch(std::bad_alloc& e) {
609 OOM_panic();
610 } catch(std::exception& e) {
611 messages << "FATAL: " << e.what() << std::endl;
612 platform::fatal_error();
614 delete args;
615 return NULL;
618 void join_emulator_thread()
620 emulation_thread->join();
623 keyboard::mouse_calibration mouse_cal = {0};
624 keyboard::key_mouse mouse_x(lsnes_instance.keyboard, "mouse_x", "mouse", mouse_cal);
625 keyboard::key_mouse mouse_y(lsnes_instance.keyboard, "mouse_y", "mouse", mouse_cal);
626 keyboard::key_key mouse_l(lsnes_instance.keyboard, "mouse_left", "mouse");
627 keyboard::key_key mouse_m(lsnes_instance.keyboard, "mouse_center", "mouse");
628 keyboard::key_key mouse_r(lsnes_instance.keyboard, "mouse_right", "mouse");
629 keyboard::key_key mouse_i(lsnes_instance.keyboard, "mouse_inwindow", "mouse");
631 std::pair<double, double> calc_scale_factors(double factor, bool ar,
632 double par)
634 if(!ar)
635 return std::make_pair(factor, factor);
636 else if(par < 1) {
637 //Too wide, make taller.
638 return std::make_pair(factor, factor / par);
639 } else {
640 //Too narrow, make wider.
641 return std::make_pair(factor * par, factor);
645 void handle_wx_mouse(wxMouseEvent& e)
647 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled,
648 (our_rom.rtype) ? our_rom.rtype->get_PAR() : 1.0);
649 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_x, e.GetX() /
650 sfactors.first));
651 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_y, e.GetY() /
652 sfactors.second));
653 if(e.Entering())
654 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_i, 1));
655 if(e.Leaving())
656 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_i, 0));
657 if(e.LeftDown())
658 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_l, 1));
659 if(e.LeftUp())
660 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_l, 0));
661 if(e.MiddleDown())
662 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_m, 1));
663 if(e.MiddleUp())
664 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_m, 0));
665 if(e.RightDown())
666 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_r, 1));
667 if(e.RightUp())
668 lsnes_instance.iqueue.queue(keypress_info(keyboard::modifier_set(), mouse_r, 0));
671 bool is_readonly_mode()
673 bool ret;
674 lsnes_instance.iqueue.run([&ret]() {
675 ret = lsnes_instance.mlogic ? lsnes_instance.mlogic.get_movie().readonly_mode() : false;
677 return ret;
680 std::pair<int, int> UI_controller_index_by_logical(unsigned lid)
682 std::pair<int, int> ret;
683 lsnes_instance.iqueue.run([&ret, lid]() { ret = CORE().controls.lcid_to_pcid(lid); });
684 return ret;
687 void set_speed(double target)
689 if(target < 0)
690 lsnes_instance.framerate.set_speed_multiplier(std::numeric_limits<double>::infinity());
691 else
692 lsnes_instance.framerate.set_speed_multiplier(target / 100);
695 void update_preferences()
697 preferred_core.clear();
698 for(auto i : core_type::get_core_types()) {
699 std::string val = i->get_hname() + " / " + i->get_core_identifier();
700 for(auto j : i->get_extensions()) {
701 std::string key = "ext:" + j;
702 if(core_selections.count(key) && core_selections[key] == val)
703 preferred_core[key] = i;
705 std::string key2 = "type:" + i->get_iname();
706 if(core_selections.count(key2) && core_selections[key2] == val)
707 preferred_core[key2] = i;
711 std::string movie_path()
713 return lsnes_instance.setcache.get("moviepath");
716 bool is_lsnes_movie(const std::string& filename)
718 std::istream* s = NULL;
719 try {
720 bool ans = false;
721 s = &zip::openrel(filename, "");
722 char buf[6] = {0};
723 s->read(buf, 5);
724 if(*s && !strcmp(buf, "lsmv\x1A"))
725 ans = true;
726 delete s;
727 if(ans) return true;
728 } catch(...) {
729 delete s;
731 try {
732 zip::reader r(filename);
733 std::istream& s = r["systemid"];
734 std::string s2;
735 std::getline(s, s2);
736 delete &s;
737 istrip_CR(s2);
738 return (s2 == "lsnes-rr1");
739 } catch(...) {
740 return false;
744 class loadfile : public wxFileDropTarget
746 public:
747 loadfile(wxwin_mainwindow* win) : pwin(win) {};
748 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
750 bool ret = false;
751 if(filenames.Count() == 2) {
752 std::string a = tostdstring(filenames[0]);
753 std::string b = tostdstring(filenames[1]);
754 bool amov = is_lsnes_movie(a);
755 bool bmov = is_lsnes_movie(b);
756 if(amov == bmov)
757 return false;
758 if(amov) std::swap(a, b);
759 lsnes_instance.iqueue.run_async([a, b]() {
760 lsnes_instance.command.invoke("unpause-emulator");
761 romload_request req;
762 req.packfile = a;
763 load_new_rom(req);
764 lsnes_instance.command.invoke("load-smart " + b);
765 }, [](std::exception& e) {});
766 ret = true;
768 if(filenames.Count() == 1) {
769 std::string a = tostdstring(filenames[0]);
770 bool amov = is_lsnes_movie(a);
771 if(amov) {
772 lsnes_instance.iqueue.queue("load-smart " + a);
773 pwin->recent_movies->add(a);
774 ret = true;
775 } else {
776 romload_request req;
777 req.packfile = a;
778 lsnes_instance.iqueue.run_async([req]() {
779 lsnes_instance.command.invoke("unpause-emulator");
780 load_new_rom(req);
781 }, [](std::exception& e) {});
782 pwin->recent_roms->add(loadreq_to_multirom(req));
783 ret = true;
786 return ret;
788 wxwin_mainwindow* pwin;
792 void boot_emulator(loaded_rom& rom, moviefile& movie, bool fscreen)
794 update_preferences();
795 try {
796 struct emu_args* a = new emu_args;
797 a->rom = rom;
798 a->initial = &movie;
799 a->load_has_to_succeed = false;
800 modal_pause_holder hld;
801 emulation_thread = new threads::thread(emulator_main, a);
802 main_window = new wxwin_mainwindow(fscreen);
803 main_window->Show();
804 } catch(std::bad_alloc& e) {
805 OOM_panic();
809 wxwin_mainwindow::panel::panel(wxWindow* win)
810 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
812 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
813 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
814 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
815 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
816 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
817 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
818 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
819 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
820 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
821 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
822 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
823 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
824 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
825 SetMinSize(wxSize(512, 448));
828 void wxwin_mainwindow::menu_start(wxString name)
830 while(!upper.empty())
831 upper.pop();
832 current_menu = new wxMenu();
833 menubar->Append(current_menu, name);
836 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
838 while(!upper.empty())
839 upper.pop();
840 menubar->Append(menu, name);
841 current_menu = NULL;
844 wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
846 return current_menu->AppendSubMenu(menu, name);
849 void wxwin_mainwindow::menu_entry(int id, wxString name)
851 current_menu->Append(id, name);
852 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
853 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
856 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
858 checkitems[id] = current_menu->AppendCheckItem(id, name);
859 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
860 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
863 void wxwin_mainwindow::menu_start_sub(wxString name)
865 wxMenu* old = current_menu;
866 upper.push(current_menu);
867 current_menu = new wxMenu();
868 old->AppendSubMenu(current_menu, name);
871 void wxwin_mainwindow::menu_end_sub()
873 current_menu = upper.top();
874 upper.pop();
877 bool wxwin_mainwindow::menu_ischecked(int id)
879 if(checkitems.count(id))
880 return checkitems[id]->IsChecked();
881 else
882 return false;
885 void wxwin_mainwindow::menu_check(int id, bool newstate)
887 if(checkitems.count(id))
888 return checkitems[id]->Check(newstate);
889 else
890 return;
893 void wxwin_mainwindow::menu_enable(int id, bool newstate)
895 auto item = menubar->FindItem(id);
896 if(!item)
897 return;
898 item->Enable(newstate);
901 void wxwin_mainwindow::menu_separator()
903 current_menu->AppendSeparator();
906 void wxwin_mainwindow::panel::request_paint()
908 Refresh();
911 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
913 if(wx_escape_count >= 3 && is_fs) {
914 //Leave fullscreen mode.
915 main_window->enter_or_leave_fullscreen(false);
917 lsnes_instance.fbuf.render_framebuffer();
918 static
919 uint8_t* srcp[1];
920 int srcs[1];
921 uint8_t* dstp[1];
922 int dsts[1];
923 wxPaintDC dc(this);
924 uint32_t tw, th;
925 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
926 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled, our_rom.rtype ?
927 our_rom.rtype->get_PAR() : 1.0);
928 if(rotate_enabled) {
929 tw = lsnes_instance.fbuf.main_screen.get_height() * sfactors.second + 0.5;
930 th = lsnes_instance.fbuf.main_screen.get_width() * sfactors.first + 0.5;
931 } else {
932 tw = lsnes_instance.fbuf.main_screen.get_width() * sfactors.first + 0.5;
933 th = lsnes_instance.fbuf.main_screen.get_height() * sfactors.second + 0.5;
935 if(!tw || !th) {
936 main_window_dirty = false;
937 return;
939 //Scale this to fullscreen.
940 if(is_fs) {
941 wxSize screen = main_window->GetSize();
942 double fss = min(1.0 * screen.GetWidth() / tw, 1.0 * screen.GetHeight() / th);
943 tw *= fss;
944 th *= fss;
947 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
948 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
949 if(screen_buffer) {
950 delete[] screen_buffer;
951 screen_buffer = NULL;
953 if(rotate_buffer) {
954 delete[] rotate_buffer;
955 rotate_buffer = NULL;
957 old_height = th;
958 old_width = tw;
959 old_flags = scaling_flags;
960 old_hflip = hflip_enabled;
961 old_vflip = vflip_enabled;
962 old_rotate = rotate_enabled;
963 uint32_t w = lsnes_instance.fbuf.main_screen.get_width();
964 uint32_t h = lsnes_instance.fbuf.main_screen.get_height();
965 if(w && h)
966 sws_ctx = sws_getCachedContext(sws_ctx, rotate_enabled ? h : w, rotate_enabled ? w : h,
967 PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
968 tw = max(tw, static_cast<uint32_t>(128));
969 th = max(th, static_cast<uint32_t>(112));
970 screen_buffer = new unsigned char[tw * th * 3];
971 if(aux)
972 rotate_buffer = new uint32_t[lsnes_instance.fbuf.main_screen.get_width() *
973 lsnes_instance.fbuf.main_screen.get_height()];
974 SetMinSize(wxSize(tw, th));
975 signal_resize_needed();
977 if(aux) {
978 //Hflip, Vflip or rotate active.
979 size_t width = lsnes_instance.fbuf.main_screen.get_width();
980 size_t height = lsnes_instance.fbuf.main_screen.get_height();
981 size_t width1 = width - 1;
982 size_t height1 = height - 1;
983 size_t stride = lsnes_instance.fbuf.main_screen.rowptr(1) - lsnes_instance.fbuf.main_screen.rowptr(0);
984 uint32_t* pixels = lsnes_instance.fbuf.main_screen.rowptr(0);
985 if(rotate_enabled) {
986 for(unsigned y = 0; y < height; y++) {
987 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
988 uint32_t* dpixels = rotate_buffer + (height1 - y);
989 if(hflip_enabled)
990 for(unsigned x = 0; x < width; x++)
991 dpixels[x * height] = pixels2[width1 - x];
992 else
993 for(unsigned x = 0; x < width; x++)
994 dpixels[x * height] = pixels2[x];
996 } else {
997 for(unsigned y = 0; y < height; y++) {
998 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
999 uint32_t* dpixels = rotate_buffer + y * width;
1000 if(hflip_enabled)
1001 for(unsigned x = 0; x < width; x++)
1002 dpixels[x] = pixels2[width1 - x];
1003 else
1004 for(unsigned x = 0; x < width; x++)
1005 dpixels[x] = pixels2[x];
1009 if(aux)
1010 srcs[0] = 4 * (rotate_enabled ? lsnes_instance.fbuf.main_screen.get_height() :
1011 lsnes_instance.fbuf.main_screen.get_width());
1012 else
1013 srcs[0] = 4 * lsnes_instance.fbuf.main_screen.get_stride();
1014 dsts[0] = 3 * tw;
1015 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : lsnes_instance.fbuf.main_screen.rowptr(0));
1016 dstp[0] = screen_buffer;
1017 memset(screen_buffer, 0, tw * th * 3);
1018 if(lsnes_instance.fbuf.main_screen.get_width() && lsnes_instance.fbuf.main_screen.get_height())
1019 sws_scale(sws_ctx, srcp, srcs, 0, rotate_enabled ? lsnes_instance.fbuf.main_screen.get_width() :
1020 lsnes_instance.fbuf.main_screen.get_height(),
1021 dstp, dsts);
1022 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
1023 dc.DrawBitmap(bmp, 0, 0, false);
1024 main_window_dirty = false;
1025 main_window->update_statusbar();
1028 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
1030 //Blank.
1033 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
1035 handle_wx_keyboard(e, true);
1038 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
1040 handle_wx_keyboard(e, false);
1043 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
1045 handle_wx_mouse(e);
1048 wxwin_mainwindow::wxwin_mainwindow(bool fscreen)
1049 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
1050 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
1052 download_in_progress = NULL;
1053 Centre();
1054 mwindow = NULL;
1055 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
1056 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
1057 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
1058 spanel_shown = true;
1059 toplevel->SetSizeHints(this);
1060 SetSizer(toplevel);
1061 Fit();
1062 gpanel->SetFocus();
1063 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
1064 SetMenuBar(menubar = new wxMenuBar);
1065 SetStatusBar(statusbar = new wxStatusBar(this));
1067 menu_start(wxT("File"));
1068 menu_start_sub(wxT("New"));
1069 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
1070 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
1071 menu_end_sub();
1072 menu_start_sub(wxT("Load"));
1073 menu_entry(wxID_LOAD_STATE, wxT("State..."));
1074 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
1075 menu_entry(wxID_DOWNLOAD, wxT("Download movie..."));
1076 if(loadlib::library::name() != "") {
1077 menu_separator();
1078 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name()));
1079 menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager"));
1081 menu_separator();
1082 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
1083 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST, wxT("ROM..."));
1084 menu_special_sub(wxT("Multifile ROM"), loadroms = new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST + 1,
1085 wxID_LOAD_ROM_IMAGE_LAST, [this](core_type* t) { this->do_load_rom_image(t); }));
1086 menu_special_sub(wxT("Project"), projects = new projects_menu(this, wxID_PROJECT_FIRST, wxID_PROJECT_LAST,
1087 get_config_path() + "/recent-projects.txt", [this](const std::string& id) {
1088 this->project_selected(id); }));
1089 menu_separator();
1090 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu<recentfiles::multirom>(this,
1091 wxID_RROM_FIRST, wxID_RROM_LAST, get_config_path() + "/recent-roms.txt", recent_rom_selected));
1092 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu<recentfiles::path>(this,
1093 wxID_RMOVIE_FIRST, wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt",
1094 recent_movie_selected));
1095 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts = new recent_menu<recentfiles::path>(this,
1096 wxID_RLUA_FIRST, wxID_RLUA_LAST, get_config_path() + "/recent-scripts.txt",
1097 recent_script_selected));
1098 menu_separator();
1099 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
1100 menu_separator();
1101 branches_menu* brlist;
1102 auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, wxID_BRANCH_FIRST,
1103 wxID_BRANCH_LAST));
1104 brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
1105 brlist->update();
1106 menu_end_sub();
1107 menu_start_sub(wxT("Save"));
1108 menu_entry(wxID_SAVE_STATE, wxT("State..."));
1109 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
1110 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
1111 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
1112 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
1113 menu_separator();
1114 menu_entry(wxID_CHDIR, wxT("Change working directory..."));
1115 menu_separator();
1116 menu_special_sub(wxT("Upload"), new upload_menu(this, wxID_UPLOAD_FIRST, wxID_UPLOAD_LAST));
1117 menu_end_sub();
1118 menu_start_sub(wxT("Close"));
1119 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
1120 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
1121 menu_enable(wxID_CLOSE_PROJECT, lsnes_instance.project.get() != NULL);
1122 menu_enable(wxID_CLOSE_ROM, lsnes_instance.project.get() == NULL);
1123 menu_end_sub();
1124 menu_separator();
1125 menu_entry(wxID_EXIT, wxT("Quit"));
1127 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this)));
1129 menu_start(wxT("Movie"));
1130 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
1131 menu_check(wxID_READONLY_MODE, is_readonly_mode());
1132 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
1133 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
1134 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
1135 menu_separator();
1136 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
1138 menu_start(wxT("Speed"));
1139 menu_entry(wxID_SPEED_5, wxT("1/20x"));
1140 menu_entry(wxID_SPEED_10, wxT("1/10x"));
1141 menu_entry(wxID_SPEED_17, wxT("1/6x"));
1142 menu_entry(wxID_SPEED_20, wxT("1/5x"));
1143 menu_entry(wxID_SPEED_25, wxT("1/4x"));
1144 menu_entry(wxID_SPEED_33, wxT("1/3x"));
1145 menu_entry(wxID_SPEED_50, wxT("1/2x"));
1146 menu_entry(wxID_SPEED_100, wxT("1x"));
1147 menu_entry(wxID_SPEED_150, wxT("1.5x"));
1148 menu_entry(wxID_SPEED_200, wxT("2x"));
1149 menu_entry(wxID_SPEED_300, wxT("3x"));
1150 menu_entry(wxID_SPEED_500, wxT("5x"));
1151 menu_entry(wxID_SPEED_1000, wxT("10x"));
1152 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
1153 menu_entry(wxID_SET_SPEED, wxT("Set..."));
1155 menu_start(wxT("Tools"));
1156 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
1157 menu_separator();
1158 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
1159 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
1160 menu_separator();
1161 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
1162 menu_separator();
1163 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
1164 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
1165 menu_entry(wxID_MULTITRACK, wxT("Multitrack..."));
1166 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
1167 menu_separator();
1168 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
1169 menu_separator();
1170 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
1171 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
1172 menu_separator();
1173 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
1174 menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
1175 tracelog_menu* trlog;
1176 auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, wxID_TRACELOG_FIRST,
1177 wxID_TRACELOG_LAST));
1178 trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
1179 trlog->update();
1180 menu_entry(wxID_DISASSEMBLER, wxT("Disassembler..."));
1181 menu_separator();
1182 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
1183 menu_separator();
1184 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
1185 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
1187 menu_start(wxT("Configure"));
1188 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
1189 menu_check(wxID_SHOW_STATUS, true);
1190 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
1191 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
1192 menu_special_sub(wxT("Settings"), new settings_menu(this, wxID_SETTINGS_FIRST));
1193 if(audioapi_driver_initialized()) {
1194 menu_separator();
1195 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
1196 menu_entry(wxID_VUDISPLAY, wxT("VU display / sound controls"));
1197 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
1199 menu_separator();
1200 menu_entry(wxID_ENTER_FULLSCREEN, wxT("Enter fullscreen mode"));
1202 menu_start(wxT("Help"));
1203 menu_entry(wxID_ABOUT, wxT("About..."));
1205 corechange.set(notify_core_change, []() { signal_core_change(); });
1206 titlechange.set(notify_title_change, []() { signal_core_change(); });
1207 newcore.set(notify_new_core, []() { update_preferences(); });
1208 unmuted.set(notify_sound_unmute, [this](bool unmute) {
1209 runuifun([this, unmute]() { this->menu_check(wxID_AUDIO_ENABLED, unmute); });
1211 modechange.set(notify_mode_change, [this](bool readonly) {
1212 runuifun([this, readonly]() { this->menu_check(wxID_READONLY_MODE, readonly); });
1214 gpanel->SetDropTarget(new loadfile(this));
1215 spanel->SetDropTarget(new loadfile(this));
1216 set_hasher_callback(hash_callback);
1217 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1218 menubar->SetMenuLabel(1, towxstring(our_rom.rtype->get_systemmenu_name()));
1219 focus_timer = new _focus_timer;
1220 status_timer = new _status_timer;
1221 if(fscreen) {
1222 wx_escape_count = 0;
1223 enter_or_leave_fullscreen(true);
1227 wxwin_mainwindow::~wxwin_mainwindow()
1229 if(sws_ctx) sws_freeContext(sws_ctx);
1230 if(screen_buffer) delete[] screen_buffer;
1231 if(rotate_buffer) delete[] rotate_buffer;
1232 focus_timer->Stop();
1233 delete focus_timer;
1234 status_timer->Stop();
1235 delete status_timer;
1238 void wxwin_mainwindow::request_paint()
1240 gpanel->Refresh();
1243 void wxwin_mainwindow::on_close(wxCloseEvent& e)
1245 //Veto it for now, latter things will delete it.
1246 e.Veto();
1247 lsnes_instance.iqueue.queue("quit-emulator");
1250 void wxwin_mainwindow::notify_update() throw()
1252 if(!main_window_dirty) {
1253 main_window_dirty = true;
1254 gpanel->Refresh();
1258 void wxwin_mainwindow::notify_resized() throw()
1260 toplevel->Layout();
1261 toplevel->SetSizeHints(this);
1262 Fit();
1265 void wxwin_mainwindow::notify_update_status() throw()
1267 spanel->request_paint();
1268 if(mwindow)
1269 mwindow->notify_update();
1270 status_updated = true;
1273 void wxwin_mainwindow::notify_exit() throw()
1275 wxwidgets_exiting = true;
1276 join_emulator_thread();
1277 Destroy();
1280 std::string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1282 if(!vars.count(key))
1283 return "";
1284 return utf8::to8(vars.find(key)->second);
1287 void wxwin_mainwindow::update_statusbar()
1289 if(download_in_progress) {
1290 statusbar->SetStatusText(towxstring(download_in_progress->statusmsg()));
1291 return;
1293 if(hashing_in_progress) {
1294 //TODO: Display this as a dialog.
1295 std::ostringstream s;
1296 s << "Hashing ROMs, approximately " << ((hashing_left + 524288) >> 20) << " of "
1297 << ((hashing_total + 524288) >> 20) << "MB left...";
1298 statusbar->SetStatusText(towxstring(s.str()));
1299 return;
1301 auto& vars = lsnes_instance.status.get_read();
1302 if(!vars.valid) {
1303 lsnes_instance.status.put_read();
1304 return;
1306 try {
1307 std::ostringstream s;
1308 bool recording = (vars.mode == 'R');
1309 if(vars.movie_valid) {
1310 if(recording)
1311 s << "Frame: " << vars.curframe;
1312 else
1313 s << "Frame: " << vars.curframe << "/" << vars.length;
1314 s << " Lag: " << vars.lag;
1315 if(vars.subframe == _lsnes_status::subframe_savepoint)
1316 s << " Subframe: S";
1317 else if(vars.subframe == _lsnes_status::subframe_video)
1318 s << " Subframe: V";
1319 else
1320 s << " Subframe: " << vars.subframe;
1321 } else {
1322 s << "Frame: N/A Lag: N/A Subframe: N/A";
1324 if(vars.saveslot_valid) {
1325 s << " Slot: ";
1326 if(vars.branch_valid) s << utf8::to8(vars.branch) << "ā†’";
1327 s << vars.saveslot;
1328 s << " [" << utf8::to8(vars.slotinfo) << "]";
1330 s << " Speed: " << vars.speed << "% ";
1331 if(vars.pause == _lsnes_status::pause_break)
1332 s << " Breakpoint";
1333 else if(vars.pause == _lsnes_status::pause_normal)
1334 s << " Paused";
1335 if(vars.dumping)
1336 s << " Dumping";
1337 if(vars.mode == 'C')
1338 s << " Corrupt";
1339 else if(vars.mode == 'R')
1340 s << " Recording";
1341 else if(vars.mode == 'P')
1342 s << " Playback";
1343 else if(vars.mode == 'F')
1344 s << " Finished";
1345 else
1346 s << " Unknown";
1347 if(vars.mbranch_valid)
1348 s << " Branch: " << utf8::to8(vars.mbranch);
1349 std::string macros = utf8::to8(vars.macros);
1350 if(macros.length())
1351 s << " Macros: " << macros;
1353 statusbar->SetStatusText(towxstring(s.str()));
1354 } catch(std::exception& e) {
1356 lsnes_instance.status.put_read();
1359 #define NEW_KEYBINDING "A new binding..."
1360 #define NEW_ALIAS "A new alias..."
1361 #define NEW_WATCH "A new watch..."
1363 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1365 try {
1366 handle_menu_click_cancelable(e);
1367 } catch(canceled_exception& e) {
1368 //Ignore.
1369 } catch(std::bad_alloc& e) {
1370 OOM_panic();
1371 } catch(std::exception& e) {
1372 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1376 void wxwin_mainwindow::refresh_title() throw()
1378 SetTitle(getname());
1379 auto p = lsnes_instance.project.get();
1380 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1381 for(int i = wxID_LOAD_ROM_IMAGE_FIRST; i <= wxID_LOAD_ROM_IMAGE_LAST; i++)
1382 menu_enable(i, !p);
1383 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1384 menu_enable(wxID_CLOSE_ROM, p == NULL);
1385 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1386 menubar->SetMenuLabel(1, towxstring(our_rom.rtype->get_systemmenu_name()));
1389 namespace
1391 struct movie_or_savestate
1393 public:
1394 typedef std::pair<std::string,std::string> returntype;
1395 movie_or_savestate(bool is_state)
1397 state = is_state;
1399 filedialog_input_params input(bool save) const
1401 filedialog_input_params p;
1402 std::string ext = state ? lsnes_instance.project.savestate_ext() : "lsmv";
1403 std::string name = state ? "Savestates" : "Movies";
1404 if(save) {
1405 p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
1406 p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
1407 } else
1408 p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
1409 ext));
1410 if(!save && state) {
1411 p.types.push_back(filedialog_type_entry("Savestates [playback]", "*." + ext +
1412 ";*." + ext + ".backup", ext));
1413 p.types.push_back(filedialog_type_entry("Savestates [recording]", "*." + ext +
1414 ";*." + ext + ".backup", ext));
1415 p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
1416 ";*." + ext + ".backup", ext));
1417 p.types.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext +
1418 ";*." + ext + ".backup", ext));
1420 p.default_type = save ? (state ? save_dflt_binary(lsnes_instance.settings) :
1421 movie_dflt_binary(lsnes_instance.settings)) : 0;
1422 return p;
1424 std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
1426 std::string cmdmod;
1427 if(save)
1428 cmdmod = p.typechoice ? "-binary" : "-zip";
1429 else if(state)
1430 switch(p.typechoice) {
1431 case 0: cmdmod = ""; break;
1432 case 1: cmdmod = "-readonly"; break;
1433 case 2: cmdmod = "-state"; break;
1434 case 3: cmdmod = "-preserve"; break;
1435 case 4: cmdmod = "-allbranches"; break;
1437 return std::make_pair(cmdmod, p.path);
1439 private:
1440 bool state;
1442 struct movie_or_savestate filetype_movie(false);
1443 struct movie_or_savestate filetype_savestate(true);
1446 void wxwin_mainwindow::project_selected(const std::string& id)
1448 std::string filename, displayname;
1449 bool load_ok = false;
1450 lsnes_instance.iqueue.run([id, &filename, &displayname, &load_ok]() -> void {
1451 try {
1452 auto& p = lsnes_instance.project.load(id); //Check.
1453 filename = p.filename;
1454 displayname = p.name;
1455 load_ok = true;
1456 delete &p;
1457 switch_projects(id);
1458 } catch(std::exception& e) {
1459 messages << "Failed to change project: " << e.what() << std::endl;
1462 if(load_ok) {
1463 recentfiles::namedobj obj;
1464 obj._id = id;
1465 obj._filename = filename;
1466 obj._display = displayname;
1467 projects->add(obj);
1471 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1473 std::string filename;
1474 std::pair<std::string, std::string> filename2;
1475 bool s;
1476 switch(e.GetId()) {
1477 case wxID_FRAMEADVANCE:
1478 lsnes_instance.iqueue.queue("+advance-frame");
1479 lsnes_instance.iqueue.queue("-advance-frame");
1480 return;
1481 case wxID_SUBFRAMEADVANCE:
1482 lsnes_instance.iqueue.queue("+advance-poll");
1483 lsnes_instance.iqueue.queue("-advance-poll");
1484 return;
1485 case wxID_NEXTPOLL:
1486 lsnes_instance.iqueue.queue("advance-skiplag");
1487 return;
1488 case wxID_PAUSE:
1489 lsnes_instance.iqueue.queue("pause-emulator");
1490 return;
1491 case wxID_EXIT:
1492 lsnes_instance.iqueue.queue("quit-emulator");
1493 return;
1494 case wxID_AUDIO_ENABLED:
1495 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1496 return;
1497 case wxID_CANCEL_SAVES:
1498 lsnes_instance.iqueue.queue("cancel-saves");
1499 return;
1500 case wxID_LOAD_MOVIE:
1501 filename = choose_file_load(this, "Load Movie", lsnes_instance.project.moviepath(),
1502 filetype_movie).second;
1503 recent_movies->add(filename);
1504 lsnes_instance.iqueue.queue("load-movie " + filename);
1505 return;
1506 case wxID_LOAD_STATE:
1507 filename2 = choose_file_load(this, "Load State", lsnes_instance.project.moviepath(),
1508 filetype_savestate);
1509 recent_movies->add(filename2.second);
1510 lsnes_instance.iqueue.queue("load" + filename2.first + " " + filename2.second);
1511 return;
1512 case wxID_REWIND_MOVIE:
1513 lsnes_instance.iqueue.queue("rewind-movie");
1514 return;
1515 case wxID_SAVE_MOVIE:
1516 filename2 = choose_file_save(this, "Save Movie", lsnes_instance.project.moviepath(), filetype_movie,
1517 project_prefixname("lsmv"));
1518 recent_movies->add(filename2.second);
1519 lsnes_instance.iqueue.queue("save-movie" + filename2.first + " " + filename2.second);
1520 return;
1521 case wxID_SAVE_SUBTITLES:
1522 lsnes_instance.iqueue.queue("save-subtitle " + choose_file_save(this, "Save subtitles",
1523 lsnes_instance.project.moviepath(), filetype_sub, project_prefixname("sub")));
1524 return;
1525 case wxID_SAVE_STATE:
1526 filename2 = choose_file_save(this, "Save State", lsnes_instance.project.moviepath(),
1527 filetype_savestate);
1528 recent_movies->add(filename2.second);
1529 lsnes_instance.iqueue.queue("save-state" + filename2.first + " " + filename2.second);
1530 return;
1531 case wxID_SAVE_SCREENSHOT:
1532 lsnes_instance.iqueue.queue("take-screenshot " + choose_file_save(this, "Save Screenshot",
1533 lsnes_instance.project.moviepath(), filetype_png, get_default_screenshot_name()));
1534 return;
1535 case wxID_RUN_SCRIPT:
1536 lsnes_instance.iqueue.queue("run-script " + pick_file_member(this, "Select Script",
1537 lsnes_instance.project.otherpath()));
1538 return;
1539 case wxID_RUN_LUA: {
1540 std::string f = choose_file_load(this, "Select Lua Script", lsnes_instance.project.otherpath(),
1541 filetype_lua_script);
1542 lsnes_instance.iqueue.queue("run-lua " + f);
1543 recent_scripts->add(f);
1544 return;
1546 case wxID_RESET_LUA:
1547 lsnes_instance.iqueue.queue("reset-lua");
1548 return;
1549 case wxID_EVAL_LUA:
1550 lsnes_instance.iqueue.queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1551 return;
1552 case wxID_READONLY_MODE:
1553 s = menu_ischecked(wxID_READONLY_MODE);
1554 lsnes_instance.iqueue.run([s]() {
1555 if(!s)
1556 lua_callback_movie_lost("readwrite");
1557 if(lsnes_instance.mlogic) lsnes_instance.mlogic.get_movie().readonly_mode(s);
1558 notify_mode_change(s);
1559 if(!s)
1560 lua_callback_do_readwrite();
1561 update_movie_state();
1562 graphics_driver_notify_status();
1564 return;
1565 case wxID_AUTOHOLD:
1566 wxeditor_autohold_display(this);
1567 return;
1568 case wxID_EDIT_AUTHORS:
1569 wxeditor_authors_display(this);
1570 return;
1571 case wxID_EDIT_MACROS:
1572 wxeditor_macro_display(this);
1573 return;
1574 case wxID_EDIT_SUBTITLES:
1575 wxeditor_subtitles_display(this);
1576 return;
1577 case wxID_EDIT_VSUBTITLES:
1578 show_wxeditor_voicesub(this);
1579 return;
1580 case wxID_EDIT_MEMORYWATCH:
1581 wxeditor_memorywatches_display(this);
1582 return;
1583 case wxID_SAVE_MEMORYWATCH: {
1584 modal_pause_holder hld;
1585 std::set<std::string> old_watches;
1586 lsnes_instance.iqueue.run([&old_watches]() { old_watches = lsnes_instance.mwatch.enumerate(); });
1587 std::string filename = choose_file_save(this, "Save watches to file",
1588 lsnes_instance.project.otherpath(), filetype_watch);
1589 std::ofstream out(filename.c_str());
1590 for(auto i : old_watches) {
1591 std::string val;
1592 lsnes_instance.iqueue.run([i, &val]() {
1593 try {
1594 val = lsnes_instance.mwatch.get_string(i);
1595 } catch(std::exception& e) {
1596 messages << "Can't get value of watch '" << i << "': " << e.what()
1597 << std::endl;
1600 out << i << std::endl << val << std::endl;
1602 out.close();
1603 return;
1605 case wxID_LOAD_MEMORYWATCH: {
1606 modal_pause_holder hld;
1607 std::set<std::string> old_watches;
1608 lsnes_instance.iqueue.run([&old_watches]() { old_watches = lsnes_instance.mwatch.enumerate(); });
1609 std::map<std::string, std::string> new_watches;
1610 std::string filename = choose_file_load(this, "Choose memory watch file",
1611 lsnes_instance.project.otherpath(), filetype_watch);
1612 try {
1613 std::istream& in = zip::openrel(filename, "");
1614 while(in) {
1615 std::string wname;
1616 std::string wexpr;
1617 std::getline(in, wname);
1618 std::getline(in, wexpr);
1619 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1621 delete &in;
1622 } catch(std::exception& e) {
1623 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1624 wxICON_EXCLAMATION);
1625 return;
1628 lsnes_instance.iqueue.run([&new_watches, &old_watches]() {
1629 handle_watch_load(new_watches, old_watches);
1631 return;
1633 case wxID_MEMORY_SEARCH:
1634 wxwindow_memorysearch_display();
1635 return;
1636 case wxID_TASINPUT:
1637 wxeditor_tasinput_display(this);
1638 return;
1639 case wxID_ABOUT: {
1640 std::ostringstream str;
1641 str << "Version: lsnes rr" << lsnes_version << std::endl;
1642 str << "Revision: " << lsnes_git_revision << std::endl;
1643 for(auto i : core_core::all_cores())
1644 if(!i->is_hidden())
1645 str << "Core: " << i->get_core_identifier() << std::endl;
1646 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1647 return;
1649 case wxID_SHOW_STATUS: {
1650 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1651 if(newstate)
1652 spanel->Show();
1653 if(newstate && !spanel_shown)
1654 toplevel->Add(spanel, 1, wxGROW);
1655 else if(!newstate && spanel_shown)
1656 toplevel->Detach(spanel);
1657 if(!newstate)
1658 spanel->Hide();
1659 spanel_shown = newstate;
1660 toplevel->Layout();
1661 toplevel->SetSizeHints(this);
1662 Fit();
1663 return;
1665 case wxID_DEDICATED_MEMORY_WATCH: {
1666 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1667 if(newstate && !mwindow) {
1668 mwindow = new wxwin_status(-1, "Memory Watch");
1669 spanel->set_watch_flag(1);
1670 mwindow->Show();
1671 } else if(!newstate && mwindow) {
1672 mwindow->Destroy();
1673 mwindow = NULL;
1674 spanel->set_watch_flag(0);
1676 return;
1678 case wxID_SET_SPEED: {
1679 std::string value = "infinite";
1680 double val = lsnes_instance.framerate.get_speed_multiplier();
1681 if(!(val == std::numeric_limits<double>::infinity()))
1682 value = (stringfmt() << (100 * val)).str();
1683 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1684 try {
1685 if(value == "infinite")
1686 lsnes_instance.framerate.set_speed_multiplier(
1687 std::numeric_limits<double>::infinity());
1688 else {
1689 double v = parse_value<double>(value) / 100;
1690 if(v <= 0.0001)
1691 throw 42;
1692 lsnes_instance.framerate.set_speed_multiplier(v);
1694 } catch(...) {
1695 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1697 return;
1699 case wxID_SPEED_5:
1700 set_speed(5);
1701 break;
1702 case wxID_SPEED_10:
1703 set_speed(10);
1704 break;
1705 case wxID_SPEED_17:
1706 set_speed(16.66666666666);
1707 break;
1708 case wxID_SPEED_20:
1709 set_speed(20);
1710 break;
1711 case wxID_SPEED_25:
1712 set_speed(25);
1713 break;
1714 case wxID_SPEED_33:
1715 set_speed(33.3333333333333);
1716 break;
1717 case wxID_SPEED_50:
1718 set_speed(50);
1719 break;
1720 case wxID_SPEED_100:
1721 set_speed(100);
1722 break;
1723 case wxID_SPEED_150:
1724 set_speed(150);
1725 break;
1726 case wxID_SPEED_200:
1727 set_speed(200);
1728 break;
1729 case wxID_SPEED_300:
1730 set_speed(300);
1731 break;
1732 case wxID_SPEED_500:
1733 set_speed(500);
1734 break;
1735 case wxID_SPEED_1000:
1736 set_speed(1000);
1737 break;
1738 case wxID_SPEED_TURBO:
1739 set_speed(-1);
1740 break;
1741 case wxID_LOAD_LIBRARY: {
1742 std::string name = std::string("load ") + loadlib::library::name();
1743 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name,
1744 lsnes_instance.project.otherpath(), single_type(loadlib::library::extension(),
1745 loadlib::library::name())))));
1746 handle_post_loadlibrary();
1747 break;
1749 case wxID_PLUGIN_MANAGER:
1750 wxeditor_plugin_manager_display(this);
1751 return;
1752 case wxID_RELOAD_ROM_IMAGE:
1753 lsnes_instance.iqueue.run([]() {
1754 lsnes_instance.command.invoke("unpause-emulator");
1755 reload_current_rom();
1757 return;
1758 case wxID_NEW_MOVIE:
1759 show_projectwindow(this);
1760 return;
1761 case wxID_SHOW_MESSAGES:
1762 msg_window->reshow();
1763 return;
1764 case wxID_CONFLICTRESOLUTION:
1765 show_conflictwindow(this);
1766 return;
1767 case wxID_VUDISPLAY:
1768 open_vumeter_window(this);
1769 return;
1770 case wxID_DISASSEMBLER:
1771 wxeditor_disassembler_display(this);
1772 return;
1773 case wxID_MOVIE_EDIT:
1774 wxeditor_movie_display(this);
1775 return;
1776 case wxID_NEW_PROJECT:
1777 open_new_project_window(this);
1778 return;
1779 case wxID_CLOSE_PROJECT:
1780 lsnes_instance.iqueue.run([]() -> void { lsnes_instance.project.set(NULL); });
1781 return;
1782 case wxID_CLOSE_ROM:
1783 lsnes_instance.iqueue.run([]() -> void { close_rom(); });
1784 return;
1785 case wxID_ENTER_FULLSCREEN:
1786 wx_escape_count = 0;
1787 enter_or_leave_fullscreen(true);
1788 return;
1789 case wxID_LOAD_ROM_IMAGE_FIRST:
1790 do_load_rom_image(NULL);
1791 return;
1792 case wxID_HEXEDITOR:
1793 wxeditor_hexedit_display(this);
1794 return;
1795 case wxID_MULTITRACK:
1796 wxeditor_multitrack_display(this);
1797 return;
1798 case wxID_CHDIR: {
1799 wxDirDialog* d = new wxDirDialog(this, wxT("Change working directory"), wxT("."), wxDD_DIR_MUST_EXIST);
1800 if(d->ShowModal() == wxID_CANCEL) {
1801 d->Destroy();
1802 return;
1804 std::string path = tostdstring(d->GetPath());
1805 d->Destroy();
1806 chdir(path.c_str());
1807 messages << "Changed working directory to '" << path << "'" << std::endl;
1808 return;
1810 case wxID_DOWNLOAD: {
1811 if(download_in_progress) return;
1812 filename = pick_text(this, "Download movie", "Enter URL to download");
1813 download_in_progress = new file_download();
1814 download_in_progress->url = lsnes_uri_rewrite(filename);
1815 download_in_progress->target_slot = "wxwidgets_download_tmp";
1816 download_in_progress->do_async();
1817 new download_timer(this);
1818 return;
1823 void wxwin_mainwindow::action_updated()
1825 reinterpret_cast<system_menu*>(sysmenu)->update(true);
1828 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs)
1830 if(fs && !is_fs) {
1831 if(spanel_shown)
1832 toplevel->Detach(spanel);
1833 spanel->Hide();
1834 is_fs = fs;
1835 ShowFullScreen(true);
1836 Fit();
1837 } else if(!fs && is_fs) {
1838 ShowFullScreen(false);
1839 if(spanel_shown) {
1840 spanel->Show();
1841 toplevel->Add(spanel, 1, wxGROW);
1843 Fit();
1844 is_fs = fs;