lsnes/src/core/mainloop.cpp
2014-02-06 12:39:46 +02:00

1320 lines
46 KiB
C++

#include "lsnes.hpp"
#include "core/command.hpp"
#include "core/controller.hpp"
#include "core/command.hpp"
#include "core/debug.hpp"
#include "core/dispatch.hpp"
#include "core/framebuffer.hpp"
#include "core/framerate.hpp"
#include "core/inthread.hpp"
#include "core/keymapper.hpp"
#include "core/multitrack.hpp"
#include "lua/lua.hpp"
#include "library/string.hpp"
#include "core/mainloop.hpp"
#include "core/movie.hpp"
#include "core/moviedata.hpp"
#include "core/moviefile.hpp"
#include "core/memorymanip.hpp"
#include "core/memorywatch.hpp"
#include "core/project.hpp"
#include "core/rom.hpp"
#include "core/romloader.hpp"
#include "core/settings.hpp"
#include "core/window.hpp"
#include "interface/callbacks.hpp"
#include "interface/romtype.hpp"
#include "library/framebuffer.hpp"
#include "library/zip.hpp"
#include <iomanip>
#include <cassert>
#include <sstream>
#include <iostream>
#include <limits>
#include <set>
#include <sys/time.h>
#define SPECIAL_FRAME_START 0
#define SPECIAL_FRAME_VIDEO 1
#define SPECIAL_SAVEPOINT 2
#define SPECIAL_NONE 3
void update_movie_state();
time_t random_seed_value = 0;
settingvar::variable<settingvar::model_bool<settingvar::yes_no>> jukebox_dflt_binary(lsnes_vset,
"jukebox-default-binary", "Movie‣Saving‣Saveslots binary", true);
settingvar::variable<settingvar::model_bool<settingvar::yes_no>> movie_dflt_binary(lsnes_vset, "movie-default-binary",
"Movie‣Saving‣Movies binary", false);
settingvar::variable<settingvar::model_bool<settingvar::yes_no>> save_dflt_binary(lsnes_vset,
"savestate-default-binary", "Movie‣Saving‣Savestates binary", false);
namespace
{
settingvar::variable<settingvar::model_int<0,999999>> advance_timeout_first(lsnes_vset, "advance-timeout",
"Delays‣First frame advance", 500);
settingvar::variable<settingvar::model_int<0,999999>> advance_timeout_subframe(lsnes_vset,
"advance-subframe-timeout", "Delays‣Subframe advance", 100);
settingvar::variable<settingvar::model_bool<settingvar::yes_no>> pause_on_end(lsnes_vset, "pause-on-end",
"Movie‣Pause on end", false);
settingvar::variable<settingvar::model_int<0,999999999>> jukebox_size(lsnes_vset, "jukebox-size",
"Movie‣Number of save slots", 12);
enum advance_mode
{
ADVANCE_QUIT, //Quit the emulator.
ADVANCE_AUTO, //Normal (possibly slowed down play).
ADVANCE_LOAD, //Loading a state.
ADVANCE_FRAME, //Frame advance.
ADVANCE_SUBFRAME, //Subframe advance.
ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal).
ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame.
ADVANCE_PAUSE, //Unconditional pause.
};
//Our thread.
threadid_class emulation_thread;
//Flags related to repeating advance.
bool advanced_once;
bool cancel_advance;
//Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
enum advance_mode amode;
//Mode and filename of pending load, one of LOAD_* constants.
bool load_paused;
int loadmode;
std::string pending_load;
std::string pending_new_project;
//Queued saves (all savestates).
std::set<std::pair<std::string, int>> queued_saves;
//Save jukebox.
size_t save_jukebox_pointer;
//Special subframe location. One of SPECIAL_* constants.
int location_special;
//Last frame params.
bool last_hires = false;
bool last_interlace = false;
//Unsafe rewind.
bool do_unsafe_rewind = false;
void* unsafe_rewind_obj = NULL;
//Stop at frame.
bool stop_at_frame_active = false;
uint64_t stop_at_frame = 0;
//Macro hold.
bool macro_hold_1;
bool macro_hold_2;
enum advance_mode old_mode;
std::string save_jukebox_name(size_t i)
{
return (stringfmt() << "$SLOT:" << (i + 1)).str();
}
std::map<std::string, std::string> slotinfo_cache;
std::string vector_to_string(const std::vector<char>& x)
{
std::string y(x.begin(), x.end());
while(y.length() > 0 && y[y.length() - 1] < 32)
y = y.substr(0, y.length() - 1);
return y;
}
std::string get_slotinfo(const std::string& _filename)
{
std::string filename = resolve_relative_path(_filename);
if(!slotinfo_cache.count(filename)) {
std::ostringstream out;
try {
moviefile::brief_info info(filename);
if(!movb)
out << "No movie";
else if(movb.get_mfile().projectid == info.projectid)
out << info.rerecords << "R/" << info.current_frame << "F";
else
out << "Wrong movie";
} catch(...) {
out << "Nonexistent";
}
slotinfo_cache[filename] = out.str();
}
return slotinfo_cache[filename];
}
void flush_slotinfo(const std::string& filename)
{
slotinfo_cache.erase(resolve_relative_path(filename));
}
void flush_slotinfo()
{
slotinfo_cache.clear();
}
}
void mainloop_signal_need_rewind(void* ptr)
{
if(ptr) {
old_mode = amode;
amode = ADVANCE_LOAD;
}
do_unsafe_rewind = true;
unsafe_rewind_obj = ptr;
}
controller_frame movie_logic::update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
{
if(lua_requests_subframe_paint)
redraw_framebuffer();
if(subframe) {
if(amode == ADVANCE_SUBFRAME) {
if(!cancel_advance) {
if(!advanced_once)
platform::wait(advance_timeout_first * 1000);
else
platform::wait(advance_timeout_subframe * 1000);
advanced_once = true;
}
if(cancel_advance) {
stop_at_frame_active = false;
amode = ADVANCE_PAUSE;
cancel_advance = false;
}
platform::set_paused(amode == ADVANCE_PAUSE);
} else if(amode == ADVANCE_FRAME) {
;
} else {
if(amode == ADVANCE_SKIPLAG) {
stop_at_frame_active = false;
amode = ADVANCE_PAUSE;
}
platform::set_paused(amode == ADVANCE_PAUSE);
cancel_advance = false;
}
location_special = SPECIAL_NONE;
update_movie_state();
} else {
if(amode == ADVANCE_SKIPLAG_PENDING)
amode = ADVANCE_SKIPLAG;
if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) {
if(!cancel_advance) {
uint64_t wait = 0;
if(!advanced_once)
wait = advance_timeout_first * 1000;
else if(amode == ADVANCE_SUBFRAME)
wait = advance_timeout_subframe * 1000;
else
wait = to_wait_frame(get_utime());
platform::wait(wait);
advanced_once = true;
}
if(cancel_advance) {
stop_at_frame_active = false;
amode = ADVANCE_PAUSE;
cancel_advance = false;
}
platform::set_paused(amode == ADVANCE_PAUSE);
} else if(amode == ADVANCE_AUTO && movb.get_movie().readonly_mode() && pause_on_end &&
!stop_at_frame_active) {
if(movb.get_movie().get_current_frame() == movb.get_movie().get_frame_count()) {
stop_at_frame_active = false;
amode = ADVANCE_PAUSE;
platform::set_paused(true);
}
} else if(amode == ADVANCE_AUTO && stop_at_frame_active) {
if(movb.get_movie().get_current_frame() >= stop_at_frame) {
stop_at_frame_active = false;
amode = ADVANCE_PAUSE;
platform::set_paused(true);
}
} else {
platform::set_paused((amode == ADVANCE_PAUSE));
cancel_advance = false;
}
location_special = SPECIAL_FRAME_START;
update_movie_state();
}
platform::flush_command_queue();
controller_frame tmp = controls.get(movb.get_movie().get_current_frame());
our_rom.rtype->pre_emulate_frame(tmp); //Preset controls, the lua will override if needed.
lua_callback_do_input(tmp, subframe);
multitrack_editor.process_frame(tmp);
controls.commit(tmp);
return tmp;
}
namespace
{
//Do pending load (automatically unpauses).
void mark_pending_load(std::string filename, int lmode)
{
loadmode = lmode;
pending_load = filename;
old_mode = amode;
amode = ADVANCE_LOAD;
platform::cancel_wait();
platform::set_paused(false);
}
void mark_pending_save(std::string filename, int smode, int binary)
{
int tmp = -1;
if(smode == SAVE_MOVIE) {
//Just do this immediately.
do_save_movie(filename, binary);
flush_slotinfo(translate_name_mprefix(filename, tmp, -1));
return;
}
if(location_special == SPECIAL_SAVEPOINT) {
//We can save immediately here.
do_save_state(filename, binary);
flush_slotinfo(translate_name_mprefix(filename, tmp, -1));
return;
}
queued_saves.insert(std::make_pair(filename, binary));
messages << "Pending save on '" << filename << "'" << std::endl;
}
struct jukebox_size_listener : public settingvar::listener
{
jukebox_size_listener() { lsnes_vset.add_listener(*this); }
~jukebox_size_listener() throw() {lsnes_vset.remove_listener(*this); };
void on_setting_change(settingvar::group& grp, const settingvar::base& val)
{
if(val.get_iname() == "jukebox-size") {
if(save_jukebox_pointer >= jukebox_size)
save_jukebox_pointer = 0;
}
update_movie_state();
}
};
}
void update_movie_state()
{
auto p = project_get();
bool readonly = false;
static unsigned last_controllers = 0;
{
uint64_t magic[4];
our_rom.region->fill_framerate_magic(magic);
if(movb)
voice_frame_number(movb.get_movie().get_current_frame(), 1.0 * magic[1] / magic[0]);
else
voice_frame_number(0, 60.0); //Default.
}
auto& _status = platform::get_emustatus();
if(movb && !system_corrupt) {
_status.set("!frame", (stringfmt() << movb.get_movie().get_current_frame()).str());
_status.set("!length", (stringfmt() << movb.get_movie().get_frame_count()).str());
_status.set("!lag", (stringfmt() << movb.get_movie().get_lag_frames()).str());
if(location_special == SPECIAL_FRAME_START)
_status.set("!subframe", "0");
else if(location_special == SPECIAL_SAVEPOINT)
_status.set("!subframe", "S");
else if(location_special == SPECIAL_FRAME_VIDEO)
_status.set("!subframe", "V");
else
_status.set("!subframe", (stringfmt() << movb.get_movie().next_poll_number()).str());
} else {
_status.set("!frame", "N/A");
_status.set("!length", "N/A");
_status.set("!lag", "N/A");
_status.set("!subframe", "N/A");
}
_status.set("!dumping", (information_dispatch::get_dumper_count() ? "Y" : ""));
if(movb) {
auto& mo = movb.get_movie();
readonly = mo.readonly_mode();
if(system_corrupt)
_status.set("!mode", "C");
else if(!readonly)
_status.set("!mode", "R");
else if(mo.get_frame_count() >= mo.get_current_frame())
_status.set("!mode", "P");
else
_status.set("!mode", "F");
} else {
_status.erase("!subframe");
}
if(jukebox_size > 0) {
int tmp = -1;
std::string sfilen = translate_name_mprefix(save_jukebox_name(save_jukebox_pointer), tmp, -1);
_status.set("!saveslot", (stringfmt() << (save_jukebox_pointer + 1)).str());
_status.set("!saveslotinfo", get_slotinfo(sfilen));
} else {
_status.erase("!saveslot");
_status.erase("!saveslotinfo");
}
if(p) {
_status.set("!branch", p->get_branch_string());
} else {
_status.erase("!branch");
}
{
std::string cur_branch = movb ? movb.get_mfile().current_branch() : "";
if(cur_branch != "")
_status.set("!mbranch", cur_branch);
else
_status.erase("!mbranch");
}
_status.set("!speed", (stringfmt() << (unsigned)(100 * get_realized_multiplier() + 0.5)).str());
if(movb && !system_corrupt) {
time_t timevalue = static_cast<time_t>(movb.get_mfile().rtc_second);
struct tm* time_decompose = gmtime(&timevalue);
char datebuffer[512];
strftime(datebuffer, 511, "%Y%m%d(%a)T%H%M%S", time_decompose);
_status.set("RTC", datebuffer);
} else {
_status.erase("RTC");
}
auto mset = controls.active_macro_set();
bool mfirst = true;
std::ostringstream mss;
for(auto i: mset) {
if(!mfirst) mss << ",";
mss << i;
mfirst = false;
}
_status.set("!macros", mss.str());
controller_frame c;
if(!multitrack_editor.any_records())
c = movb.get_movie().get_controls();
else
c = controls.get_committed();
for(unsigned i = 0;; i++) {
auto pindex = controls.lcid_to_pcid(i);
if(pindex.first < 0 || !controls.is_present(pindex.first, pindex.second)) {
for(unsigned j = i; j < last_controllers; j++)
_status.erase((stringfmt() << "P" << (j + 1)).str());
last_controllers = i;
break;
}
char32_t buffer[MAX_DISPLAY_LENGTH];
c.display(pindex.first, pindex.second, buffer);
std::u32string _buffer = buffer;
if(readonly && multitrack_editor.is_enabled()) {
multitrack_edit::state st = multitrack_editor.get(pindex.first, pindex.second);
if(st == multitrack_edit::MT_PRESERVE)
_buffer += U" (keep)";
else if(st == multitrack_edit::MT_OVERWRITE)
_buffer += U" (rewrite)";
else if(st == multitrack_edit::MT_OR)
_buffer += U" (OR)";
else if(st == multitrack_edit::MT_XOR)
_buffer += U" (XOR)";
else
_buffer += U" (\?\?\?)";
}
_status.set((stringfmt() << "P" << (i + 1)).str(), _buffer);
}
notify_status_update();
}
uint64_t audio_irq_time;
uint64_t controller_irq_time;
uint64_t frame_irq_time;
struct lsnes_callbacks : public emucore_callbacks
{
public:
~lsnes_callbacks() throw()
{
}
int16_t get_input(unsigned port, unsigned index, unsigned control)
{
int16_t x;
x = movb.input_poll(port, index, control);
lua_callback_snoop_input(port, index, control, x);
return x;
}
int16_t set_input(unsigned port, unsigned index, unsigned control, int16_t value)
{
if(!movb.get_movie().readonly_mode()) {
controller_frame f = movb.get_movie().get_controls();
f.axis3(port, index, control, value);
movb.get_movie().set_controls(f);
}
return movb.get_movie().next_input(port, index, control);
}
void notify_latch(std::list<std::string>& args)
{
lua_callback_do_latch(args);
}
void timer_tick(uint32_t increment, uint32_t per_second)
{
if(!movb)
return;
auto& m = movb.get_mfile();
m.rtc_subsecond += increment;
while(m.rtc_subsecond >= per_second) {
m.rtc_second++;
m.rtc_subsecond -= per_second;
}
}
std::string get_firmware_path()
{
return lsnes_vset["firmwarepath"].str();
}
std::string get_base_path()
{
return our_rom.msu1_base;
}
time_t get_time()
{
return movb ? movb.get_mfile().rtc_second : 0;
}
time_t get_randomseed()
{
return random_seed_value;
}
void output_frame(framebuffer::raw& screen, uint32_t fps_n, uint32_t fps_d)
{
lua_callback_do_frame_emulated();
location_special = SPECIAL_FRAME_VIDEO;
update_movie_state();
redraw_framebuffer(screen, false, true);
uint32_t g = gcd(fps_n, fps_d);
fps_n /= g;
fps_d /= g;
information_dispatch::do_frame(screen, fps_n, fps_d);
}
void action_state_updated()
{
graphics_driver_action_updated();
}
void memory_read(uint64_t addr, uint64_t value)
{
debug_fire_callback_read(addr, value);
}
void memory_write(uint64_t addr, uint64_t value)
{
debug_fire_callback_write(addr, value);
}
void memory_execute(uint64_t addr, uint64_t proc)
{
debug_fire_callback_exec(addr, proc);
}
void memory_trace(uint64_t proc, const char* str)
{
debug_fire_callback_trace(proc, str);
}
};
namespace
{
command::fnptr<const std::string&> test4(lsnes_cmd, "test4", "test", "test",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
std::list<std::string> _args;
std::string args2 = args;
for(auto& sym : token_iterator_foreach(args, {" ", "\t"}))
_args.push_back(sym);
lua_callback_do_latch(_args);
});
command::fnptr<> count_rerecords(lsnes_cmd, "count-rerecords", "Count rerecords",
"Syntax: count-rerecords\nCounts rerecords.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
std::vector<char> tmp;
uint64_t x = movb.get_rrdata().write(tmp);
messages << x << " rerecord(s)" << std::endl;
});
command::fnptr<const std::string&> quit_emulator(lsnes_cmd, "quit-emulator", "Quit the emulator",
"Syntax: quit-emulator [/y]\nQuits emulator (/y => don't ask for confirmation).\n",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
amode = ADVANCE_QUIT;
platform::set_paused(false);
platform::cancel_wait();
});
command::fnptr<> unpause_emulator(lsnes_cmd, "unpause-emulator", "Unpause the emulator",
"Syntax: unpause-emulator\nUnpauses the emulator.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
amode = ADVANCE_AUTO;
platform::set_paused(false);
platform::cancel_wait();
messages << "Unpaused" << std::endl;
});
command::fnptr<> pause_emulator(lsnes_cmd, "pause-emulator", "(Un)pause the emulator",
"Syntax: pause-emulator\n(Un)pauses the emulator.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(amode != ADVANCE_AUTO) {
amode = ADVANCE_AUTO;
platform::set_paused(false);
platform::cancel_wait();
messages << "Unpaused" << std::endl;
} else {
platform::cancel_wait();
cancel_advance = false;
stop_at_frame_active = false;
amode = ADVANCE_PAUSE;
messages << "Paused" << std::endl;
}
});
command::fnptr<> save_jukebox_prev(lsnes_cmd, "cycle-jukebox-backward", "Cycle save jukebox backwards",
"Syntax: cycle-jukebox-backward\nCycle save jukebox backwards\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
return;
if(save_jukebox_pointer == 0)
save_jukebox_pointer = jukebox_size - 1;
else
save_jukebox_pointer--;
if(save_jukebox_pointer >= jukebox_size)
save_jukebox_pointer = 0;
update_movie_state();
});
command::fnptr<> save_jukebox_next(lsnes_cmd, "cycle-jukebox-forward", "Cycle save jukebox forwards",
"Syntax: cycle-jukebox-forward\nCycle save jukebox forwards\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
return;
if(save_jukebox_pointer >= jukebox_size - 1)
save_jukebox_pointer = 0;
else
save_jukebox_pointer++;
if(save_jukebox_pointer >= jukebox_size)
save_jukebox_pointer = 0;
update_movie_state();
});
command::fnptr<const std::string&> save_jukebox_set(lsnes_cmd, "set-jukebox-slot", "Set jukebox slot",
"Syntax: set-jukebox-slot\nSet jukebox slot\n", [](const std::string& args)
throw(std::bad_alloc, std::runtime_error) {
if(!regex_match("[1-9][0-9]{0,8}", args))
throw std::runtime_error("Bad slot number");
uint32_t slot = parse_value<uint32_t>(args);
if(slot >= jukebox_size)
throw std::runtime_error("Bad slot number");
save_jukebox_pointer = slot - 1;
update_movie_state();
});
command::fnptr<> load_jukebox(lsnes_cmd, "load-jukebox", "Load save from jukebox",
"Syntax: load-jukebox\nLoad save from jukebox\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_load(save_jukebox_name(save_jukebox_pointer), LOAD_STATE_CURRENT);
});
command::fnptr<> load_jukebox_readwrite(lsnes_cmd, "load-jukebox-readwrite", "Load save from jukebox in"
" read-write mode", "Syntax: load-jukebox-readwrite\nLoad save from jukebox in read-write mode\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_load(save_jukebox_name(save_jukebox_pointer), LOAD_STATE_RW);
});
command::fnptr<> load_jukebox_readonly(lsnes_cmd, "load-jukebox-readonly", "Load save from jukebox in "
"read-only mode", "Syntax: load-jukebox-readonly\nLoad save from jukebox in read-only mode\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_load(save_jukebox_name(save_jukebox_pointer), LOAD_STATE_RO);
});
command::fnptr<> load_jukebox_preserve(lsnes_cmd, "load-jukebox-preserve", "Load save from jukebox, "
"preserving input", "Syntax: load-jukebox-preserve\nLoad save from jukebox, preserving input\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_load(save_jukebox_name(save_jukebox_pointer), LOAD_STATE_PRESERVE);
});
command::fnptr<> load_jukebox_movie(lsnes_cmd, "load-jukebox-movie", "Load save from jukebox as movie",
"Syntax: load-jukebox-movie\nLoad save from jukebox as movie\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_load(save_jukebox_name(save_jukebox_pointer), LOAD_STATE_MOVIE);
});
command::fnptr<> save_jukebox_c(lsnes_cmd, "save-jukebox", "Save save to jukebox",
"Syntax: save-jukebox\nSave save to jukebox\n",
[]() throw(std::bad_alloc, std::runtime_error) {
if(jukebox_size == 0)
throw std::runtime_error("No slot selected");
mark_pending_save(save_jukebox_name(save_jukebox_pointer), SAVE_STATE, -1);
});
command::fnptr<> padvance_frame(lsnes_cmd, "+advance-frame", "Advance one frame",
"Syntax: +advance-frame\nAdvances the emulation by one frame.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
amode = ADVANCE_FRAME;
cancel_advance = false;
advanced_once = false;
platform::cancel_wait();
platform::set_paused(false);
});
command::fnptr<> nadvance_frame(lsnes_cmd, "-advance-frame", "Advance one frame",
"No help available\n",
[]() throw(std::bad_alloc, std::runtime_error) {
cancel_advance = true;
platform::cancel_wait();
platform::set_paused(false);
});
command::fnptr<> padvance_poll(lsnes_cmd, "+advance-poll", "Advance one subframe",
"Syntax: +advance-poll\nAdvances the emulation by one subframe.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
amode = ADVANCE_SUBFRAME;
cancel_advance = false;
advanced_once = false;
platform::cancel_wait();
platform::set_paused(false);
});
command::fnptr<> nadvance_poll(lsnes_cmd, "-advance-poll", "Advance one subframe",
"No help available\n",
[]() throw(std::bad_alloc, std::runtime_error) {
cancel_advance = true;
platform::cancel_wait();
platform::set_paused(false);
});
command::fnptr<> advance_skiplag(lsnes_cmd, "advance-skiplag", "Skip to next poll",
"Syntax: advance-skiplag\nAdvances the emulation to the next poll.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
amode = ADVANCE_SKIPLAG_PENDING;
platform::cancel_wait();
platform::set_paused(false);
});
command::fnptr<> reset_c(lsnes_cmd, "reset", "Reset the system",
"Syntax: reset\nReset\nResets the system in beginning of the next frame.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
int sreset_action = our_rom.rtype->reset_action(false);
if(sreset_action < 0) {
platform::error_message("Core does not support resets");
messages << "Emulator core does not support resets" << std::endl;
return;
}
our_rom.rtype->execute_action(sreset_action, std::vector<interface_action_paramval>());
});
command::fnptr<> hreset_c(lsnes_cmd, "reset-hard", "Reset the system",
"Syntax: reset-hard\nReset-hard\nHard resets the system in beginning of the next frame.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
int hreset_action = our_rom.rtype->reset_action(true);
if(hreset_action < 0) {
platform::error_message("Core does not support hard resets");
messages << "Emulator core does not support hard resets" << std::endl;
return;
}
our_rom.rtype->execute_action(hreset_action, std::vector<interface_action_paramval>());
});
command::fnptr<command::arg_filename> load_c(lsnes_cmd, "load", "Load savestate (current mode)",
"Syntax: load <file>\nLoads SNES state from <file> in current mode\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_CURRENT);
});
command::fnptr<command::arg_filename> load_smart_c(lsnes_cmd, "load-smart", "Load savestate (heuristic mode)",
"Syntax: load <file>\nLoads SNES state from <file> in heuristic mode\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_DEFAULT);
});
command::fnptr<command::arg_filename> load_state_c(lsnes_cmd, "load-state", "Load savestate (R/W)",
"Syntax: load-state <file>\nLoads SNES state from <file> in Read/Write mode\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_RW);
});
command::fnptr<command::arg_filename> load_readonly(lsnes_cmd, "load-readonly", "Load savestate (RO)",
"Syntax: load-readonly <file>\nLoads SNES state from <file> in read-only mode\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_RO);
});
command::fnptr<command::arg_filename> load_preserve(lsnes_cmd, "load-preserve", "Load savestate (preserve "
"input)", "Syntax: load-preserve <file>\nLoads SNES state from <file> preserving input\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_PRESERVE);
});
command::fnptr<command::arg_filename> load_movie_c(lsnes_cmd, "load-movie", "Load movie",
"Syntax: load-movie <file>\nLoads SNES movie from <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_MOVIE);
});
command::fnptr<command::arg_filename> load_allbr_c(lsnes_cmd, "load-allbranches", "Load savestate "
"(all branches)", "Syntax: load-allbranches <file>\nLoads SNES state from <file> with all "
"branches\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_load(args, LOAD_STATE_ALLBRANCH);
});
command::fnptr<command::arg_filename> save_state(lsnes_cmd, "save-state", "Save state",
"Syntax: save-state <file>\nSaves SNES state to <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_STATE, -1);
});
command::fnptr<command::arg_filename> save_state2(lsnes_cmd, "save-state-binary", "Save state (binary)",
"Syntax: save-state-binary <file>\nSaves binary state to <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_STATE, 1);
});
command::fnptr<command::arg_filename> save_state3(lsnes_cmd, "save-state-zip", "Save state (zip)",
"Syntax: save-state-zip <file>\nSaves zip state to <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_STATE, 0);
});
command::fnptr<command::arg_filename> save_movie(lsnes_cmd, "save-movie", "Save movie",
"Syntax: save-movie <file>\nSaves SNES movie to <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_MOVIE, -1);
});
command::fnptr<command::arg_filename> save_movie2(lsnes_cmd, "save-movie-binary", "Save movie (binary)",
"Syntax: save-movie-binary <file>\nSaves binary movie to <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_MOVIE, 1);
});
command::fnptr<command::arg_filename> save_movie3(lsnes_cmd, "save-movie-zip", "Save movie (zip)",
"Syntax: save-movie-zip <file>\nSaves zip movie to <file>\n",
[](command::arg_filename args) throw(std::bad_alloc, std::runtime_error) {
mark_pending_save(args, SAVE_MOVIE, 0);
});
command::fnptr<> set_rwmode(lsnes_cmd, "set-rwmode", "Switch to read/write mode",
"Syntax: set-rwmode\nSwitches to read/write mode\n",
[]() throw(std::bad_alloc, std::runtime_error) {
lua_callback_movie_lost("readwrite");
movb.get_movie().readonly_mode(false);
notify_mode_change(false);
lua_callback_do_readwrite();
update_movie_state();
});
command::fnptr<> set_romode(lsnes_cmd, "set-romode", "Switch to read-only mode",
"Syntax: set-romode\nSwitches to read-only mode\n",
[]() throw(std::bad_alloc, std::runtime_error) {
movb.get_movie().readonly_mode(true);
notify_mode_change(true);
update_movie_state();
});
command::fnptr<> toggle_rwmode(lsnes_cmd, "toggle-rwmode", "Toggle read/write mode",
"Syntax: toggle-rwmode\nToggles read/write mode\n",
[]() throw(std::bad_alloc, std::runtime_error) {
bool c = movb.get_movie().readonly_mode();
if(c)
lua_callback_movie_lost("readwrite");
movb.get_movie().readonly_mode(!c);
notify_mode_change(!c);
if(c)
lua_callback_do_readwrite();
update_movie_state();
});
command::fnptr<> repaint(lsnes_cmd, "repaint", "Redraw the screen",
"Syntax: repaint\nRedraws the screen\n",
[]() throw(std::bad_alloc, std::runtime_error) {
redraw_framebuffer();
});
command::fnptr<> tpon(lsnes_cmd, "toggle-pause-on-end", "Toggle pause on end", "Toggle pause on end\n",
[]() throw(std::bad_alloc, std::runtime_error) {
bool tmp = pause_on_end;
pause_on_end.set(!tmp);
messages << "Pause-on-end is now " << (tmp ? "OFF" : "ON") << std::endl;
});
command::fnptr<> spon(lsnes_cmd, "set-pause-on-end", "Set pause on end", "Set pause on end\n",
[]() throw(std::bad_alloc, std::runtime_error) {
pause_on_end.set(true);
messages << "Pause-on-end is now ON" << std::endl;
});
command::fnptr<> cpon(lsnes_cmd, "clear-pause-on-end", "Clear pause on end", "Clear pause on end\n",
[]() throw(std::bad_alloc, std::runtime_error) {
pause_on_end.set(false);
messages << "Pause-on-end is now OFF" << std::endl;
});
command::fnptr<> rewind_movie(lsnes_cmd, "rewind-movie", "Rewind movie to the beginning",
"Syntax: rewind-movie\nRewind movie to the beginning\n",
[]() throw(std::bad_alloc, std::runtime_error) {
mark_pending_load("SOME NONBLANK NAME", LOAD_STATE_BEGINNING);
});
command::fnptr<> cancel_save(lsnes_cmd, "cancel-saves", "Cancel all pending saves", "Syntax: "
"cancel-save\nCancel pending saves\n",
[]() throw(std::bad_alloc, std::runtime_error) {
queued_saves.clear();
messages << "Pending saves canceled." << std::endl;
});
command::fnptr<> flushslots(lsnes_cmd, "flush-slotinfo", "Flush slotinfo cache",
"Flush slotinfo cache\n",
[]() throw(std::bad_alloc, std::runtime_error) {
flush_slotinfo();
});
command::fnptr<> mhold1(lsnes_cmd, "+hold-macro", "Hold macro (hold)",
"Hold macros enable\n", []() throw(std::bad_alloc, std::runtime_error) {
macro_hold_1 = true;
});
command::fnptr<> mhold2(lsnes_cmd, "-hold-macro", "Hold macro (hold)",
"Hold macros disable\n", []() throw(std::bad_alloc, std::runtime_error) {
macro_hold_1 = false;
});
command::fnptr<> mhold3(lsnes_cmd, "hold-macro", "Hold macro (toggle)",
"Hold macros toggle\n", []() throw(std::bad_alloc, std::runtime_error) {
macro_hold_2 = !macro_hold_2;
if(macro_hold_2)
messages << "Macros are held for next frame." << std::endl;
else
messages << "Macros are not held for next frame." << std::endl;
});
keyboard::invbind imhold1(lsnes_mapper, "+hold-macro", "Macro‣Hold all macros");
keyboard::invbind imhold2(lsnes_mapper, "hold-macro", "Macro‣Hold all macros (typed)");
keyboard::invbind ipause_emulator(lsnes_mapper, "pause-emulator", "Speed‣(Un)pause");
keyboard::invbind ijback(lsnes_mapper, "cycle-jukebox-backward", "Slot select‣Cycle backwards");
keyboard::invbind ijforward(lsnes_mapper, "cycle-jukebox-forward", "Slot select‣Cycle forwards");
keyboard::invbind iloadj(lsnes_mapper, "load-jukebox", "Load‣Selected slot");
keyboard::invbind iloadjrw(lsnes_mapper, "load-jukebox-readwrite", "Load‣Selected slot (readwrite mode)");
keyboard::invbind iloadjro(lsnes_mapper, "load-jukebox-readonly", "Load‣Selected slot (readonly mode)");
keyboard::invbind iloadjp(lsnes_mapper, "load-jukebox-preserve", "Load‣Selected slot (preserve input)");
keyboard::invbind iloadjm(lsnes_mapper, "load-jukebox-movie", "Load‣Selected slot (as movie)");
keyboard::invbind isavej(lsnes_mapper, "save-jukebox", "Save‣Selected slot");
keyboard::invbind iadvframe(lsnes_mapper, "+advance-frame", "Speed‣Advance frame");
keyboard::invbind iadvsubframe(lsnes_mapper, "+advance-poll", "Speed‣Advance subframe");
keyboard::invbind iskiplag(lsnes_mapper, "advance-skiplag", "Speed‣Advance poll");
keyboard::invbind ireset(lsnes_mapper, "reset", "System‣Reset");
keyboard::invbind iset_rwmode(lsnes_mapper, "set-rwmode", "Movie‣Switch to read/write");
keyboard::invbind itoggle_romode(lsnes_mapper, "set-romode", "Movie‣Switch to read-only");
keyboard::invbind itoggle_rwmode(lsnes_mapper, "toggle-rwmode", "Movie‣Toggle read-only");
keyboard::invbind irepaint(lsnes_mapper, "repaint", "System‣Repaint screen");
keyboard::invbind itogglepause(lsnes_mapper, "toggle-pause-on-end", "Movie‣Toggle pause-on-end");
keyboard::invbind irewind_movie(lsnes_mapper, "rewind-movie", "Movie‣Rewind movie");
keyboard::invbind icancel_saves(lsnes_mapper, "cancel-saves", "Save‣Cancel pending saves");
keyboard::invbind iload1(lsnes_mapper, "load $SLOT:1", "Load‣Slot 1");
keyboard::invbind iload2(lsnes_mapper, "load $SLOT:2", "Load‣Slot 2");
keyboard::invbind iload3(lsnes_mapper, "load $SLOT:3", "Load‣Slot 3");
keyboard::invbind iload4(lsnes_mapper, "load $SLOT:4", "Load‣Slot 4");
keyboard::invbind iload5(lsnes_mapper, "load $SLOT:5", "Load‣Slot 5");
keyboard::invbind iload6(lsnes_mapper, "load $SLOT:6", "Load‣Slot 6");
keyboard::invbind iload7(lsnes_mapper, "load $SLOT:7", "Load‣Slot 7");
keyboard::invbind iload8(lsnes_mapper, "load $SLOT:8", "Load‣Slot 8");
keyboard::invbind iload9(lsnes_mapper, "load $SLOT:9", "Load‣Slot 9");
keyboard::invbind iload10(lsnes_mapper, "load $SLOT:10", "Load‣Slot 10");
keyboard::invbind iload11(lsnes_mapper, "load $SLOT:11", "Load‣Slot 11");
keyboard::invbind iload12(lsnes_mapper, "load $SLOT:12", "Load‣Slot 12");
keyboard::invbind iload13(lsnes_mapper, "load $SLOT:13", "Load‣Slot 13");
keyboard::invbind iload14(lsnes_mapper, "load $SLOT:14", "Load‣Slot 14");
keyboard::invbind iload15(lsnes_mapper, "load $SLOT:15", "Load‣Slot 15");
keyboard::invbind iload16(lsnes_mapper, "load $SLOT:16", "Load‣Slot 16");
keyboard::invbind iload17(lsnes_mapper, "load $SLOT:17", "Load‣Slot 17");
keyboard::invbind iload18(lsnes_mapper, "load $SLOT:18", "Load‣Slot 18");
keyboard::invbind iload19(lsnes_mapper, "load $SLOT:19", "Load‣Slot 19");
keyboard::invbind iload20(lsnes_mapper, "load $SLOT:20", "Load‣Slot 20");
keyboard::invbind iload21(lsnes_mapper, "load $SLOT:21", "Load‣Slot 21");
keyboard::invbind iload22(lsnes_mapper, "load $SLOT:22", "Load‣Slot 22");
keyboard::invbind iload23(lsnes_mapper, "load $SLOT:23", "Load‣Slot 23");
keyboard::invbind iload24(lsnes_mapper, "load $SLOT:24", "Load‣Slot 24");
keyboard::invbind iload25(lsnes_mapper, "load $SLOT:25", "Load‣Slot 25");
keyboard::invbind iload26(lsnes_mapper, "load $SLOT:26", "Load‣Slot 26");
keyboard::invbind iload27(lsnes_mapper, "load $SLOT:27", "Load‣Slot 27");
keyboard::invbind iload28(lsnes_mapper, "load $SLOT:28", "Load‣Slot 28");
keyboard::invbind iload29(lsnes_mapper, "load $SLOT:29", "Load‣Slot 29");
keyboard::invbind iload30(lsnes_mapper, "load $SLOT:30", "Load‣Slot 30");
keyboard::invbind iload31(lsnes_mapper, "load $SLOT:31", "Load‣Slot 31");
keyboard::invbind iload32(lsnes_mapper, "load $SLOT:32", "Load‣Slot 32");
keyboard::invbind isave1(lsnes_mapper, "save-state $SLOT:1", "Save‣Slot 1");
keyboard::invbind isave2(lsnes_mapper, "save-state $SLOT:2", "Save‣Slot 2");
keyboard::invbind isave3(lsnes_mapper, "save-state $SLOT:3", "Save‣Slot 3");
keyboard::invbind isave4(lsnes_mapper, "save-state $SLOT:4", "Save‣Slot 4");
keyboard::invbind isave5(lsnes_mapper, "save-state $SLOT:5", "Save‣Slot 5");
keyboard::invbind isave6(lsnes_mapper, "save-state $SLOT:6", "Save‣Slot 6");
keyboard::invbind isave7(lsnes_mapper, "save-state $SLOT:7", "Save‣Slot 7");
keyboard::invbind isave8(lsnes_mapper, "save-state $SLOT:8", "Save‣Slot 8");
keyboard::invbind isave9(lsnes_mapper, "save-state $SLOT:9", "Save‣Slot 9");
keyboard::invbind isave10(lsnes_mapper, "save-state $SLOT:10", "Save‣Slot 10");
keyboard::invbind isave11(lsnes_mapper, "save-state $SLOT:11", "Save‣Slot 11");
keyboard::invbind isave12(lsnes_mapper, "save-state $SLOT:12", "Save‣Slot 12");
keyboard::invbind isave13(lsnes_mapper, "save-state $SLOT:13", "Save‣Slot 13");
keyboard::invbind isave14(lsnes_mapper, "save-state $SLOT:14", "Save‣Slot 14");
keyboard::invbind isave15(lsnes_mapper, "save-state $SLOT:15", "Save‣Slot 15");
keyboard::invbind isave16(lsnes_mapper, "save-state $SLOT:16", "Save‣Slot 16");
keyboard::invbind isave17(lsnes_mapper, "save-state $SLOT:17", "Save‣Slot 17");
keyboard::invbind isave18(lsnes_mapper, "save-state $SLOT:18", "Save‣Slot 18");
keyboard::invbind isave19(lsnes_mapper, "save-state $SLOT:19", "Save‣Slot 19");
keyboard::invbind isave20(lsnes_mapper, "save-state $SLOT:20", "Save‣Slot 20");
keyboard::invbind isave21(lsnes_mapper, "save-state $SLOT:21", "Save‣Slot 21");
keyboard::invbind isave22(lsnes_mapper, "save-state $SLOT:22", "Save‣Slot 22");
keyboard::invbind isave23(lsnes_mapper, "save-state $SLOT:23", "Save‣Slot 23");
keyboard::invbind isave24(lsnes_mapper, "save-state $SLOT:24", "Save‣Slot 24");
keyboard::invbind isave25(lsnes_mapper, "save-state $SLOT:25", "Save‣Slot 25");
keyboard::invbind isave26(lsnes_mapper, "save-state $SLOT:26", "Save‣Slot 26");
keyboard::invbind isave27(lsnes_mapper, "save-state $SLOT:27", "Save‣Slot 27");
keyboard::invbind isave28(lsnes_mapper, "save-state $SLOT:28", "Save‣Slot 28");
keyboard::invbind isave29(lsnes_mapper, "save-state $SLOT:29", "Save‣Slot 29");
keyboard::invbind isave30(lsnes_mapper, "save-state $SLOT:30", "Save‣Slot 30");
keyboard::invbind isave31(lsnes_mapper, "save-state $SLOT:31", "Save‣Slot 31");
keyboard::invbind isave32(lsnes_mapper, "save-state $SLOT:32", "Save‣Slot 32");
bool on_quit_prompt = false;
class mywindowcallbacks : public information_dispatch
{
public:
mywindowcallbacks() : information_dispatch("mainloop-window-callbacks")
{
closenotify.set(notify_close, [this]() {
if(on_quit_prompt) {
amode = ADVANCE_QUIT;
platform::set_paused(false);
platform::cancel_wait();
return;
}
on_quit_prompt = true;
try {
amode = ADVANCE_QUIT;
platform::set_paused(false);
platform::cancel_wait();
} catch(...) {
}
on_quit_prompt = false;
});
}
~mywindowcallbacks() throw() {}
void on_new_dumper(const std::string& n)
{
update_movie_state();
}
void on_destroy_dumper(const std::string& n)
{
update_movie_state();
}
private:
struct dispatch::target<> closenotify;
} mywcb;
//If there is a pending load, perform it. Return 1 on successful load, 0 if nothing to load, -1 on load
//failing.
int handle_load()
{
std::string old_project = movb ? movb.get_mfile().projectid : "";
jumpback:
if(do_unsafe_rewind && unsafe_rewind_obj) {
if(!movb)
return 0;
uint64_t t = get_utime();
std::vector<char> s;
lua_callback_do_unsafe_rewind(s, 0, 0, movb.get_movie(), unsafe_rewind_obj);
notify_mode_change(false);
do_unsafe_rewind = false;
movb.get_mfile().is_savestate = true;
location_special = SPECIAL_SAVEPOINT;
update_movie_state();
messages << "Rewind done in " << (get_utime() - t) << " usec." << std::endl;
return 1;
}
if(pending_new_project != "") {
std::string id = pending_new_project;
pending_new_project = "";
project_info* old = project_get();
if(old && old->id == id)
goto nothing_to_do;
try {
auto& p = project_load(id);
project_set(&p);
if(project_get() != old)
delete old;
flush_slotinfo(); //Wrong movie may be stale.
return 1;
} catch(std::exception& e) {
platform::error_message(std::string("Can't switch projects: ") + e.what());
messages << "Can't switch projects: " << e.what() << std::endl;
goto nothing_to_do;
}
nothing_to_do:
amode = old_mode;
platform::set_paused(amode == ADVANCE_PAUSE);
platform::flush_command_queue();
if(amode == ADVANCE_LOAD)
goto jumpback;
return 0;
}
if(pending_load != "") {
bool system_was_corrupt = system_corrupt;
system_corrupt = false;
try {
if(loadmode != LOAD_STATE_BEGINNING && loadmode != LOAD_STATE_ROMRELOAD &&
!do_load_state(pending_load, loadmode)) {
if(system_was_corrupt)
system_corrupt = system_was_corrupt;
pending_load = "";
return -1;
}
if(loadmode == LOAD_STATE_BEGINNING)
do_load_rewind();
if(loadmode == LOAD_STATE_ROMRELOAD)
do_load_rom();
} catch(std::exception& e) {
if(!system_corrupt && system_was_corrupt)
system_corrupt = true;
platform::error_message(std::string("Load failed: ") + e.what());
messages << "Load failed: " << e.what() << std::endl;
}
pending_load = "";
amode = load_paused ? ADVANCE_PAUSE : ADVANCE_AUTO;
platform::set_paused(load_paused);
load_paused = false;
if(!system_corrupt) {
location_special = SPECIAL_SAVEPOINT;
update_movie_state();
platform::flush_command_queue();
if(amode == ADVANCE_QUIT)
return -1;
if(amode == ADVANCE_LOAD)
goto jumpback;
}
if(old_project != (movb ? movb.get_mfile().projectid : ""))
flush_slotinfo(); //Wrong movie may be stale.
return 1;
}
return 0;
}
//If there are pending saves, perform them.
void handle_saves()
{
if(!movb)
return;
if(!queued_saves.empty() || (do_unsafe_rewind && !unsafe_rewind_obj)) {
our_rom.rtype->runtosave();
for(auto i : queued_saves) {
do_save_state(i.first, i.second);
int tmp = -1;
flush_slotinfo(translate_name_mprefix(i.first, tmp, -1));
}
if(do_unsafe_rewind && !unsafe_rewind_obj) {
uint64_t t = get_utime();
std::vector<char> s = our_rom.save_core_state(true);
uint64_t secs = movb.get_mfile().rtc_second;
uint64_t ssecs = movb.get_mfile().rtc_subsecond;
lua_callback_do_unsafe_rewind(s, secs, ssecs, movb.get_movie(), NULL);
do_unsafe_rewind = false;
messages << "Rewind point set in " << (get_utime() - t) << " usec." << std::endl;
}
}
queued_saves.clear();
}
bool handle_corrupt()
{
if(!system_corrupt)
return false;
while(system_corrupt) {
platform::set_paused(true);
platform::flush_command_queue();
handle_load();
if(amode == ADVANCE_QUIT)
return true;
}
return true;
}
}
void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_to_succeed) throw(std::bad_alloc,
std::runtime_error)
{
platform::system_thread_available(true);
//Basic initialization.
dispatch_set_error_streams(&messages.getstream());
emulation_thread = this_thread_id();
jukebox_size_listener jlistener;
voicethread_task();
init_special_screens();
our_rom = rom;
lsnes_callbacks lsnes_callbacks_obj;
ecore_callbacks = &lsnes_callbacks_obj;
core_core::install_all_handlers();
//Load our given movie.
bool first_round = false;
bool just_did_loadstate = false;
bool used = false;
try {
do_load_state(initial, LOAD_STATE_INITIAL, used);
location_special = SPECIAL_SAVEPOINT;
update_movie_state();
first_round = movb.get_mfile().is_savestate;
just_did_loadstate = first_round;
} catch(std::bad_alloc& e) {
OOM_panic();
} catch(std::exception& e) {
if(!used)
delete &initial;
platform::error_message(std::string("Can't load initial state: ") + e.what());
messages << "ERROR: Can't load initial state: " << e.what() << std::endl;
if(load_has_to_succeed) {
messages << "FATAL: Can't load movie" << std::endl;
throw;
}
system_corrupt = true;
update_movie_state();
redraw_framebuffer(screen_corrupt);
}
lua_callback_startup();
platform::set_paused(initial.start_paused);
amode = initial.start_paused ? ADVANCE_PAUSE : ADVANCE_AUTO;
stop_at_frame_active = false;
uint64_t time_x = get_utime();
while(amode != ADVANCE_QUIT || !queued_saves.empty()) {
if(handle_corrupt()) {
first_round = movb && movb.get_mfile().is_savestate;
just_did_loadstate = first_round;
continue;
}
ack_frame_tick(get_utime());
if(amode == ADVANCE_SKIPLAG_PENDING)
amode = ADVANCE_SKIPLAG;
if(!first_round) {
controls.reset_framehold();
movb.get_movie().get_pollcounters().set_framepflag(false);
movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
movb.get_movie().get_pollcounters().set_framepflag(true);
if(!macro_hold_1 && !macro_hold_2) {
controls.advance_macros();
}
macro_hold_2 = false;
if(amode == ADVANCE_QUIT && queued_saves.empty())
break;
handle_saves();
int r = 0;
if(queued_saves.empty())
r = handle_load();
if(r > 0 || system_corrupt) {
movb.get_movie().get_pollcounters().set_framepflag(movb.get_mfile().is_savestate);
first_round = movb.get_mfile().is_savestate;
if(system_corrupt)
amode = ADVANCE_PAUSE;
else
amode = old_mode;
stop_at_frame_active = false;
just_did_loadstate = first_round;
controls.reset_framehold();
continue;
} else if(r < 0) {
//Not exactly desriable, but this at least won't desync.
stop_at_frame_active = false;
if(amode == ADVANCE_QUIT)
return;
amode = ADVANCE_PAUSE;
}
}
if(just_did_loadstate) {
//If we just loadstated, we are up to date.
if(amode == ADVANCE_QUIT)
break;
platform::set_paused(amode == ADVANCE_PAUSE);
platform::flush_command_queue();
//We already have done the reset this frame if we are going to do one at all.
movb.get_movie().set_controls(movb.update_controls(true));
movb.get_movie().set_all_DRDY();
just_did_loadstate = false;
}
frame_irq_time = get_utime() - time_x;
our_rom.rtype->emulate();
random_mix_timing_entropy();
time_x = get_utime();
if(amode == ADVANCE_AUTO)
platform::wait(to_wait_frame(get_utime()));
first_round = false;
lua_callback_do_frame();
}
information_dispatch::do_dump_end();
core_core::uninstall_all_handlers();
voicethread_kill();
platform::system_thread_available(false);
//Kill some things to avoid crashes.
debug_core_change();
project_set(NULL, true);
lsnes_memorywatch.clear_multi(lsnes_memorywatch.enumerate());
}
void set_stop_at_frame(uint64_t frame)
{
stop_at_frame = frame;
stop_at_frame_active = (frame != 0);
amode = ADVANCE_AUTO;
platform::set_paused(false);
}
void do_flush_slotinfo()
{
flush_slotinfo();
}
void switch_projects(const std::string& newproj)
{
pending_new_project = newproj;
amode = ADVANCE_LOAD;
old_mode = ADVANCE_PAUSE;
platform::cancel_wait();
platform::set_paused(false);
}
void load_new_rom(const romload_request& req)
{
if(_load_new_rom(req)) {
mark_pending_load("SOME NONBLANK NAME", LOAD_STATE_ROMRELOAD);
}
}
void reload_current_rom()
{
if(reload_active_rom()) {
mark_pending_load("SOME NONBLANK NAME", LOAD_STATE_ROMRELOAD);
}
}
void close_rom()
{
if(load_null_rom()) {
load_paused = true;
mark_pending_load("SOME NONBLANK NAME", LOAD_STATE_ROMRELOAD);
}
}