5 #include "core/command.hpp"
6 #include "core/controller.hpp"
7 #include "core/dispatch.hpp"
8 #include "core/framerate.hpp"
9 #include "core/joystickapi.hpp"
10 #include "core/keymapper.hpp"
11 #include "core/loadlib.hpp"
12 #include "lua/lua.hpp"
13 #include "core/advdumper.hpp"
14 #include "core/mainloop.hpp"
15 #include "core/misc.hpp"
16 #include "core/instance.hpp"
17 #include "core/moviefile-common.hpp"
18 #include "core/moviedata.hpp"
19 #include "core/random.hpp"
20 #include "core/rom.hpp"
21 #include "core/settings.hpp"
22 #include "core/window.hpp"
23 #include "interface/romtype.hpp"
24 #include "library/crandom.hpp"
25 #include "library/directory.hpp"
26 #include "library/string.hpp"
27 #include "library/threads.hpp"
28 #include "library/utf8.hpp"
29 #include "library/zip.hpp"
31 #include "platform/wxwidgets/settings-common.hpp"
32 #include "platform/wxwidgets/platform.hpp"
33 #include "platform/wxwidgets/window_messages.hpp"
34 #include "platform/wxwidgets/window_status.hpp"
35 #include "platform/wxwidgets/window_mainwindow.hpp"
38 #include <boost/lexical_cast.hpp>
42 #include <wx/control.h>
43 #include <wx/combobox.h>
44 #include <wx/cmdline.h>
47 #define UISERV_REFRESH_TITLE 9990
48 #define UISERV_RESIZED 9991
49 #define UISERV_UIFUN 9992
50 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
51 #define UISERV_EXIT 9994
52 #define UISERV_UPDATE_STATUS 9995
53 #define UISERV_UPDATE_MESSAGES 9996
54 #define UISERV_UPDATE_SCREEN 9997
55 #define UISERV_PANIC 9998
56 #define UISERV_ERROR 9999
58 wxwin_messages
* msg_window
;
59 wxwin_mainwindow
* main_window
;
60 std::string our_rom_name
;
62 bool wxwidgets_exiting
= false;
66 threads::id ui_thread
;
67 volatile bool panic_ack
= false;
68 std::string error_message_text
;
69 volatile bool modal_dialog_confirm
;
70 volatile bool modal_dialog_active
;
71 threads::lock ui_mutex
;
72 threads::cv ui_condition
;
73 threads::thread
* joystick_thread_handle
;
74 bool if_update_messages
= false;
75 bool if_update_screen
= false;
76 bool if_update_status
= false;
77 threads::lock if_mutex
;
79 void* joystick_thread(int _args
)
81 joystick_driver_thread_fn();
85 struct uiserv_event
: public wxEvent
87 uiserv_event(int code
)
92 wxEvent
* Clone() const
94 return new uiserv_event(*this);
98 class ui_services_type
: public wxEvtHandler
100 bool ProcessEvent(wxEvent
& event
);
103 struct ui_queue_entry
109 std::list
<ui_queue_entry
> ui_queue
;
111 bool ui_services_type::ProcessEvent(wxEvent
& event
)
113 int c
= event
.GetId();
114 if(c
== UISERV_PANIC
) {
116 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR
|
119 } else if(c
== UISERV_REFRESH_TITLE
) {
121 main_window
->refresh_title();
122 } else if(c
== UISERV_RESIZED
) {
124 main_window
->notify_resized();
125 } else if(c
== UISERV_ERROR
) {
126 std::string text
= error_message_text
;
127 wxMessageBox(towxstring(text
), _T("lsnes: Error"), wxICON_EXCLAMATION
| wxOK
, main_window
);
128 } else if(c
== UISERV_UPDATE_MESSAGES
) {
129 { threads::alock
h(if_mutex
); if_update_messages
= false; }
131 msg_window
->notify_update();
132 } else if(c
== UISERV_UPDATE_STATUS
) {
133 { threads::alock
h(if_mutex
); if_update_status
= false; }
135 main_window
->notify_update_status();
136 wxeditor_movie_update();
137 wxeditor_hexeditor_update();
138 } else if(c
== UISERV_UPDATE_SCREEN
) {
139 { threads::alock
h(if_mutex
); if_update_screen
= false; }
141 main_window
->notify_update();
142 wxwindow_memorysearch_update();
143 wxwindow_tasinput_update();
144 } else if(c
== UISERV_EXIT
) {
146 main_window
->notify_exit();
147 } else if(c
== UISERV_UIFUN
) {
148 std::list
<ui_queue_entry
>::iterator i
;
150 queue_synchronous_fn_warning
= true;
153 threads::alock
h(ui_mutex
);
156 i
= ui_queue
.begin();
163 queue_synchronous_fn_warning
= false;
168 ui_services_type
* ui_services
;
170 void post_ui_event(int code
)
172 //Coalesce some messages.
173 if(code
== UISERV_UPDATE_MESSAGES
) {
174 threads::alock
h(if_mutex
);
175 if(if_update_messages
) return;
176 if_update_messages
= true;
178 if(code
== UISERV_UPDATE_SCREEN
) {
179 threads::alock
h(if_mutex
);
180 if(if_update_screen
) return;
181 if_update_screen
= true;
183 if(code
== UISERV_UPDATE_STATUS
) {
184 threads::alock
h(if_mutex
);
185 if(if_update_status
) return;
186 if_update_status
= true;
188 uiserv_event
uic(code
);
189 wxPostEvent(ui_services
, uic
);
192 void handle_config_line(std::string line
)
195 if(r
= regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
196 lsnes_instance
.setcache
->set(r
[1], r
[2], true);
197 messages
<< "Setting " << r
[1] << " set to " << r
[2] << std::endl
;
198 } else if(r
= regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
199 if(!lsnes_instance
.command
->valid_alias_name(r
[1])) {
200 messages
<< "Illegal alias name " << r
[1] << std::endl
;
203 std::string tmp
= lsnes_instance
.command
->get_alias_for(r
[1]);
204 tmp
= tmp
+ r
[2] + "\n";
205 lsnes_instance
.command
->set_alias_for(r
[1], tmp
);
206 messages
<< r
[1] << " aliased to " << r
[2] << std::endl
;
207 } else if(r
= regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line
)) {
208 std::string tmp
= r
[4];
209 regex_results r2
= regex("(load|load-smart|load-readonly|load-preserve|load-state"
210 "|load-movie|save-state|save-movie)[ \t]+\\$\\{project\\}(.*)\\.lsmv", tmp
);
211 if(r2
) tmp
= r2
[1] + " $SLOT:" + r2
[2];
212 lsnes_instance
.mapper
->bind(r
[1], r
[2], r
[3], tmp
);
213 if(r
[1] != "" || r
[2] != "")
214 messages
<< r
[1] << "/" << r
[2] << " ";
215 messages
<< r
[3] << " bound to '" << tmp
<< "'" << std::endl
;
216 } else if(r
= regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line
)) {
217 keyboard::ctrlrkey
* ckey
= lsnes_instance
.mapper
->get_controllerkey(r
[2]);
220 messages
<< r
[1] << " bound (button) to " << r
[2] << std::endl
;
222 lsnes_instance
.buttons
->button_keys
[r
[2]] = r
[1];
223 } else if(r
= regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
225 core_selections
[r
[1]] = r
[2];
226 messages
<< "Prefer " << r
[2] << " for " << r
[1] << std::endl
;
229 (stringfmt() << "Unrecognized directive: " << line
).throwex();
232 void load_configuration()
234 std::string cfg
= get_config_path() + "/lsneswxw.cfg";
235 std::ifstream
cfgfile(cfg
.c_str());
238 while(std::getline(cfgfile
, line
)) {
240 handle_config_line(line
);
241 } catch(std::exception
& e
) {
242 messages
<< "Error processing line " << lineno
<< ": " << e
.what() << std::endl
;
246 (*lsnes_instance
.abindmanager
)();
247 lsnes_uri_rewrite
.load(get_config_path() + "/lsnesurirewrite.cfg");
250 void save_configuration()
252 std::string cfg
= get_config_path() + "/lsneswxw.cfg";
253 std::string cfgtmp
= cfg
+ ".tmp";
254 std::ofstream
cfgfile(cfgtmp
.c_str());
256 for(auto i
: lsnes_instance
.setcache
->get_all())
257 cfgfile
<< "SET " << i
.first
<< " " << i
.second
<< std::endl
;
259 for(auto i
: lsnes_instance
.command
->get_aliases()) {
260 std::string old_alias_value
= lsnes_instance
.command
->get_alias_for(i
);
261 while(old_alias_value
!= "") {
262 std::string aliasline
;
263 size_t s
= old_alias_value
.find_first_of("\n");
264 if(s
< old_alias_value
.length()) {
265 aliasline
= old_alias_value
.substr(0, s
);
266 old_alias_value
= old_alias_value
.substr(s
+ 1);
268 aliasline
= old_alias_value
;
269 old_alias_value
= "";
271 cfgfile
<< "ALIAS " << i
<< " " << aliasline
<< std::endl
;
275 for(auto i
: lsnes_instance
.mapper
->get_bindings())
276 cfgfile
<< "BIND " << std::string(i
) << " " << lsnes_instance
.mapper
->get(i
) << std::endl
;
278 for(auto i
: lsnes_instance
.mapper
->get_controller_keys()) {
281 while((b
= i
->get_string(idx
++)) != "")
282 cfgfile
<< "BUTTON " << b
<< " " << i
->get_command() << std::endl
;
284 for(auto i
: lsnes_instance
.buttons
->button_keys
)
285 cfgfile
<< "BUTTON " << i
.second
<< " " << i
.first
<< std::endl
;
286 for(auto i
: core_selections
)
288 cfgfile
<< "PREFER " << i
.first
<< " " << i
.second
<< std::endl
;
290 show_message_ok(NULL
, "Error Saving configuration", "Error saving configuration",
295 directory::rename_overwrite(cfgtmp
.c_str(), cfg
.c_str());
297 std::ofstream
lsave(get_config_path() + "/" + our_rom_name
+ ".ls");
299 lsnes_uri_rewrite
.save(get_config_path() + "/lsnesurirewrite.cfg");
302 void* eloop_helper(int x
)
304 platform::dummy_event_loop();
308 std::string
get_loaded_movie(const std::vector
<std::string
>& cmdline
)
310 for(auto i
: cmdline
)
311 if(!i
.empty() && i
[0] != '-')
317 wxString
towxstring(const std::string
& str
) throw(std::bad_alloc
)
319 return wxString(str
.c_str(), wxConvUTF8
);
322 std::string
tostdstring(const wxString
& str
) throw(std::bad_alloc
)
324 return std::string(str
.mb_str(wxConvUTF8
));
327 wxString
towxstring(const std::u32string
& str
) throw(std::bad_alloc
)
329 return wxString(utf8::to8(str
).c_str(), wxConvUTF8
);
332 std::u32string
tou32string(const wxString
& str
) throw(std::bad_alloc
)
334 return utf8::to32(std::string(str
.mb_str(wxConvUTF8
)));
337 std::string
pick_archive_member(wxWindow
* parent
, const std::string
& filename
) throw(std::bad_alloc
)
339 //Did we pick a .zip file?
342 zip::reader
zr(filename
);
343 std::vector
<wxString
> files
;
345 files
.push_back(towxstring(i
));
346 wxSingleChoiceDialog
* d2
= new wxSingleChoiceDialog(parent
, wxT("Select file within .zip"),
347 wxT("Select member"), files
.size(), &files
[0]);
348 if(d2
->ShowModal() == wxID_CANCEL
) {
352 f
= filename
+ "/" + tostdstring(d2
->GetStringSelection());
361 void signal_program_exit()
363 post_ui_event(UISERV_EXIT
);
366 void signal_resize_needed()
368 post_ui_event(UISERV_RESIZED
);
372 static const wxCmdLineEntryDesc dummy_descriptor_table
[] = {
373 { wxCMD_LINE_PARAM
, NULL
, NULL
, NULL
, wxCMD_LINE_VAL_STRING
, wxCMD_LINE_PARAM_OPTIONAL
|
374 wxCMD_LINE_PARAM_MULTIPLE
},
378 class lsnes_app
: public wxApp
382 virtual bool OnInit();
383 virtual int OnExit();
384 virtual void OnInitCmdLine(wxCmdLineParser
& parser
);
385 virtual bool OnCmdLineParsed(wxCmdLineParser
& parser
);
388 bool pluginmanager_mode
;
391 std::vector
<std::string
> cmdline
;
392 std::map
<std::string
, std::string
> c_settings
;
393 std::vector
<std::string
> c_lua
;
394 bool exit_immediately
;
395 bool fullscreen_mode
;
399 IMPLEMENT_APP(lsnes_app
)
401 lsnes_app::lsnes_app()
403 settings_mode
= false;
404 pluginmanager_mode
= false;
405 exit_immediately
= false;
406 fullscreen_mode
= false;
407 start_unpaused
= false;
410 void lsnes_app::OnInitCmdLine(wxCmdLineParser
& parser
)
412 parser
.SetDesc(dummy_descriptor_table
);
413 parser
.SetSwitchChars(wxT(""));
416 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser
& parser
)
418 for(size_t i
= 0; i
< parser
.GetParamCount(); i
++)
419 cmdline
.push_back(tostdstring(parser
.GetParam(i
)));
420 for(auto i
: cmdline
) {
422 if(i
== "--help" || i
== "-h") {
423 std::cout
<< "--settings: Show the settings dialog" << std::endl
;
424 std::cout
<< "--pluginmanager: Show the plugin manager" << std::endl
;
425 std::cout
<< "--fullscreen: Start fullscreen" << std::endl
;
426 std::cout
<< "--unpause: Start unpaused (only if ROM is loaded)" << std::endl
;
427 std::cout
<< "--rom=<filename>: Load specified ROM on startup" << std::endl
;
428 std::cout
<< "--load=<filename>: Load specified save/movie on starup" << std::endl
;
429 std::cout
<< "--lua=<filename>: Load specified Lua script on startup" << std::endl
;
430 std::cout
<< "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl
;
431 std::cout
<< "<filename>: Load specified ROM on startup" << std::endl
;
432 exit_immediately
= true;
435 if(i
== "--settings")
436 settings_mode
= true;
438 start_unpaused
= true;
439 if(i
== "--fullscreen")
440 fullscreen_mode
= true;
441 if(i
== "--pluginmanager")
442 pluginmanager_mode
= true;
443 if(r
= regex("--set=([^=]+)=(.+)", i
))
444 c_settings
[r
[1]] = r
[2];
445 if(r
= regex("--lua=(.+)", i
))
446 c_lua
.push_back(r
[1]);
451 bool lsnes_app::OnInit()
459 } catch(std::exception
& e
) {
460 show_message_ok(NULL
, "RNG error", "Error initializing system RNG", wxICON_ERROR
);
466 bring_app_foreground();
468 if(pluginmanager_mode
)
469 if(!wxeditor_plugin_manager_display(NULL
))
472 ui_services
= new ui_services_type();
474 ui_thread
= threads::this_id();
477 messages
<< "lsnes version: lsnes rr" << lsnes_version
<< std::endl
;
479 loaded_rom dummy_rom
;
480 std::map
<std::string
, std::string
> settings
;
481 auto ctrldata
= dummy_rom
.rtype
->controllerconfig(settings
);
482 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex());
484 lsnes_instance
.buttons
->reinit();
485 lsnes_instance
.controls
->set_ports(ports
);
487 std::string cfgpath
= get_config_path();
488 autoload_libraries([](const std::string
& libname
, const std::string
& error
, bool system
) {
489 show_message_ok(NULL
, "Error loading plugin " + libname
, "Error loading '" + libname
+ "'\n\n" +
490 error
, wxICON_EXCLAMATION
);
492 wxeditor_plugin_manager_notify_fail(libname
);
494 messages
<< "Saving per-user data to: " << get_config_path() << std::endl
;
495 messages
<< "--- Loading configuration --- " << std::endl
;
496 load_configuration();
497 messages
<< "--- End running lsnesrc --- " << std::endl
;
500 //We got to boot this up quite a bit to get the joystick driver working.
501 //In practicular, we need joystick thread and emulator thread in pause.
502 joystick_thread_handle
= new threads::thread(joystick_thread
, 6);
503 threads::thread
* dummy_loop
= new threads::thread(eloop_helper
, 8);
504 display_settings_dialog(NULL
, NULL
);
505 platform::exit_dummy_event_loop();
506 joystick_driver_signal();
507 joystick_thread_handle
->join();
509 save_configuration();
513 lsnes_instance
.mdumper
->set_output(&messages
.getstream());
515 joystick_thread_handle
= new threads::thread(joystick_thread
, 7);
517 msg_window
= new wxwin_messages();
520 init_main_callbacks();
521 const std::string movie_file
= get_loaded_movie(cmdline
);
525 rom
= construct_rom(movie_file
, cmdline
);
526 rom
.load(c_settings
, mov
.movie_rtc_second
, mov
.movie_rtc_subsecond
);
527 } catch(std::exception
& e
) {
528 std::cerr
<< "Can't load ROM: " << e
.what() << std::endl
;
529 show_message_ok(NULL
, "Error loading ROM", std::string("Error loading ROM:\n\n") +
530 e
.what(), wxICON_EXCLAMATION
);
531 quit_lua(); //Don't crash.
535 moviefile
* mov
= NULL
;
538 mov
= new moviefile(movie_file
, *rom
.rtype
);
539 rom
.load(mov
->settings
, mov
->movie_rtc_second
, mov
->movie_rtc_subsecond
);
540 } catch(std::exception
& e
) {
541 std::cerr
<< "Can't load state: " << e
.what() << std::endl
;
542 show_message_ok(NULL
, "Error loading movie", std::string("Error loading movie:\n\n") +
543 e
.what(), wxICON_EXCLAMATION
);
544 quit_lua(); //Don't crash.
548 mov
= new moviefile(rom
, c_settings
, DEFAULT_RTC_SECOND
, DEFAULT_RTC_SUBSECOND
);
551 mov
->start_paused
= start_unpaused
? !(rom
.rtype
&& !rom
.rtype
->isnull()) : true;
553 lua_add_startup_script(i
);
554 boot_emulator(rom
, *mov
, fullscreen_mode
);
558 int lsnes_app::OnExit()
562 //NULL these so no further messages will be sent.
568 save_configuration();
570 lsnes_instance
.mlogic
->release_memory();
571 joystick_driver_signal();
572 joystick_thread_handle
->join();
574 lsnes_instance
.buttons
->cleanup();
576 deinitialize_wx_keyboard();
580 void do_save_configuration()
582 save_configuration();
587 struct _graphics_driver drv
= {
588 .init
= []() -> void {
589 initialize_wx_keyboard();
591 .quit
= []() -> void {},
592 .notify_message
= []() -> void
594 post_ui_event(UISERV_UPDATE_MESSAGES
);
596 .notify_status
= []() -> void
598 post_ui_event(UISERV_UPDATE_STATUS
);
600 .notify_screen
= []() -> void
602 post_ui_event(UISERV_UPDATE_SCREEN
);
604 .error_message
= [](const std::string
& text
) -> void {
605 error_message_text
= text
;
606 post_ui_event(UISERV_ERROR
);
608 .fatal_error
= []() -> void {
609 //Fun: This can be called from any thread!
610 if(ui_thread
== threads::this_id()) {
612 platform::set_modal_pause(true);
613 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"),
614 wxICON_ERROR
| wxOK
);
616 //Emulation thread panic. Signal the UI thread.
617 post_ui_event(UISERV_PANIC
);
621 .name
= []() -> const char* { return "wxwidgets graphics plugin"; },
622 .action_updated
= []()
624 runuifun([]() -> void { main_window
->action_updated(); });
626 .request_rom
= [](rom_request
& req
)
628 rom_request
* _req
= &req
;
632 threads::alock
h(lock
);
633 runuifun([_req
, &lock
, &cv
, &done
]() -> void {
635 main_window
->request_rom(*_req
);
637 _req
->canceled
= true;
639 threads::alock
h(lock
);
647 struct graphics_driver
_drv(drv
);
650 void signal_core_change()
652 post_ui_event(UISERV_REFRESH_TITLE
);
655 void _runuifun_async(void (*fn
)(void*), void* arg
)
657 threads::alock
h(ui_mutex
);
661 ui_queue
.push_back(e
);
662 post_ui_event(UISERV_UIFUN
);
666 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
668 std::string
pick_file(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
)
670 wxString _title
= towxstring(title
);
671 wxString _startdir
= towxstring(startdir
);
672 std::string filespec
;
673 filespec
= "All files|*";
674 wxFileDialog
* d
= new wxFileDialog(parent
, _title
, _startdir
, wxT(""), towxstring(filespec
), wxFD_OPEN
);
675 if(d
->ShowModal() == wxID_CANCEL
)
676 throw canceled_exception();
677 std::string filename
= tostdstring(d
->GetPath());
680 throw canceled_exception();
684 std::string
pick_file_member(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
)
686 std::string filename
= pick_file(parent
, title
, startdir
);
687 //Did we pick a .zip file?
688 if(!regex_match(".*\\.[zZ][iI][pP]", filename
))
689 return filename
; //Not a ZIP.
691 zip::reader
zr(filename
);
692 std::vector
<std::string
> files
;
695 filename
= filename
+ "/" + pick_among(parent
, "Select member", "Select file within .zip", files
);
696 } catch(canceled_exception
& e
) {
697 //Throw these forward.
705 std::string
pick_among(wxWindow
* parent
, const std::string
& title
, const std::string
& prompt
,
706 const std::vector
<std::string
>& choices
, unsigned defaultchoice
)
708 std::vector
<wxString
> _choices
;
709 for(auto i
: choices
)
710 _choices
.push_back(towxstring(i
));
711 wxSingleChoiceDialog
* d2
= new wxSingleChoiceDialog(parent
, towxstring(prompt
), towxstring(title
),
712 _choices
.size(), &_choices
[0]);
713 d2
->SetSelection(defaultchoice
);
714 if(d2
->ShowModal() == wxID_CANCEL
) {
716 throw canceled_exception();
718 std::string out
= tostdstring(d2
->GetStringSelection());
723 std::string
pick_text(wxWindow
* parent
, const std::string
& title
, const std::string
& prompt
, const std::string
& dflt
,
726 wxTextEntryDialog
* d2
= new wxTextEntryDialog(parent
, towxstring(prompt
), towxstring(title
), towxstring(dflt
),
727 wxOK
| wxCANCEL
| wxCENTRE
| (multiline
? wxTE_MULTILINE
: 0));
728 if(d2
->ShowModal() == wxID_CANCEL
) {
730 throw canceled_exception();
732 std::string text
= tostdstring(d2
->GetValue());
737 void show_message_ok(wxWindow
* parent
, const std::string
& title
, const std::string
& text
, int icon
)
739 wxMessageDialog
* d3
= new wxMessageDialog(parent
, towxstring(text
), towxstring(title
), wxOK
| icon
);
744 bool run_show_error(wxWindow
* parent
, const std::string
& title
, const std::string
& text
, std::function
<void()> fn
)
749 } catch(std::exception
& e
) {
750 std::string err
= e
.what();
751 std::string _title
= title
;
752 std::string _text
= (text
== "") ? err
: (text
+ ": " + err
);
753 runuifun([parent
, _title
, _text
]() {
754 show_message_ok(parent
, _title
, _text
, wxICON_EXCLAMATION
);
760 void show_exception(wxWindow
* parent
, const std::string
& title
, const std::string
& text
, std::exception
& e
)
762 std::string err
= e
.what();
763 std::string _title
= title
;
764 std::string _text
= (text
== "") ? err
: (text
+ ": " + err
);
765 show_message_ok(parent
, _title
, _text
, wxICON_EXCLAMATION
);