1 #include "mainloop.hpp"
3 #include "framerate.hpp"
4 #include "memorywatch.hpp"
9 #include "moviefile.hpp"
11 #include "keymapper.hpp"
13 #include "settings.hpp"
19 #include "memorymanip.hpp"
20 #include "keymapper.hpp"
22 #include "videodumper2.hpp"
26 #include <snes/snes.hpp>
27 #include <ui-libsnes/libsnes.hpp>
28 #include "framerate.hpp"
30 #define LOAD_STATE_RW 0
31 #define LOAD_STATE_RO 1
32 #define LOAD_STATE_PRESERVE 2
33 #define LOAD_STATE_MOVIE 3
34 #define LOAD_STATE_DEFAULT 4
37 #define SPECIAL_FRAME_START 0
38 #define SPECIAL_FRAME_VIDEO 1
39 #define SPECIAL_SAVEPOINT 2
40 #define SPECIAL_NONE 3
42 #define BUTTON_LEFT 0 //Gamepad
43 #define BUTTON_RIGHT 1 //Gamepad
44 #define BUTTON_UP 2 //Gamepad
45 #define BUTTON_DOWN 3 //Gamepad
46 #define BUTTON_A 4 //Gamepad
47 #define BUTTON_B 5 //Gamepad
48 #define BUTTON_X 6 //Gamepad
49 #define BUTTON_Y 7 //Gamepad
50 #define BUTTON_L 8 //Gamepad & Mouse
51 #define BUTTON_R 9 //Gamepad & Mouse
52 #define BUTTON_SELECT 10 //Gamepad
53 #define BUTTON_START 11 //Gamepad & Justifier
54 #define BUTTON_TRIGGER 12 //Superscope.
55 #define BUTTON_CURSOR 13 //Superscope & Justifier
56 #define BUTTON_PAUSE 14 //Superscope
57 #define BUTTON_TURBO 15 //Superscope
59 void update_movie_state();
60 void draw_nosignal(uint16_t* target
);
61 void draw_corrupt(uint16_t* target
);
68 ADVANCE_QUIT
, //Quit the emulator.
69 ADVANCE_AUTO
, //Normal (possibly slowed down play).
70 ADVANCE_FRAME
, //Frame advance.
71 ADVANCE_SUBFRAME
, //Subframe advance.
72 ADVANCE_SKIPLAG
, //Skip lag (oneshot, reverts to normal).
73 ADVANCE_SKIPLAG_PENDING
, //Activate skip lag mode at next frame.
74 ADVANCE_PAUSE
, //Unconditional pause.
77 //Analog input physical controller IDs and types.
78 int analog
[4] = {-1, -1, -1};
79 bool analog_is_mouse
[4] = {false, false, false};
81 std::map
<std::string
, std::string
> memory_watches
;
82 //Previous mouse mask.
83 int prev_mouse_mask
= 0;
84 //Flags related to repeating advance.
88 struct loaded_rom
* our_rom
;
90 struct moviefile our_movie
;
91 //Handle to the graphics system.
95 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
96 enum advance_mode amode
;
97 //Mode and filename of pending load, one of LOAD_* constants.
99 std::string pending_load
;
100 //Queued saves (all savestates).
101 std::set
<std::string
> queued_saves
;
102 bool stepping_into_save
;
104 controls_t curcontrols
;
105 controls_t autoheld_controls
;
106 //Emulator status area.
107 std::map
<std::string
, std::string
>* status
;
108 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
109 long pending_reset_cycles
= -1;
110 //Set by every video refresh.
111 bool video_refresh_done
;
112 //Special subframe location. One of SPECIAL_* constants.
113 int location_special
;
114 //Types of connected controllers.
115 enum porttype_t porttype1
= PT_GAMEPAD
;
116 enum porttype_t porttype2
= PT_NONE
;
117 //System corrupt flag.
119 //Current screen, no signal screen and corrupt screen.
120 lcscreen framebuffer
;
121 lcscreen nosignal_screen
;
122 lcscreen corrupt_screen
;
124 numeric_setting
advance_timeout_first("advance-timeout", 0, 999999999, 500);
125 numeric_setting
savecompression("savecompression", 0, 9, 7);
127 void send_analog_input(int32_t x
, int32_t y
, unsigned index
)
129 if(analog_is_mouse
[index
]) {
131 y
-= (framebuffer
.height
/ 2);
136 if(analog
[index
] < 0) {
137 out(win
) << "No analog controller in slot #" << (index
+ 1) << std::endl
;
140 curcontrols(analog
[index
] >> 2, analog
[index
] & 3, 0) = x
;
141 curcontrols(analog
[index
] >> 2, analog
[index
] & 3, 1) = y
;
144 void redraw_framebuffer()
146 uint32_t hscl
= 1, vscl
= 1;
147 if(framebuffer
.width
< 512)
149 if(framebuffer
.height
< 400)
152 struct lua_render_context lrc
;
158 lrc
.width
= framebuffer
.width
* hscl
;
159 lrc
.height
= framebuffer
.height
* vscl
;
160 lrc
.rshift
= scr
.active_rshift
;
161 lrc
.gshift
= scr
.active_gshift
;
162 lrc
.bshift
= scr
.active_bshift
;
163 lua_callback_do_paint(&lrc
, win
);
164 scr
.reallocate(framebuffer
.width
* hscl
+ lrc
.left_gap
+ lrc
.right_gap
, framebuffer
.height
* vscl
+
165 lrc
.top_gap
+ lrc
.bottom_gap
, lrc
.left_gap
, lrc
.top_gap
);
166 scr
.copy_from(framebuffer
, hscl
, vscl
);
167 //We would want divide by 2, but we'll do it ourselves in order to do mouse.
168 win
->set_window_compensation(lrc
.left_gap
, lrc
.top_gap
, 1, 1);
170 win
->notify_screen_update();
173 void fill_special_frames()
175 uint16_t buf
[512*448];
177 nosignal_screen
= lcscreen(buf
, 512, 448);
179 corrupt_screen
= lcscreen(buf
, 512, 448);
183 class firmware_path_setting
: public setting
186 firmware_path_setting() : setting("firmwarepath") { _firmwarepath
= "./"; default_firmware
= true; }
187 void blank() throw(std::bad_alloc
, std::runtime_error
)
189 _firmwarepath
= "./";
190 default_firmware
= true;
193 bool is_set() throw()
195 return !default_firmware
;
198 void set(const std::string
& value
) throw(std::bad_alloc
, std::runtime_error
)
200 _firmwarepath
= value
;
201 default_firmware
= false;
204 std::string
get() throw(std::bad_alloc
)
206 return _firmwarepath
;
209 operator std::string() throw(std::bad_alloc
)
211 return _firmwarepath
;
214 std::string _firmwarepath
;
215 bool default_firmware
;
216 } firmwarepath_setting
;
218 class mymovielogic
: public movie_logic
221 mymovielogic() : movie_logic(dummy_movie
) {}
223 controls_t
update_controls(bool subframe
) throw(std::bad_alloc
, std::runtime_error
)
225 if(lua_requests_subframe_paint
)
226 redraw_framebuffer();
229 if(amode
== ADVANCE_SUBFRAME
) {
230 if(!cancel_advance
&& !advanced_once
) {
231 win
->wait_msec(advance_timeout_first
);
232 advanced_once
= true;
235 amode
= ADVANCE_PAUSE
;
236 cancel_advance
= false;
238 win
->paused(amode
== ADVANCE_PAUSE
);
239 } else if(amode
== ADVANCE_FRAME
) {
242 win
->paused(amode
== ADVANCE_SKIPLAG
|| amode
== ADVANCE_PAUSE
);
243 cancel_advance
= false;
245 if(amode
== ADVANCE_SKIPLAG
)
246 amode
= ADVANCE_AUTO
;
247 location_special
= SPECIAL_NONE
;
248 update_movie_state();
250 if(amode
== ADVANCE_SKIPLAG_PENDING
)
251 amode
= ADVANCE_SKIPLAG
;
252 if(amode
== ADVANCE_FRAME
|| amode
== ADVANCE_SUBFRAME
) {
253 if(!cancel_advance
) {
254 win
->wait_msec(advanced_once
? to_wait_frame(get_ticks_msec()) :
255 advance_timeout_first
);
256 advanced_once
= true;
259 amode
= ADVANCE_PAUSE
;
260 cancel_advance
= false;
262 win
->paused(amode
== ADVANCE_PAUSE
);
264 win
->paused((amode
== ADVANCE_PAUSE
));
265 cancel_advance
= false;
267 location_special
= SPECIAL_FRAME_START
;
268 update_movie_state();
270 win
->notify_screen_update();
272 if(!subframe
&& pending_reset_cycles
>= 0) {
273 curcontrols(CONTROL_SYSTEM_RESET
) = 1;
274 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = pending_reset_cycles
/ 10000;
275 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = pending_reset_cycles
% 10000;
276 } else if(!subframe
) {
277 curcontrols(CONTROL_SYSTEM_RESET
) = 0;
278 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = 0;
279 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = 0;
281 controls_t tmp
= curcontrols
^ autoheld_controls
;
282 lua_callback_do_input(tmp
, subframe
, win
);
293 //Lookup physical controller id based on UI controller id and given types (-1 if invalid).
294 int lookup_physical_controller(unsigned ui_id
)
296 bool p1multitap
= (porttype1
== PT_MULTITAP
);
297 unsigned p1devs
= port_types
[porttype1
].devices
;
298 unsigned p2devs
= port_types
[porttype2
].devices
;
299 if(ui_id
>= p1devs
+ p2devs
)
305 return 4 + ui_id
- p1devs
;
315 //Look up controller type given UI controller id (note: Non-present controllers give PT_NONE, not the type
316 //of port, multitap controllers give PT_GAMEPAD, not PT_MULTITAP, and justifiers give PT_JUSTIFIER, not
318 enum devicetype_t
lookup_controller_type(unsigned ui_id
)
320 int x
= lookup_physical_controller(ui_id
);
323 enum porttype_t rawtype
= (x
& 4) ? porttype2
: porttype1
;
324 if((x
& 3) < port_types
[rawtype
].devices
)
325 return port_types
[rawtype
].dtype
;
330 void set_analog_controllers()
333 for(unsigned i
= 0; i
< 8; i
++) {
334 enum devicetype_t t
= lookup_controller_type(i
);
335 analog_is_mouse
[index
] = (t
== DT_MOUSE
);
336 if(t
== DT_MOUSE
|| t
== DT_SUPERSCOPE
|| t
== DT_JUSTIFIER
) {
337 analog
[index
++] = lookup_physical_controller(i
);
341 for(; index
< 3; index
++)
345 std::map
<std::string
, std::pair
<unsigned, unsigned>> buttonmap
;
347 const char* buttonnames
[] = {
348 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
352 void init_buttonmap()
357 for(unsigned i
= 0; i
< 8; i
++)
358 for(unsigned j
= 0; j
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); j
++) {
359 std::ostringstream x
;
360 x
<< (i
+ 1) << buttonnames
[j
];
361 buttonmap
[x
.str()] = std::make_pair(i
, j
);
367 void do_button_action(unsigned ui_id
, unsigned button
, short newstate
, bool do_xor
= false)
369 enum devicetype_t p
= lookup_controller_type(ui_id
);
370 int x
= lookup_physical_controller(ui_id
);
374 out(win
) << "No such controller #" << (ui_id
+ 1) << std::endl
;
378 case BUTTON_UP
: bid
= SNES_DEVICE_ID_JOYPAD_UP
; break;
379 case BUTTON_DOWN
: bid
= SNES_DEVICE_ID_JOYPAD_DOWN
; break;
380 case BUTTON_LEFT
: bid
= SNES_DEVICE_ID_JOYPAD_LEFT
; break;
381 case BUTTON_RIGHT
: bid
= SNES_DEVICE_ID_JOYPAD_RIGHT
; break;
382 case BUTTON_A
: bid
= SNES_DEVICE_ID_JOYPAD_A
; break;
383 case BUTTON_B
: bid
= SNES_DEVICE_ID_JOYPAD_B
; break;
384 case BUTTON_X
: bid
= SNES_DEVICE_ID_JOYPAD_X
; break;
385 case BUTTON_Y
: bid
= SNES_DEVICE_ID_JOYPAD_Y
; break;
386 case BUTTON_L
: bid
= SNES_DEVICE_ID_JOYPAD_L
; break;
387 case BUTTON_R
: bid
= SNES_DEVICE_ID_JOYPAD_R
; break;
388 case BUTTON_SELECT
: bid
= SNES_DEVICE_ID_JOYPAD_SELECT
; break;
389 case BUTTON_START
: bid
= SNES_DEVICE_ID_JOYPAD_START
; break;
391 out(win
) << "Invalid button for gamepad" << std::endl
;
397 case BUTTON_L
: bid
= SNES_DEVICE_ID_MOUSE_LEFT
; break;
398 case BUTTON_R
: bid
= SNES_DEVICE_ID_MOUSE_RIGHT
; break;
400 out(win
) << "Invalid button for mouse" << std::endl
;
406 case BUTTON_START
: bid
= SNES_DEVICE_ID_JUSTIFIER_START
; break;
407 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_JUSTIFIER_TRIGGER
; break;
409 out(win
) << "Invalid button for justifier" << std::endl
;
415 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
; break;
416 case BUTTON_CURSOR
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
; break;
417 case BUTTON_PAUSE
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
; break;
418 case BUTTON_TURBO
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TURBO
; break;
420 out(win
) << "Invalid button for superscope" << std::endl
;
426 autoheld_controls((x
& 4) ? 1 : 0, x
& 3, bid
) ^= newstate
;
428 curcontrols((x
& 4) ? 1 : 0, x
& 3, bid
) = newstate
;
431 //Recognize and react to [+-]controller commands.
432 bool do_button_action(std::string cmd
)
435 if(cmd
.length() < 12)
437 std::string prefix
= cmd
.substr(0, 11);
438 if(prefix
!= "+controller" && prefix
!= "-controller" && prefix
!= "controllerh")
440 std::string button
= cmd
.substr(11);
441 if(!buttonmap
.count(button
))
443 auto i
= buttonmap
[button
];
444 do_button_action(i
.first
, i
.second
, (cmd
[0] != '-') ? 1 : 0, (cmd
[0] == 'c'));
449 void do_save_state(const std::string
& filename
) throw(std::bad_alloc
,
452 lua_callback_pre_save(filename
, true, win
);
454 uint64_t origtime
= get_ticks_msec();
455 our_movie
.is_savestate
= true;
456 our_movie
.sram
= save_sram();
457 our_movie
.savestate
= save_core_state();
458 framebuffer
.save(our_movie
.screenshot
);
459 auto s
= movb
.get_movie().save_state();
460 our_movie
.movie_state
.resize(s
.size());
461 memcpy(&our_movie
.movie_state
[0], &s
[0], s
.size());
462 our_movie
.input
= movb
.get_movie().save();
463 our_movie
.save(filename
, savecompression
);
464 uint64_t took
= get_ticks_msec() - origtime
;
465 out(win
) << "Saved state '" << filename
<< "' in " << took
<< "ms." << std::endl
;
466 lua_callback_post_save(filename
, true, win
);
467 } catch(std::bad_alloc
& e
) {
469 } catch(std::exception
& e
) {
470 win
->message(std::string("Save failed: ") + e
.what());
471 lua_callback_err_save(filename
, win
);
476 void do_save_movie(const std::string
& filename
) throw(std::bad_alloc
, std::runtime_error
)
478 lua_callback_pre_save(filename
, false, win
);
480 uint64_t origtime
= get_ticks_msec();
481 our_movie
.is_savestate
= false;
482 our_movie
.input
= movb
.get_movie().save();
483 our_movie
.save(filename
, savecompression
);
484 uint64_t took
= get_ticks_msec() - origtime
;
485 out(win
) << "Saved movie '" << filename
<< "' in " << took
<< "ms." << std::endl
;
486 lua_callback_post_save(filename
, false, win
);
487 } catch(std::bad_alloc
& e
) {
489 } catch(std::exception
& e
) {
490 win
->message(std::string("Save failed: ") + e
.what());
491 lua_callback_err_save(filename
, win
);
495 void warn_hash_mismatch(const std::string
& mhash
, const loaded_slot
& slot
, const std::string
& name
)
497 if(mhash
!= slot
.sha256
) {
498 out(win
) << "WARNING: " << name
<< " hash mismatch!" << std::endl
499 << "\tMovie: " << mhash
<< std::endl
500 << "\tOur ROM: " << slot
.sha256
<< std::endl
;
504 void set_dev(bool port
, porttype_t t
, bool set_core
= true)
507 switch(set_core
? t
: PT_INVALID
) {
509 snes_set_controller_port_device(port
, SNES_DEVICE_NONE
);
512 snes_set_controller_port_device(port
, SNES_DEVICE_JOYPAD
);
515 snes_set_controller_port_device(port
, SNES_DEVICE_MULTITAP
);
518 snes_set_controller_port_device(port
, SNES_DEVICE_MOUSE
);
521 snes_set_controller_port_device(port
, SNES_DEVICE_SUPER_SCOPE
);
524 snes_set_controller_port_device(port
, SNES_DEVICE_JUSTIFIER
);
527 snes_set_controller_port_device(port
, SNES_DEVICE_JUSTIFIERS
);
536 set_analog_controllers();
539 //Load state from loaded movie file (does not catch errors).
540 void do_load_state(struct moviefile
& _movie
, int lmode
)
542 bool will_load_state
= _movie
.is_savestate
&& lmode
!= LOAD_STATE_MOVIE
;
543 if(gtype::toromtype(_movie
.gametype
) != our_rom
->rtype
)
544 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
545 if(gtype::toromregion(_movie
.gametype
) != our_rom
->orig_region
&& our_rom
->orig_region
!= REGION_AUTO
)
546 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
548 if(_movie
.coreversion
!= bsnes_core_version
) {
549 if(will_load_state
) {
550 std::ostringstream x
;
551 x
<< "ERROR: Emulator core version mismatch!" << std::endl
552 << "\tThis version: " << bsnes_core_version
<< std::endl
553 << "\tFile is from: " << _movie
.coreversion
<< std::endl
;
554 throw std::runtime_error(x
.str());
556 out(win
) << "WARNING: Emulator core version mismatch!" << std::endl
557 << "\tThis version: " << bsnes_core_version
<< std::endl
558 << "\tFile is from: " << _movie
.coreversion
<< std::endl
;
560 warn_hash_mismatch(_movie
.rom_sha256
, our_rom
->rom
, "ROM #1");
561 warn_hash_mismatch(_movie
.romxml_sha256
, our_rom
->rom_xml
, "XML #1");
562 warn_hash_mismatch(_movie
.slota_sha256
, our_rom
->slota
, "ROM #2");
563 warn_hash_mismatch(_movie
.slotaxml_sha256
, our_rom
->slota_xml
, "XML #2");
564 warn_hash_mismatch(_movie
.slotb_sha256
, our_rom
->slotb
, "ROM #3");
565 warn_hash_mismatch(_movie
.slotbxml_sha256
, our_rom
->slotb_xml
, "XML #3");
567 SNES::config
.random
= false;
568 SNES::config
.expansion_port
= SNES::System::ExpansionPortDevice::None
;
571 if(lmode
== LOAD_STATE_PRESERVE
)
572 newmovie
= movb
.get_movie();
574 newmovie
.load(_movie
.rerecords
, _movie
.projectid
, _movie
.input
);
576 if(will_load_state
) {
577 std::vector
<unsigned char> tmp
;
578 tmp
.resize(_movie
.movie_state
.size());
579 memcpy(&tmp
[0], &_movie
.movie_state
[0], tmp
.size());
580 newmovie
.restore_state(tmp
, true);
584 rrdata::read_base(_movie
.projectid
);
585 rrdata::add_internal();
587 our_rom
->region
= gtype::toromregion(_movie
.gametype
);
590 if(_movie
.is_savestate
&& lmode
!= LOAD_STATE_MOVIE
) {
591 //Load the savestate and movie state.
592 set_dev(false, _movie
.port1
);
593 set_dev(true, _movie
.port2
);
594 load_core_state(_movie
.savestate
);
595 framebuffer
.load(_movie
.screenshot
);
597 load_sram(_movie
.movie_sram
, win
);
598 set_dev(false, _movie
.port1
);
599 set_dev(true, _movie
.port2
);
600 framebuffer
= nosignal_screen
;
602 } catch(std::bad_alloc
& e
) {
604 } catch(std::exception
& e
) {
605 system_corrupt
= true;
609 //Okay, copy the movie data.
611 if(!our_movie
.is_savestate
|| lmode
== LOAD_STATE_MOVIE
) {
612 our_movie
.is_savestate
= false;
613 our_movie
.host_memory
.clear();
615 movb
.get_movie() = newmovie
;
616 //Activate RW mode if needed.
617 if(lmode
== LOAD_STATE_RW
)
618 movb
.get_movie().readonly_mode(false);
619 if(lmode
== LOAD_STATE_DEFAULT
&& !(movb
.get_movie().get_frame_count()))
620 movb
.get_movie().readonly_mode(false);
621 out(win
) << "ROM Type ";
622 switch(our_rom
->rtype
) {
629 case ROMTYPE_BSXSLOTTED
:
630 out(win
) << "BS-X slotted";
632 case ROMTYPE_SUFAMITURBO
:
633 out(win
) << "Sufami Turbo";
636 out(win
) << "Super Game Boy";
639 out(win
) << "Unknown";
642 out(win
) << " region ";
643 switch(our_rom
->region
) {
651 out(win
) << "Unknown";
654 out(win
) << std::endl
;
655 uint64_t mlength
= _movie
.get_movie_length();
658 std::ostringstream x
;
659 if(mlength
> 3600000000000) {
660 x
<< mlength
/ 3600000000000 << ":";
661 mlength
%= 3600000000000;
663 x
<< std::setfill('0') << std::setw(2) << mlength
/ 60000000000 << ":";
664 mlength
%= 60000000000;
665 x
<< std::setfill('0') << std::setw(2) << mlength
/ 1000000000 << ".";
666 mlength
%= 1000000000;
667 x
<< std::setfill('0') << std::setw(3) << mlength
/ 1000000;
668 out(win
) << "Rerecords " << _movie
.rerecords
<< " length " << x
.str() << " ("
669 << _movie
.get_frame_count() << " frames)" << std::endl
;
672 if(_movie
.gamename
!= "")
673 out(win
) << "Game Name: " << _movie
.gamename
<< std::endl
;
674 for(size_t i
= 0; i
< _movie
.authors
.size(); i
++)
675 out(win
) << "Author: " << _movie
.authors
[i
].first
<< "(" << _movie
.authors
[i
].second
<< ")"
680 void do_load_state(const std::string
& filename
, int lmode
)
682 uint64_t origtime
= get_ticks_msec();
683 lua_callback_pre_load(filename
, win
);
684 struct moviefile mfile
;
686 mfile
= moviefile(filename
);
687 } catch(std::bad_alloc
& e
) {
689 } catch(std::exception
& e
) {
690 win
->message("Can't read movie/savestate '" + filename
+ "': " + e
.what());
691 lua_callback_err_load(filename
, win
);
695 do_load_state(mfile
, lmode
);
696 uint64_t took
= get_ticks_msec() - origtime
;
697 out(win
) << "Loaded '" << filename
<< "' in " << took
<< "ms." << std::endl
;
698 lua_callback_post_load(filename
, our_movie
.is_savestate
, win
);
699 } catch(std::bad_alloc
& e
) {
701 } catch(std::exception
& e
) {
702 win
->message("Can't load movie/savestate '" + filename
+ "': " + e
.what());
703 lua_callback_err_load(filename
, win
);
708 //Do pending load (automatically unpauses).
709 void mark_pending_load(const std::string
& filename
, int lmode
)
712 pending_load
= filename
;
713 amode
= ADVANCE_AUTO
;
718 //Mark pending save (movies save immediately).
719 void mark_pending_save(const std::string
& filename
, int smode
)
721 if(smode
== SAVE_MOVIE
) {
722 //Just do this immediately.
723 do_save_movie(filename
);
726 queued_saves
.insert(filename
);
727 win
->message("Pending save on '" + filename
+ "'");
731 std::vector
<char>& get_host_memory()
733 return our_movie
.host_memory
;
738 return movb
.get_movie();
741 void update_movie_state()
743 auto& _status
= win
->get_emustatus();
745 std::ostringstream x
;
746 x
<< movb
.get_movie().get_current_frame() << "(";
747 if(location_special
== SPECIAL_FRAME_START
)
749 else if(location_special
== SPECIAL_SAVEPOINT
)
751 else if(location_special
== SPECIAL_FRAME_VIDEO
)
754 x
<< movb
.get_movie().next_poll_number();
755 x
<< ";" << movb
.get_movie().get_lag_frames() << ")/" << movb
.get_movie().get_frame_count();
756 _status
["Frame"] = x
.str();
759 std::ostringstream x
;
760 if(movb
.get_movie().readonly_mode())
764 if(dump_in_progress())
766 _status
["Flags"] = x
.str();
768 for(auto i
= memory_watches
.begin(); i
!= memory_watches
.end(); i
++) {
770 _status
["M[" + i
->first
+ "]"] = evaluate_watch(i
->second
);
775 if(movb
.get_movie().readonly_mode())
776 c
= movb
.get_movie().get_controls();
778 c
= curcontrols
^ autoheld_controls
;
779 for(unsigned i
= 0; i
< 8; i
++) {
780 unsigned pindex
= lookup_physical_controller(i
);
781 unsigned port
= pindex
>> 2;
782 unsigned dev
= pindex
& 3;
783 auto ctype
= lookup_controller_type(i
);
784 std::ostringstream x
;
787 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_LEFT
) ? "l" : " ");
788 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_RIGHT
) ? "r" : " ");
789 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_UP
) ? "u" : " ");
790 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_DOWN
) ? "d" : " ");
791 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_A
) ? "A" : " ");
792 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_B
) ? "B" : " ");
793 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_X
) ? "X" : " ");
794 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_Y
) ? "Y" : " ");
795 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_L
) ? "L" : " ");
796 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_R
) ? "R" : " ");
797 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_START
) ? "S" : " ");
798 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_SELECT
) ? "s" : " ");
801 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_X
) << " ";
802 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_Y
) << " ";
803 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_LEFT
) ? "L" : " ");
804 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_RIGHT
) ? "R" : " ");
807 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_X
) << " ";
808 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_Y
) << " ";
809 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
) ? "T" : " ");
810 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
) ? "C" : " ");
811 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TURBO
) ? "t" : " ");
812 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
) ? "P" : " ");
815 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_X
) << " ";
816 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_Y
) << " ";
817 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_START
) ? "T" : " ");
818 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_TRIGGER
) ? "S" : " ");
823 char y
[3] = {'P', 0, 0};
825 _status
[std::string(y
)] = x
.str();
830 class my_interface
: public SNES::Interface
832 string
path(SNES::Cartridge::Slot slot
, const string
&hint
)
834 return static_cast<std::string
>(firmwarepath_setting
).c_str();
837 void video_refresh(const uint16_t *data
, bool hires
, bool interlace
, bool overscan
)
839 if(stepping_into_save
)
840 win
->message("Got video refresh in runtosave, expect desyncs!");
841 video_refresh_done
= true;
842 bool region
= (SNES::system
.region() == SNES::System::Region::PAL
);
843 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
844 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
845 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
846 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
847 lcscreen
ls(data
, hires
, interlace
, overscan
, region
);
849 location_special
= SPECIAL_FRAME_VIDEO
;
850 update_movie_state();
851 redraw_framebuffer();
853 struct lua_render_context lrc
;
860 lrc
.width
= framebuffer
.width
;
861 lrc
.height
= framebuffer
.height
;
862 video_fill_shifts(lrc
.rshift
, lrc
.gshift
, lrc
.bshift
);
863 lua_callback_do_video(&lrc
, win
);
864 dump_frame(framebuffer
, &rq
, lrc
.left_gap
, lrc
.right_gap
, lrc
.top_gap
, lrc
.bottom_gap
, region
, win
);
867 void audio_sample(int16_t l_sample
, int16_t r_sample
)
869 uint16_t _l
= l_sample
;
870 uint16_t _r
= r_sample
;
871 win
->play_audio_sample(_l
+ 32768, _r
+ 32768);
872 dump_audio_sample(_l
, _r
, win
);
875 void audio_sample(uint16_t l_sample
, uint16_t r_sample
)
877 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
878 win
->play_audio_sample(l_sample
+ 32768, r_sample
+ 32768);
879 dump_audio_sample(l_sample
, r_sample
, win
);
882 int16_t input_poll(bool port
, SNES::Input::Device device
, unsigned index
, unsigned id
)
885 x
= movb
.input_poll(port
, index
, id
);
886 //if(id == SNES_DEVICE_ID_JOYPAD_START)
887 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
892 class mycommandhandler
: public aliasexpand_commandhandler
895 void docommand2(std::string
& cmd
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
897 if(is_cmd_prefix(cmd
, "quit-emulator")) {
898 tokensplitter
t(cmd
);
899 std::string dummy
= t
;
900 std::string tail
= t
.tail();
901 if(tail
== "/y" || win
->modal_message("Really quit?", true)) {
902 amode
= ADVANCE_QUIT
;
906 } else if(is_cmd_prefix(cmd
, "pause-emulator")) {
907 if(amode
!= ADVANCE_AUTO
) {
908 amode
= ADVANCE_AUTO
;
911 win
->message("Unpaused");
914 cancel_advance
= false;
915 amode
= ADVANCE_PAUSE
;
916 win
->message("Paused");
918 } else if(is_cmd_prefix(cmd
, "+advance-frame")) {
919 amode
= ADVANCE_FRAME
;
920 cancel_advance
= false;
921 advanced_once
= false;
924 } else if(is_cmd_prefix(cmd
, "-advance-frame")) {
925 cancel_advance
= true;
928 } else if(is_cmd_prefix(cmd
, "+advance-poll")) {
929 amode
= ADVANCE_SUBFRAME
;
930 cancel_advance
= false;
931 advanced_once
= false;
934 } else if(is_cmd_prefix(cmd
, "-advance-poll")) {
935 cancel_advance
= true;
938 } else if(is_cmd_prefix(cmd
, "advance-skiplag")) {
939 amode
= ADVANCE_SKIPLAG
;
942 } else if(is_cmd_prefix(cmd
, "reset")) {
943 pending_reset_cycles
= 0;
944 } else if(is_cmd_prefix(cmd
, "load-state")) {
945 tokensplitter
t(cmd
);
946 std::string dummy
= t
;
947 mark_pending_load(t
.tail(), LOAD_STATE_RW
);
948 } else if(is_cmd_prefix(cmd
, "load-readonly")) {
949 tokensplitter
t(cmd
);
950 std::string dummy
= t
;
951 mark_pending_load(t
.tail(), LOAD_STATE_RO
);
952 } else if(is_cmd_prefix(cmd
, "load-preserve")) {
953 tokensplitter
t(cmd
);
954 std::string dummy
= t
;
955 mark_pending_load(t
.tail(), LOAD_STATE_PRESERVE
);
956 } else if(is_cmd_prefix(cmd
, "load-movie")) {
957 tokensplitter
t(cmd
);
958 std::string dummy
= t
;
959 mark_pending_load(t
.tail(), LOAD_STATE_MOVIE
);
960 } else if(is_cmd_prefix(cmd
, "save-state")) {
961 tokensplitter
t(cmd
);
962 std::string dummy
= t
;
963 mark_pending_save(t
.tail(), SAVE_STATE
);
964 } else if(is_cmd_prefix(cmd
, "save-movie")) {
965 tokensplitter
t(cmd
);
966 std::string dummy
= t
;
967 mark_pending_save(t
.tail(), SAVE_MOVIE
);
968 } else if(is_cmd_prefix(cmd
, "set-rwmode")) {
969 movb
.get_movie().readonly_mode(false);
970 lua_callback_do_readwrite(win
);
971 update_movie_state();
972 win
->notify_screen_update();
973 } else if(is_cmd_prefix(cmd
, "set-gamename")) {
974 tokensplitter
t(cmd
);
975 std::string dummy
= t
;
976 our_movie
.gamename
= t
.tail();
977 out(win
) << "Game name changed to '" << our_movie
.gamename
<< "'" << std::endl
;
978 } else if(is_cmd_prefix(cmd
, "get-gamename")) {
979 out(win
) << "Game name is '" << our_movie
.gamename
<< "'" << std::endl
;
980 } else if(is_cmd_prefix(cmd
, "print-authors")) {
982 for(auto i
= our_movie
.authors
.begin(); i
!= our_movie
.authors
.end(); i
++) {
983 out(win
) << (idx
++) << ": " << i
->first
<< "|" << i
->second
<< std::endl
;
985 out(win
) << "End of authors list" << std::endl
;
986 } else if(is_cmd_prefix(cmd
, "repaint")) {
987 redraw_framebuffer();
988 } else if(is_cmd_prefix(cmd
, "add-author")) {
989 tokensplitter
t(cmd
);
990 std::string dummy
= t
;
991 fieldsplitter
f(t
.tail());
992 std::string full
= f
;
993 std::string nick
= f
;
994 if(full
== "" && nick
== "") {
995 out(win
) << "syntax: add-author <author>" << std::endl
;
998 our_movie
.authors
.push_back(std::make_pair(full
, nick
));
999 out(win
) << (our_movie
.authors
.size() - 1) << ": " << full
<< "|" << nick
<< std::endl
;
1000 } else if(is_cmd_prefix(cmd
, "remove-author")) {
1001 tokensplitter
t(cmd
);
1002 std::string dummy
= t
;
1005 index
= parse_value
<uint64_t>(t
.tail());
1006 } catch(std::exception
& e
) {
1007 out(win
) << "syntax: remove-author <authornum>" << std::endl
;
1010 if(index
>= our_movie
.authors
.size()) {
1011 out(win
) << "No such author" << std::endl
;
1014 our_movie
.authors
.erase(our_movie
.authors
.begin() + index
);
1015 } else if(is_cmd_prefix(cmd
, "edit-author")) {
1016 tokensplitter
t(cmd
);
1017 std::string dummy
= t
;
1020 index
= parse_value
<uint64_t>(t
);
1021 } catch(std::exception
& e
) {
1022 out(win
) << "syntax: edit-author <authornum> <author>" << std::endl
;
1025 if(index
>= our_movie
.authors
.size()) {
1026 out(win
) << "No such author" << std::endl
;
1029 fieldsplitter
f(t
.tail());
1030 std::string full
= f
;
1031 std::string nick
= f
;
1032 if(full
== "" && nick
== "") {
1033 out(win
) << "syntax: edit-author <authornum> <author>" << std::endl
;
1036 our_movie
.authors
[index
] = std::make_pair(full
, nick
);;
1037 } else if(is_cmd_prefix(cmd
, "add-watch")) {
1038 tokensplitter
t(cmd
);
1039 std::string dummy
= t
;
1040 std::string name
= t
;
1041 if(name
== "" || t
.tail() == "") {
1042 out(win
) << "syntax: add-watch <name> <expr>" << std::endl
;
1045 std::cerr
<< "Add watch: '" << name
<< "'" << std::endl
;
1046 memory_watches
[name
] = t
.tail();
1047 update_movie_state();
1048 } else if(is_cmd_prefix(cmd
, "remove-watch")) {
1049 tokensplitter
t(cmd
);
1050 std::string dummy
= t
;
1051 std::string name
= t
;
1052 if(name
== "" || t
.tail() != "") {
1053 out(win
) << "syntax: remove-watch <name>" << std::endl
;
1056 std::cerr
<< "Erase watch: '" << name
<< "'" << std::endl
;
1057 memory_watches
.erase(name
);
1058 auto& _status
= win
->get_emustatus();
1059 _status
.erase("M[" + name
+ "]");
1060 update_movie_state();
1061 } else if(is_cmd_prefix(cmd
, "test-1")) {
1062 framebuffer
= nosignal_screen
;
1063 redraw_framebuffer();
1064 } else if(is_cmd_prefix(cmd
, "test-2")) {
1065 framebuffer
= corrupt_screen
;
1066 redraw_framebuffer();
1067 } else if(is_cmd_prefix(cmd
, "test-3")) {
1069 } else if(is_cmd_prefix(cmd
, "take-screenshot")) {
1071 tokensplitter
t(cmd
);
1072 std::string dummy
= t
;
1073 framebuffer
.save_png(t
.tail());
1074 out(win
) << "Saved PNG screenshot" << std::endl
;
1075 } catch(std::bad_alloc
& e
) {
1077 } catch(std::exception
& e
) {
1078 out(win
) << "Can't save PNG: " << e
.what() << std::endl
;
1080 } else if(is_cmd_prefix(cmd
, "mouse_button")) {
1081 tokensplitter
t(cmd
);
1082 std::string dummy
= t
;
1086 int _x
= atoi(x
.c_str());
1087 int _y
= atoi(y
.c_str());
1088 int _b
= atoi(b
.c_str());
1089 if(_b
& ~prev_mouse_mask
& 1)
1090 send_analog_input(_x
, _y
, 0);
1091 if(_b
& ~prev_mouse_mask
& 2)
1092 send_analog_input(_x
, _y
, 1);
1093 if(_b
& ~prev_mouse_mask
& 4)
1094 send_analog_input(_x
, _y
, 2);
1095 prev_mouse_mask
= _b
;
1096 } else if(do_button_action(cmd
)) {
1097 update_movie_state();
1098 win
->notify_screen_update();
1104 win
->message("Unrecognized command: " + cmd
);
1110 //If there is a pending load, perform it.
1113 if(pending_load
!= "") {
1114 do_load_state(pending_load
, loadmode
);
1115 redraw_framebuffer();
1117 pending_reset_cycles
= -1;
1118 amode
= ADVANCE_AUTO
;
1121 if(!system_corrupt
) {
1122 location_special
= SPECIAL_SAVEPOINT
;
1123 update_movie_state();
1124 win
->notify_screen_update();
1132 //If there are pending saves, perform them.
1135 if(!queued_saves
.empty()) {
1136 stepping_into_save
= true;
1137 SNES::system
.runtosave();
1138 stepping_into_save
= false;
1139 for(auto i
= queued_saves
.begin(); i
!= queued_saves
.end(); i
++)
1142 queued_saves
.clear();
1145 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
1146 bool handle_reset(long cycles
)
1149 win
->message("SNES reset");
1150 SNES::system
.reset();
1151 framebuffer
= nosignal_screen
;
1152 lua_callback_do_reset(win
);
1153 redraw_framebuffer();
1154 } else if(cycles
> 0) {
1155 video_refresh_done
= false;
1156 long cycles_executed
= 0;
1157 out(win
) << "Executing delayed reset... This can take some time!" << std::endl
;
1158 while(cycles_executed
< cycles
&& !video_refresh_done
) {
1159 SNES::cpu
.op_step();
1162 if(!video_refresh_done
)
1163 out(win
) << "SNES reset (delayed " << cycles_executed
<< ")" << std::endl
;
1165 out(win
) << "SNES reset (forced at " << cycles_executed
<< ")" << std::endl
;
1166 SNES::system
.reset();
1167 framebuffer
= nosignal_screen
;
1168 lua_callback_do_reset(win
);
1169 redraw_framebuffer();
1170 if(video_refresh_done
) {
1171 to_wait_frame(get_ticks_msec());
1178 bool handle_corrupt()
1182 while(system_corrupt
) {
1183 framebuffer
= corrupt_screen
;
1184 redraw_framebuffer();
1189 if(amode
== ADVANCE_QUIT
)
1195 void print_controller_mappings()
1197 for(unsigned i
= 0; i
< 8; i
++) {
1198 std::string type
= "unknown";
1199 if(lookup_controller_type(i
) == DT_NONE
)
1200 type
= "disconnected";
1201 if(lookup_controller_type(i
) == DT_GAMEPAD
)
1203 if(lookup_controller_type(i
) == DT_MOUSE
)
1205 if(lookup_controller_type(i
) == DT_SUPERSCOPE
)
1206 type
= "superscope";
1207 if(lookup_controller_type(i
) == DT_JUSTIFIER
)
1209 out(win
) << "Physical controller mapping: Logical " << (i
+ 1) << " is physical " <<
1210 lookup_physical_controller(i
) << " (" << type
<< ")" << std::endl
;
1215 void main_loop(window
* _win
, struct loaded_rom
& rom
, struct moviefile
& initial
) throw(std::bad_alloc
,
1218 //Basic initialization.
1222 auto old_inteface
= SNES::system
.interface
;
1223 SNES::system
.interface
= &intrf
;
1224 mycommandhandler handler
;
1225 win
->set_commandhandler(handler
);
1226 status
= &win
->get_emustatus();
1227 fill_special_frames();
1229 //Load our given movie.
1230 bool first_round
= false;
1231 bool just_did_loadstate
= false;
1233 do_load_state(initial
, LOAD_STATE_DEFAULT
);
1234 first_round
= our_movie
.is_savestate
;
1235 just_did_loadstate
= first_round
;
1236 } catch(std::bad_alloc
& e
) {
1238 } catch(std::exception
& e
) {
1239 win
->message(std::string("FATAL: Can't load initial state: ") + e
.what());
1244 lua_set_commandhandler(handler
);
1245 lua_callback_startup(win
);
1247 //print_controller_mappings();
1249 win
->set_main_surface(scr
);
1250 redraw_framebuffer();
1252 amode
= ADVANCE_PAUSE
;
1253 while(amode
!= ADVANCE_QUIT
) {
1254 if(handle_corrupt()) {
1255 first_round
= our_movie
.is_savestate
;
1256 just_did_loadstate
= true;
1259 long resetcycles
= -1;
1260 ack_frame_tick(get_ticks_msec());
1261 if(amode
== ADVANCE_SKIPLAG_PENDING
)
1262 amode
= ADVANCE_SKIPLAG
;
1265 resetcycles
= movb
.new_frame_starting(amode
== ADVANCE_SKIPLAG
);
1266 if(amode
== ADVANCE_QUIT
)
1268 bool delayed_reset
= (resetcycles
> 0);
1269 pending_reset_cycles
= -1;
1270 if(!handle_reset(resetcycles
)) {
1273 if(!delayed_reset
) {
1277 first_round
= our_movie
.is_savestate
;
1278 amode
= ADVANCE_PAUSE
;
1279 just_did_loadstate
= first_round
;
1283 if(just_did_loadstate
) {
1284 if(amode
== ADVANCE_QUIT
)
1286 amode
= ADVANCE_PAUSE
;
1287 redraw_framebuffer();
1291 just_did_loadstate
= false;
1294 if(amode
== ADVANCE_AUTO
)
1295 win
->wait_msec(to_wait_frame(get_ticks_msec()));
1296 first_round
= false;
1299 SNES::system
.interface
= old_inteface
;