// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** machine.cpp Controls execution of the core MAME system. ***************************************************************************/ #include "emu.h" #include "config.h" #include "crsshair.h" #include "debug/debugcpu.h" #include "debug/debugvw.h" #include "debugger.h" #include "dirtc.h" #include "emuopts.h" #include "fileio.h" #include "http.h" #include "image.h" #include "main.h" #include "natkeyboard.h" #include "network.h" #include "render.h" #include "romload.h" #include "tilemap.h" #include "uiinput.h" #include "ui/uimain.h" #include "corestr.h" #include "unzip.h" #include "osdepend.h" #include #include #include #if defined(__EMSCRIPTEN__) #include #endif //************************************************************************** // RUNNING MACHINE //************************************************************************** osd_interface &running_machine::osd() const { return m_manager.osd(); } //------------------------------------------------- // running_machine - constructor //------------------------------------------------- running_machine::running_machine(const machine_config &_config, machine_manager &manager) : m_side_effects_disabled(0) , debug_flags(0) , m_config(_config) , m_system(_config.gamedrv()) , m_manager(manager) , m_current_phase(machine_phase::PREINIT) , m_paused(false) , m_hard_reset_pending(false) , m_exit_pending(false) , m_soft_reset_timer(nullptr) , m_rand_seed(0x9d14abd7) , m_basename(_config.gamedrv().name) , m_sample_rate(_config.options().sample_rate()) , m_saveload_schedule(saveload_schedule::NONE) , m_saveload_schedule_time(attotime::zero) , m_saveload_searchpath(nullptr) , m_save(*this) , m_memory(*this) , m_ioport(*this) , m_parameters(*this) , m_scheduler(*this) { memset(&m_base_time, 0, sizeof(m_base_time)); // set the machine on all devices device_enumerator iter(root_device()); for (device_t &device : iter) device.set_machine(*this); // fetch core options if (options().debug()) debug_flags = (DEBUG_FLAG_ENABLED | DEBUG_FLAG_CALL_HOOK) | (DEBUG_FLAG_OSD_ENABLED); } //------------------------------------------------- // ~running_machine - destructor //------------------------------------------------- running_machine::~running_machine() { } //------------------------------------------------- // describe_context - return a string describing // which device is currently executing and its // PC //------------------------------------------------- std::string running_machine::describe_context() const { device_execute_interface *executing = m_scheduler.currently_executing(); if (executing != nullptr) { cpu_device *cpu = dynamic_cast(&executing->device()); if (cpu != nullptr) { address_space &prg = cpu->space(AS_PROGRAM); return string_format(prg.is_octal() ? "'%s' (%0*o)" : "'%s' (%0*X)", cpu->tag(), prg.logaddrchars(), cpu->pc()); } } if (m_current_phase == machine_phase::RESET) return std::string("(reset phase)"); else return std::string("(no context)"); } //------------------------------------------------- // start - initialize the emulated machine //------------------------------------------------- void running_machine::start() { // initialize basic can't-fail systems here m_configuration = std::make_unique(*this); m_input = std::make_unique(*this); m_output = std::make_unique(*this); m_render = std::make_unique(*this); m_bookkeeping = std::make_unique(*this); // allocate a soft_reset timer m_soft_reset_timer = m_scheduler.timer_alloc(timer_expired_delegate(FUNC(running_machine::soft_reset), this)); // initialize UI input m_ui_input = std::make_unique(*this); // init the OSD layer m_manager.osd().init(*this); // create the video manager and UI manager m_video = std::make_unique(*this); m_ui = manager().create_ui(*this); m_ui->set_startup_text("Initializing...", true); // initialize the base time (needed for doing record/playback) ::time(&m_base_time); // initialize the input system and input ports for the game // this must be done before memory_init in order to allow specifying // callbacks based on input port tags time_t newbase = m_ioport.initialize(); if (newbase != 0) m_base_time = newbase; // initialize natural keyboard support after ports have been initialized m_natkeyboard = std::make_unique(*this); // initialize the streams engine before the sound devices start m_sound = std::make_unique(*this); // resolve objects that can be used by memory maps for (device_t &device : device_enumerator(root_device())) device.resolve_pre_map(); // configure the address spaces, load ROMs (which needs // width/endianess of the spaces), then populate memory (which // needs rom bases), and finally initialize CPUs (which needs // complete address spaces). These operations must proceed in this // order m_rom_load = std::make_unique(*this); m_memory.initialize(); // save the random seed or save states might be broken in drivers that use the rand() method save().save_item(NAME(m_rand_seed)); // initialize image devices m_image = std::make_unique(*this); m_tilemap = std::make_unique(*this); m_crosshair = std::make_unique(*this); m_network = std::make_unique(*this); // initialize the debugger if ((debug_flags & DEBUG_FLAG_ENABLED) != 0) { m_debug_view = std::make_unique(*this); m_debugger = std::make_unique(*this); } manager().create_custom(*this); // resolve objects that are created by memory maps for (device_t &device : device_enumerator(root_device())) device.resolve_post_map(); // register callbacks for the devices, then start them add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&running_machine::reset_all_devices, this)); add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&running_machine::stop_all_devices, this)); save().register_presave(save_prepost_delegate(FUNC(running_machine::presave_all_devices), this)); start_all_devices(); save().register_postload(save_prepost_delegate(FUNC(running_machine::postload_all_devices), this)); // save outputs created before start time output().register_save(); m_render->resolve_tags(); // load cheat files manager().load_cheatfiles(*this); // start recording movie if specified const char *filename = options().mng_write(); if (filename[0] != 0) m_video->begin_recording(filename, movie_recording::format::MNG); filename = options().avi_write(); if (filename[0] != 0 && !m_video->is_recording()) m_video->begin_recording(filename, movie_recording::format::AVI); // if we're coming in with a savegame request, process it now const char *savegame = options().state(); if (savegame[0] != 0) schedule_load(savegame); // if we're in autosave mode, schedule a load else if (options().autosave() && (m_system.flags & MACHINE_SUPPORTS_SAVE) != 0) schedule_load("auto"); manager().update_machine(); } //------------------------------------------------- // run - execute the machine //------------------------------------------------- int running_machine::run(bool quiet) { int error = EMU_ERR_NONE; // use try/catch for deep error recovery try { m_manager.http()->clear(); // move to the init phase m_current_phase = machine_phase::INIT; // if we have a logfile, set up the callback if (options().log() && !quiet) { m_logfile = std::make_unique(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); std::error_condition const filerr = m_logfile->open("error.log"); if (filerr) throw emu_fatalerror("running_machine::run: unable to open error.log file"); using namespace std::placeholders; add_logerror_callback(std::bind(&running_machine::logfile_callback, this, _1)); } if (options().debug() && options().debuglog()) { m_debuglogfile = std::make_unique(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); std::error_condition const filerr = m_debuglogfile->open("debug.log"); if (filerr) throw emu_fatalerror("running_machine::run: unable to open debug.log file"); } // then finish setting up our local machine start(); // load the configuration settings manager().before_load_settings(*this); m_configuration->load_settings(); // disallow save state registrations starting here. // Don't do it earlier, config load can create network // devices with timers. m_save.allow_registration(false); // load the NVRAM nvram_load(); // set the time on RTCs (this may overwrite parts of NVRAM) set_rtc_datetime(system_time(m_base_time)); sound().ui_mute(false); if (!quiet) sound().start_recording(); m_hard_reset_pending = false; // initialize ui lists // display the startup screens manager().ui_initialize(*this); // perform a soft reset -- this takes us to the running phase soft_reset(); // handle initial load if (m_saveload_schedule != saveload_schedule::NONE) handle_saveload(); export_http_api(); #if defined(__EMSCRIPTEN__) // break out to our async javascript loop and halt emscripten_set_running_machine(this); #endif // run the CPUs until a reset or exit while ((!m_hard_reset_pending && !m_exit_pending) || m_saveload_schedule != saveload_schedule::NONE) { auto profile = g_profiler.start(PROFILER_EXTRA); // execute CPUs if not paused if (!m_paused) m_scheduler.timeslice(); // otherwise, just pump video updates through else m_video->frame_update(); // handle save/load if (m_saveload_schedule != saveload_schedule::NONE) handle_saveload(); } m_manager.http()->clear(); // and out via the exit phase m_current_phase = machine_phase::EXIT; // save the NVRAM and configuration sound().ui_mute(true); if (options().nvram_save()) nvram_save(); m_configuration->save_settings(); } catch (emu_fatalerror const &fatal) { osd_printf_error("Fatal error: %s\n", fatal.what()); error = EMU_ERR_FATALERROR; if (fatal.exitcode() != 0) error = fatal.exitcode(); } catch (emu_exception const &) { osd_printf_error("Caught unhandled emulator exception\n"); error = EMU_ERR_FATALERROR; } catch (binding_type_exception const &btex) { osd_printf_error("Error performing a late bind of function expecting type %s to instance of type %s\n", btex.target_type().name(), btex.actual_type().name()); error = EMU_ERR_FATALERROR; } catch (tag_add_exception const &aex) { osd_printf_error("Tag '%s' already exists in tagged map\n", aex.tag()); error = EMU_ERR_FATALERROR; } catch (std::exception const &ex) { osd_printf_error("Caught unhandled %s exception: %s\n", typeid(ex).name(), ex.what()); error = EMU_ERR_FATALERROR; } catch (...) { osd_printf_error("Caught unhandled exception\n"); error = EMU_ERR_FATALERROR; } // make sure our phase is set properly before cleaning up, // in case we got here via exception m_current_phase = machine_phase::EXIT; // call all exit callbacks registered call_notifiers(MACHINE_NOTIFY_EXIT); util::archive_file::cache_clear(); // close the logfile m_logfile.reset(); return error; } //------------------------------------------------- // schedule_exit - schedule a clean exit //------------------------------------------------- void running_machine::schedule_exit() { m_exit_pending = true; // if we're executing, abort out immediately m_scheduler.eat_all_cycles(); // if we're autosaving on exit, schedule a save as well if (options().autosave() && (m_system.flags & MACHINE_SUPPORTS_SAVE) && this->time() > attotime::zero) schedule_save("auto"); } //------------------------------------------------- // schedule_hard_reset - schedule a hard-reset of // the machine //------------------------------------------------- void running_machine::schedule_hard_reset() { m_hard_reset_pending = true; // if we're executing, abort out immediately m_scheduler.eat_all_cycles(); } //------------------------------------------------- // schedule_soft_reset - schedule a soft-reset of // the system //------------------------------------------------- void running_machine::schedule_soft_reset() { m_soft_reset_timer->adjust(attotime::zero); // we can't be paused since the timer needs to fire resume(); // if we're executing, abort out immediately m_scheduler.eat_all_cycles(); } //------------------------------------------------- // get_statename - allow to specify a subfolder of // the state directory for state loading/saving, // very useful for consoles or computers // where you can have separate folders for diff // software //------------------------------------------------- std::string running_machine::get_statename(const char *option) const { std::string statename_str(""); if (option == nullptr || option[0] == 0) statename_str.assign("%g"); else statename_str.assign(option); // strip any extension in the provided statename int index = statename_str.find_last_of('.'); if (index != -1) statename_str = statename_str.substr(0, index); // handle %d in the template (for image devices) std::string statename_dev("%d_"); int pos = statename_str.find(statename_dev); if (pos != -1) { // if more %d are found, revert to default and ignore them all if (statename_str.find(statename_dev, pos + 3) != -1) statename_str.assign("%g"); // else if there is a single %d, try to create the correct snapname else { int name_found = 0; // find length of the device name int end = statename_str.find_first_not_of("abcdefghijklmnopqrstuvwxyz1234567890", pos + 3); if (end == -1) end = statename_str.length(); // copy the device name to an std::string std::string devname_str; devname_str.assign(statename_str.substr(pos + 3, end - pos - 3)); //printf("check template: %s\n", devname_str.c_str()); // verify that there is such a device for this system for (device_image_interface &image : image_interface_enumerator(root_device())) { // get the device name std::string tempdevname(image.brief_instance_name()); //printf("check device: %s\n", tempdevname.c_str()); if (devname_str.compare(tempdevname) == 0) { // verify that such a device has an image mounted if (image.basename_noext() != nullptr) { std::string filename(image.basename_noext()); // setup snapname and remove the %d_ strreplace(statename_str, devname_str, filename); statename_str.erase(pos, 3); //printf("check image: %s\n", filename.c_str()); name_found = 1; } } } // or fallback to default if (name_found == 0) statename_str.assign("%g"); } } // substitute path and gamename up front strreplace(statename_str, "/", PATH_SEPARATOR); strreplace(statename_str, "%g", basename()); return statename_str; } //------------------------------------------------- // compose_saveload_filename - composes a filename // for state loading/saving //------------------------------------------------- std::string running_machine::compose_saveload_filename(std::string &&filename, const char **searchpath) { std::string result; // is this an absolute path? if (osd_is_absolute_path(filename)) { // if so, this is easy if (searchpath != nullptr) *searchpath = nullptr; result = std::move(filename); } else { // this is a relative path; first specify the search path if (searchpath != nullptr) *searchpath = options().state_directory(); // take into account the statename option const char *stateopt = options().state_name(); std::string statename = get_statename(stateopt); result = string_format("%s%s%s.sta", statename, PATH_SEPARATOR, filename); } return result; } //------------------------------------------------- // set_saveload_filename - specifies the filename // for state loading/saving //------------------------------------------------- void running_machine::set_saveload_filename(std::string &&filename) { // compose the save/load filename and persist it m_saveload_pending_file = compose_saveload_filename(std::move(filename), &m_saveload_searchpath); } //------------------------------------------------- // schedule_save - schedule a save to occur as // soon as possible //------------------------------------------------- void running_machine::schedule_save(std::string &&filename) { // specify the filename to save or load set_saveload_filename(std::move(filename)); // note the start time and set a timer for the next timeslice to actually schedule it m_saveload_schedule = saveload_schedule::SAVE; m_saveload_schedule_time = this->time(); // we can't be paused since we need to clear out anonymous timers resume(); } //------------------------------------------------- // immediate_save - save state. //------------------------------------------------- void running_machine::immediate_save(std::string_view filename) { // specify the filename to save or load set_saveload_filename(std::string(filename)); // set up some parameters for handle_saveload() m_saveload_schedule = saveload_schedule::SAVE; m_saveload_schedule_time = this->time(); // jump right into the save, anonymous timers can't hurt us! handle_saveload(); } //------------------------------------------------- // schedule_load - schedule a load to occur as // soon as possible //------------------------------------------------- void running_machine::schedule_load(std::string &&filename) { // specify the filename to save or load set_saveload_filename(std::move(filename)); // note the start time and set a timer for the next timeslice to actually schedule it m_saveload_schedule = saveload_schedule::LOAD; m_saveload_schedule_time = this->time(); // we can't be paused since we need to clear out anonymous timers resume(); } //------------------------------------------------- // immediate_load - load state. //------------------------------------------------- void running_machine::immediate_load(std::string_view filename) { // specify the filename to save or load set_saveload_filename(std::string(filename)); // set up some parameters for handle_saveload() m_saveload_schedule = saveload_schedule::LOAD; m_saveload_schedule_time = this->time(); // jump right into the load, anonymous timers can't hurt us handle_saveload(); } //------------------------------------------------- // rewind_capture - capture and append a new // state to the rewind list //------------------------------------------------- bool running_machine::rewind_capture() { return m_save.rewind()->capture(); } //------------------------------------------------- // rewind_step - a single step back through // rewind states //------------------------------------------------- bool running_machine::rewind_step() { return m_save.rewind()->step(); } //------------------------------------------------- // rewind_invalidate - mark all the future rewind // states as invalid //------------------------------------------------- void running_machine::rewind_invalidate() { m_save.rewind()->invalidate(); } //------------------------------------------------- // pause - pause the system //------------------------------------------------- void running_machine::pause() { // ignore if nothing has changed if (m_paused) return; m_paused = true; // call the callbacks call_notifiers(MACHINE_NOTIFY_PAUSE); } //------------------------------------------------- // resume - resume the system //------------------------------------------------- void running_machine::resume() { // ignore if nothing has changed if (!m_paused) return; m_paused = false; // call the callbacks call_notifiers(MACHINE_NOTIFY_RESUME); } //------------------------------------------------- // toggle_pause - toggles the pause state //------------------------------------------------- void running_machine::toggle_pause() { if (paused()) { rewind_invalidate(); resume(); } else pause(); } //------------------------------------------------- // add_notifier - add a notifier of the // given type //------------------------------------------------- void running_machine::add_notifier(machine_notification event, machine_notify_delegate callback, bool first) { if (m_current_phase != machine_phase::INIT) throw emu_fatalerror("Can only call running_machine::add_notifier at init time!"); if (first) m_notifier_list[event].push_front(std::make_unique(callback)); // exit notifiers are added to the head, and executed in reverse order else if (event == MACHINE_NOTIFY_EXIT) m_notifier_list[event].push_front(std::make_unique(callback)); // all other notifiers are added to the tail, and executed in the order registered else m_notifier_list[event].push_back(std::make_unique(callback)); } //------------------------------------------------- // add_logerror_callback - adds a callback to be // called on logerror() //------------------------------------------------- void running_machine::add_logerror_callback(logerror_callback callback) { if (m_current_phase != machine_phase::INIT) throw emu_fatalerror("Can only call running_machine::add_logerror_callback at init time!"); m_string_buffer.reserve(1024); m_logerror_list.push_back(std::make_unique(callback)); } //------------------------------------------------- // strlog - send an error logging string to the // debugger and any OSD-defined output streams //------------------------------------------------- void running_machine::strlog(const char *str) const { // log to all callbacks for (auto &cb : m_logerror_list) cb->m_func(str); } //------------------------------------------------- // debug_break - breaks into the debugger, if // enabled //------------------------------------------------- void running_machine::debug_break() { if ((debug_flags & DEBUG_FLAG_ENABLED) != 0) debugger().debug_break(); } //------------------------------------------------- // base_datetime - retrieve the time of the host // system; useful for RTC implementations //------------------------------------------------- void running_machine::base_datetime(system_time &systime) { systime.set(m_base_time); } //------------------------------------------------- // current_datetime - retrieve the current time // (offset by the base); useful for RTC // implementations //------------------------------------------------- void running_machine::current_datetime(system_time &systime) { systime.set(m_base_time + this->time().seconds()); } //------------------------------------------------- // set_rtc_datetime - set the current time on // battery-backed RTCs //------------------------------------------------- void running_machine::set_rtc_datetime(const system_time &systime) { for (device_rtc_interface &rtc : rtc_interface_enumerator(root_device())) if (rtc.has_battery()) rtc.set_current_time(systime); } //------------------------------------------------- // rand - standardized random numbers //------------------------------------------------- // TODO: using this function in the core is strongly discouraged (can affect inp playback), // maybe we should consider moving this function to somewhere else instead. u32 running_machine::rand() { m_rand_seed = 1664525 * m_rand_seed + 1013904223; // return rotated by 16 bits; the low bits have a short period // and are frequently used return (m_rand_seed >> 16) | (m_rand_seed << 16); } //------------------------------------------------- // call_notifiers - call notifiers of the given // type //------------------------------------------------- void running_machine::call_notifiers(machine_notification which) { for (auto& cb : m_notifier_list[which]) cb->m_func(); } //------------------------------------------------- // handle_saveload - attempt to perform a save // or load //------------------------------------------------- void running_machine::handle_saveload() { // if no name, bail if (!m_saveload_pending_file.empty()) { const char *const opname = (m_saveload_schedule == saveload_schedule::LOAD) ? "load" : "save"; const char *const preposname = (m_saveload_schedule == saveload_schedule::LOAD) ? "from" : "to"; // if there are anonymous timers, we can't save just yet, and we can't load yet either // because the timers might overwrite data we have loaded if (!m_scheduler.can_save()) { // if more than a second has passed, we're probably screwed if ((this->time() - m_saveload_schedule_time) > attotime::from_seconds(1)) popmessage("Error: Unable to %s state %s %s due to pending anonymous timers. See error.log for details.", opname, preposname, m_saveload_pending_file); else return; // return without cancelling the operation } else { u32 const openflags = (m_saveload_schedule == saveload_schedule::LOAD) ? OPEN_FLAG_READ : (OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); // open the file emu_file file(m_saveload_searchpath ? m_saveload_searchpath : "", openflags); auto const filerr = file.open(m_saveload_pending_file); if (!filerr) { // read/write the save state save_error saverr = (m_saveload_schedule == saveload_schedule::LOAD) ? m_save.read_file(file) : m_save.write_file(file); // handle the result switch (saverr) { case STATERR_INVALID_HEADER: popmessage("Error: Unable to %s state %s %s due to an invalid header. Make sure the save state is correct for this system.", opname, preposname, m_saveload_pending_file); break; case STATERR_READ_ERROR: popmessage("Error: Unable to %s state %s %s due to a read error (file is likely corrupt).", opname, preposname, m_saveload_pending_file); break; case STATERR_WRITE_ERROR: popmessage("Error: Unable to %s state %s %s due to a write error. Verify there is enough disk space.", opname, preposname, m_saveload_pending_file); break; case STATERR_NONE: { const char *const opnamed = (m_saveload_schedule == saveload_schedule::LOAD) ? "Loaded" : "Saved"; if (!(m_system.flags & MACHINE_SUPPORTS_SAVE)) popmessage("%s state %s %s.\nWarning: Save states are not officially supported for this system.", opnamed, preposname, m_saveload_pending_file); else popmessage("%s state %s %s.", opnamed, preposname, m_saveload_pending_file); break; } default: popmessage("Error: Unknown error during %s state %s %s.", opname, preposname, m_saveload_pending_file); break; } // close and perhaps delete the file if (saverr != STATERR_NONE && m_saveload_schedule == saveload_schedule::SAVE) file.remove_on_close(); } else if ((openflags == OPEN_FLAG_READ) && (std::errc::no_such_file_or_directory == filerr)) { // attempt to load a non-existent savestate, report empty slot popmessage("Error: Load state file %s not found.", m_saveload_pending_file); } else { popmessage("Error: Failed to open %s for %s state operation.", m_saveload_pending_file, opname); } } } // unschedule the operation m_saveload_pending_file.clear(); m_saveload_searchpath = nullptr; m_saveload_schedule = saveload_schedule::NONE; } //------------------------------------------------- // soft_reset - actually perform a soft-reset // of the system //------------------------------------------------- void running_machine::soft_reset(s32 param) { logerror("Soft reset\n"); // temporarily in the reset phase m_current_phase = machine_phase::RESET; // call all registered reset callbacks call_notifiers(MACHINE_NOTIFY_RESET); // now we're running m_current_phase = machine_phase::RUNNING; } //------------------------------------------------- // logfile_callback - callback for logging to // logfile //------------------------------------------------- void running_machine::logfile_callback(const char *buffer) { if (m_logfile != nullptr) { m_logfile->puts(buffer); m_logfile->flush(); } } //------------------------------------------------- // steal_debuglogfile - relinquish ownership of // the debug.log file //------------------------------------------------- std::unique_ptr running_machine::steal_debuglogfile() { return std::move(m_debuglogfile); } //------------------------------------------------- // start_all_devices - start any unstarted devices //------------------------------------------------- void running_machine::start_all_devices() { // iterate through the devices int last_failed_starts = -1; do { // iterate over all devices int failed_starts = 0; for (device_t &device : device_enumerator(root_device())) if (!device.started()) { // attempt to start the device, catching any expected exceptions try { // if the device doesn't have a machine yet, set it first if (!device.m_machine) device.set_machine(*this); // now start the device osd_printf_verbose("Starting %s '%s'\n", device.name(), device.tag()); device.start(); } catch (device_missing_dependencies const &) { // handle missing dependencies by moving the device to the end osd_printf_verbose(" (missing dependencies; rescheduling)\n"); failed_starts++; } } // each iteration should reduce the number of failed starts; error if this doesn't happen if (failed_starts == last_failed_starts) throw emu_fatalerror("Circular dependency in device startup!"); last_failed_starts = failed_starts; } while (last_failed_starts); } //------------------------------------------------- // reset_all_devices - reset all devices in the // hierarchy //------------------------------------------------- void running_machine::reset_all_devices() { // reset the root and it will reset children root_device().reset(); } //------------------------------------------------- // stop_all_devices - stop all the devices in the // hierarchy //------------------------------------------------- void running_machine::stop_all_devices() { // first let the debugger save comments if ((debug_flags & DEBUG_FLAG_ENABLED) != 0) debugger().cpu().comment_save(); // iterate over devices and stop them for (device_t &device : device_enumerator(root_device())) device.stop(); } //------------------------------------------------- // presave_all_devices - tell all the devices we // are about to save //------------------------------------------------- void running_machine::presave_all_devices() { for (device_t &device : device_enumerator(root_device())) device.pre_save(); } //------------------------------------------------- // postload_all_devices - tell all the devices we // just completed a load //------------------------------------------------- void running_machine::postload_all_devices() { for (device_t &device : device_enumerator(root_device())) device.post_load(); } /*************************************************************************** NVRAM MANAGEMENT ***************************************************************************/ /*------------------------------------------------- nvram_filename - returns filename of system's NVRAM depending of selected BIOS -------------------------------------------------*/ std::string running_machine::nvram_filename(device_t &device) const { // start with either basename or basename_biosnum std::ostringstream result; result << basename(); if (root_device().system_bios() != 0 && root_device().default_bios() != root_device().system_bios()) util::stream_format(result, "_%d", root_device().system_bios() - 1); // device-based NVRAM gets its own name in a subdirectory if (device.owner() != nullptr) { // add per software nvrams into one folder const char *software = nullptr; for (device_t *dev = &device; dev->owner() != nullptr; dev = dev->owner()) { device_image_interface *intf; if (dev->interface(intf)) { software = intf->basename_noext(); break; } } if (software != nullptr && *software != '\0') result << PATH_SEPARATOR << software; std::string tag(device.tag()); tag.erase(0, 1); strreplacechr(tag,':', '_'); result << PATH_SEPARATOR << tag; } return result.str(); } /*------------------------------------------------- nvram_load - load a system's NVRAM -------------------------------------------------*/ void running_machine::nvram_load() { for (device_nvram_interface &nvram : nvram_interface_enumerator(root_device())) { emu_file file(options().nvram_directory(), OPEN_FLAG_READ); if (nvram.nvram_backup_enabled() && !file.open(nvram_filename(nvram.device()))) { if (!nvram.nvram_load(file)) osd_printf_error("Error reading NVRAM file %s\n", file.filename()); file.close(); } else nvram.nvram_reset(); } } /*------------------------------------------------- nvram_save - save a system's NVRAM -------------------------------------------------*/ void running_machine::nvram_save() { for (device_nvram_interface &nvram : nvram_interface_enumerator(root_device())) { if (nvram.nvram_can_save()) { emu_file file(options().nvram_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); if (!file.open(nvram_filename(nvram.device()))) { bool error = false; if (!nvram.nvram_save(file)) { error = true; osd_printf_error("Error writing NVRAM file %s\n", file.filename()); } // close and perhaps delete the file if (error || file.size() == 0) file.remove_on_close(); file.close(); } } } } //************************************************************************** // OUTPUT //************************************************************************** void running_machine::popup_clear() const { ui().popup_time(0, " "); } void running_machine::popup_message(util::format_argument_pack const &args) const { std::string const temp(string_format(args)); ui().popup_time(temp.length() / 40 + 2, "%s", temp); } //************************************************************************** // CALLBACK ITEMS //************************************************************************** //------------------------------------------------- // notifier_callback_item - constructor //------------------------------------------------- running_machine::notifier_callback_item::notifier_callback_item(machine_notify_delegate func) : m_func(std::move(func)) { } //------------------------------------------------- // logerror_callback_item - constructor //------------------------------------------------- running_machine::logerror_callback_item::logerror_callback_item(logerror_callback func) : m_func(std::move(func)) { } void running_machine::export_http_api() { if (m_manager.http()->is_active()) { m_manager.http()->add_http_handler("/api/machine", [this](http_manager::http_request_ptr request, http_manager::http_response_ptr response) { rapidjson::StringBuffer s; rapidjson::Writer writer(s); writer.StartObject(); writer.Key("name"); writer.String(m_basename.c_str()); writer.Key("devices"); writer.StartArray(); device_enumerator iter(this->root_device()); for (device_t &device : iter) writer.String(device.tag()); writer.EndArray(); writer.EndObject(); response->set_status(200); response->set_content_type("application/json"); response->set_body(s.GetString()); }); } } //************************************************************************** // SYSTEM TIME //************************************************************************** //------------------------------------------------- // system_time - constructor //------------------------------------------------- system_time::system_time() { set(0); } system_time::system_time(time_t t) { set(t); } //------------------------------------------------- // set - fills out a system_time structure //------------------------------------------------- void system_time::set(time_t t) { // FIXME: this crashes if localtime or gmtime returns nullptr time = t; local_time.set(*localtime(&t)); utc_time.set(*gmtime(&t)); } //------------------------------------------------- // get_tm_time - converts a tm struction to a // MAME mame_system_tm structure //------------------------------------------------- void system_time::full_time::set(struct tm &t) { second = t.tm_sec; minute = t.tm_min; hour = t.tm_hour; mday = t.tm_mday; month = t.tm_mon; year = t.tm_year + 1900; weekday = t.tm_wday; day = t.tm_yday; is_dst = t.tm_isdst; } //************************************************************************** // JAVASCRIPT PORT-SPECIFIC //************************************************************************** #if defined(__EMSCRIPTEN__) running_machine * running_machine::emscripten_running_machine; void running_machine::emscripten_main_loop() { running_machine *machine = emscripten_running_machine; auto profile = g_profiler.start(PROFILER_EXTRA); // execute CPUs if not paused if (!machine->m_paused) { device_scheduler * scheduler; scheduler = &(machine->scheduler()); // Emscripten will call this function at 60Hz, so step the simulation // forward for the amount of time that has passed since the last frame const attotime frametime(0,HZ_TO_ATTOSECONDS(60)); const attotime stoptime(scheduler->time() + frametime); while (!machine->m_paused && !machine->scheduled_event_pending() && scheduler->time() < stoptime) { scheduler->timeslice(); // handle save/load if (machine->m_saveload_schedule != saveload_schedule::NONE) { machine->handle_saveload(); break; } } } // otherwise, just pump video updates through else machine->m_video->frame_update(); // cancel the emscripten loop if the system has been told to exit if (machine->exit_pending()) { emscripten_cancel_main_loop(); } } void running_machine::emscripten_set_running_machine(running_machine *machine) { emscripten_running_machine = machine; EM_ASM ( JSMESS.running = true; ); emscripten_set_main_loop(&(emscripten_main_loop), 0, 1); } running_machine * running_machine::emscripten_get_running_machine() { return emscripten_running_machine; } ui_manager * running_machine::emscripten_get_ui() { return &(emscripten_running_machine->ui()); } sound_manager * running_machine::emscripten_get_sound() { return &(emscripten_running_machine->sound()); } void running_machine::emscripten_soft_reset() { emscripten_running_machine->schedule_soft_reset(); } void running_machine::emscripten_hard_reset() { emscripten_running_machine->schedule_hard_reset(); } void running_machine::emscripten_exit() { emscripten_running_machine->schedule_exit(); } void running_machine::emscripten_save(const char *name) { emscripten_running_machine->schedule_save(name); } void running_machine::emscripten_load(const char *name) { emscripten_running_machine->schedule_load(name); } #endif /* defined(__EMSCRIPTEN__) */