mirror of
https://github.com/holub/mame
synced 2025-10-05 00:38:58 +03:00

Out of whatsnew: there are still a few files being worked on, and the build scripts which are currently set up outside of the MAME source hierarchy. Always open to cleaner ways of doing things.
1268 lines
37 KiB
C
1268 lines
37 KiB
C
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles
|
|
/***************************************************************************
|
|
|
|
machine.c
|
|
|
|
Controls execution of the core MAME system.
|
|
|
|
****************************************************************************
|
|
|
|
Since there has been confusion in the past over the order of
|
|
initialization and other such things, here it is, all spelled out
|
|
as of January, 2008:
|
|
|
|
main()
|
|
- does platform-specific init
|
|
- calls mame_execute() [mame.c]
|
|
|
|
mame_execute() [mame.c]
|
|
- calls mame_validitychecks() [validity.c] to perform validity checks on all compiled drivers
|
|
- begins resource tracking (level 1)
|
|
- calls create_machine [mame.c] to initialize the running_machine structure
|
|
- calls init_machine() [mame.c]
|
|
|
|
init_machine() [mame.c]
|
|
- calls fileio_init() [fileio.c] to initialize file I/O info
|
|
- calls config_init() [config.c] to initialize configuration system
|
|
- calls input_init() [input.c] to initialize the input system
|
|
- calls output_init() [output.c] to initialize the output system
|
|
- calls state_init() [state.c] to initialize save state system
|
|
- calls state_save_allow_registration() [state.c] to allow registrations
|
|
- calls palette_init() [palette.c] to initialize palette system
|
|
- calls render_init() [render.c] to initialize the rendering system
|
|
- calls ui_init() [ui.c] to initialize the user interface
|
|
- calls generic_machine_init() [machine/generic.c] to initialize generic machine structures
|
|
- calls timer_init() [timer.c] to reset the timer system
|
|
- calls osd_init() [osdepend.h] to do platform-specific initialization
|
|
- calls input_port_init() [inptport.c] to set up the input ports
|
|
- calls rom_init() [romload.c] to load the game's ROMs
|
|
- calls memory_init() [memory.c] to process the game's memory maps
|
|
- calls the driver's DRIVER_INIT callback
|
|
- calls device_list_start() [devintrf.c] to start any devices
|
|
- calls video_init() [video.c] to start the video system
|
|
- calls tilemap_init() [tilemap.c] to start the tilemap system
|
|
- calls crosshair_init() [crsshair.c] to configure the crosshairs
|
|
- calls sound_init() [sound.c] to start the audio system
|
|
- calls debugger_init() [debugger.c] to set up the debugger
|
|
- calls the driver's MACHINE_START, SOUND_START, and VIDEO_START callbacks
|
|
- calls cheat_init() [cheat.c] to initialize the cheat system
|
|
- calls image_init() [image.c] to initialize the image system
|
|
|
|
- calls config_load_settings() [config.c] to load the configuration file
|
|
- calls nvram_load [machine/generic.c] to load NVRAM
|
|
- calls ui_display_startup_screens() [ui.c] to display the the startup screens
|
|
- begins resource tracking (level 2)
|
|
- calls soft_reset() [mame.c] to reset all systems
|
|
|
|
-------------------( at this point, we're up and running )----------------------
|
|
|
|
- calls scheduler->timeslice() [schedule.c] over and over until we exit
|
|
- ends resource tracking (level 2), freeing all auto_mallocs and timers
|
|
- calls the nvram_save() [machine/generic.c] to save NVRAM
|
|
- calls config_save_settings() [config.c] to save the game's configuration
|
|
- calls all registered exit routines [mame.c]
|
|
- ends resource tracking (level 1), freeing all auto_mallocs and timers
|
|
|
|
- exits the program
|
|
|
|
***************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "emuopts.h"
|
|
#include "osdepend.h"
|
|
#include "config.h"
|
|
#include "debugger.h"
|
|
#include "render.h"
|
|
#include "cheat.h"
|
|
#include "uimain.h"
|
|
#include "uiinput.h"
|
|
#include "crsshair.h"
|
|
#include "validity.h"
|
|
#include "unzip.h"
|
|
#include "debug/debugcon.h"
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// GLOBAL VARIABLES
|
|
//**************************************************************************
|
|
|
|
// a giant string buffer for temporary strings
|
|
static char giant_string_buffer[65536] = { 0 };
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// JAVASCRIPT PORT-SPECIFIC
|
|
//**************************************************************************
|
|
|
|
#ifdef SDLMAME_EMSCRIPTEN
|
|
#include <emscripten.h>
|
|
|
|
static device_scheduler * scheduler;
|
|
|
|
void js_main_loop() {
|
|
attotime stoptime = scheduler->time() + attotime(0,HZ_TO_ATTOSECONDS(60));
|
|
while (scheduler->time() < stoptime) {
|
|
scheduler->timeslice();
|
|
}
|
|
}
|
|
|
|
void js_set_main_loop(device_scheduler &sched) {
|
|
scheduler = &sched;
|
|
emscripten_set_main_loop(&js_main_loop, 0, 1);
|
|
}
|
|
#endif
|
|
|
|
//**************************************************************************
|
|
// RUNNING MACHINE
|
|
//**************************************************************************
|
|
|
|
//-------------------------------------------------
|
|
// running_machine - constructor
|
|
//-------------------------------------------------
|
|
|
|
running_machine::running_machine(const machine_config &_config, osd_interface &osd, bool exit_to_game_select)
|
|
: firstcpu(NULL),
|
|
primary_screen(NULL),
|
|
palette(NULL),
|
|
pens(NULL),
|
|
colortable(NULL),
|
|
shadow_table(NULL),
|
|
debug_flags(0),
|
|
palette_data(NULL),
|
|
romload_data(NULL),
|
|
ui_input_data(NULL),
|
|
debugcpu_data(NULL),
|
|
generic_machine_data(NULL),
|
|
|
|
m_config(_config),
|
|
m_system(_config.gamedrv()),
|
|
m_osd(osd),
|
|
m_cheat(NULL),
|
|
m_render(NULL),
|
|
m_input(NULL),
|
|
m_sound(NULL),
|
|
m_video(NULL),
|
|
m_tilemap(NULL),
|
|
m_debug_view(NULL),
|
|
m_current_phase(MACHINE_PHASE_PREINIT),
|
|
m_paused(false),
|
|
m_hard_reset_pending(false),
|
|
m_exit_pending(false),
|
|
m_exit_to_game_select(exit_to_game_select),
|
|
m_new_driver_pending(NULL),
|
|
m_soft_reset_timer(NULL),
|
|
m_rand_seed(0x9d14abd7),
|
|
m_ui_active(_config.options().ui_active()),
|
|
m_basename(_config.gamedrv().name),
|
|
m_sample_rate(_config.options().sample_rate()),
|
|
m_logfile(NULL),
|
|
m_saveload_schedule(SLS_NONE),
|
|
m_saveload_schedule_time(attotime::zero),
|
|
m_saveload_searchpath(NULL),
|
|
m_logerror_list(m_respool),
|
|
|
|
m_save(*this),
|
|
m_memory(*this),
|
|
m_ioport(*this),
|
|
m_scheduler(*this),
|
|
m_lua_engine(*this)
|
|
{
|
|
memset(gfx, 0, sizeof(gfx));
|
|
memset(&m_base_time, 0, sizeof(m_base_time));
|
|
|
|
// set the machine on all devices
|
|
device_iterator iter(root_device());
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
device->set_machine(*this);
|
|
|
|
// find devices
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
if (dynamic_cast<cpu_device *>(device) != NULL)
|
|
{
|
|
firstcpu = downcast<cpu_device *>(device);
|
|
break;
|
|
}
|
|
screen_device_iterator screeniter(root_device());
|
|
primary_screen = screeniter.first();
|
|
|
|
// fetch core options
|
|
if (options().debug())
|
|
debug_flags = (DEBUG_FLAG_ENABLED | DEBUG_FLAG_CALL_HOOK) | (options().debug_internal() ? 0 : 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
|
|
//-------------------------------------------------
|
|
|
|
const char *running_machine::describe_context()
|
|
{
|
|
device_execute_interface *executing = m_scheduler.currently_executing();
|
|
if (executing != NULL)
|
|
{
|
|
cpu_device *cpu = dynamic_cast<cpu_device *>(&executing->device());
|
|
if (cpu != NULL)
|
|
m_context.printf("'%s' (%s)", cpu->tag(), core_i64_format(cpu->pc(), cpu->space(AS_PROGRAM).logaddrchars(), cpu->is_octal()));
|
|
}
|
|
else
|
|
m_context.cpy("(no context)");
|
|
|
|
return m_context;
|
|
}
|
|
|
|
TIMER_CALLBACK_MEMBER(running_machine::autoboot_callback)
|
|
{
|
|
if (strlen(options().autoboot_script())!=0) {
|
|
m_lua_engine.execute(options().autoboot_script());
|
|
}
|
|
if (strlen(options().autoboot_command())!=0) {
|
|
astring cmd = astring(options().autoboot_command());
|
|
cmd.replace("'","\\'");
|
|
astring val = astring("emu.keypost('",cmd,"')");
|
|
m_lua_engine.execute_string(val);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// start - initialize the emulated machine
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::start()
|
|
{
|
|
// initialize basic can't-fail systems here
|
|
config_init(*this);
|
|
m_input = auto_alloc(*this, input_manager(*this));
|
|
output_init(*this);
|
|
palette_init(*this);
|
|
m_render = auto_alloc(*this, render_manager(*this));
|
|
generic_machine_init(*this);
|
|
|
|
// allocate a soft_reset timer
|
|
m_soft_reset_timer = m_scheduler.timer_alloc(timer_expired_delegate(FUNC(running_machine::soft_reset), this));
|
|
|
|
// init the osd layer
|
|
m_osd.init(*this);
|
|
|
|
// create the video manager
|
|
m_video = auto_alloc(*this, video_manager(*this));
|
|
ui_init(*this);
|
|
|
|
// 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;
|
|
|
|
// intialize UI input
|
|
ui_input_init(*this);
|
|
|
|
// initialize the streams engine before the sound devices start
|
|
m_sound = auto_alloc(*this, sound_manager(*this));
|
|
|
|
// first load ROMs, then populate memory, and finally initialize CPUs
|
|
// these operations must proceed in this order
|
|
rom_init(*this);
|
|
m_memory.initialize();
|
|
|
|
// initialize the watchdog
|
|
m_watchdog_timer = m_scheduler.timer_alloc(timer_expired_delegate(FUNC(running_machine::watchdog_fired), this));
|
|
if (config().m_watchdog_vblank_count != 0 && primary_screen != NULL)
|
|
primary_screen->register_vblank_callback(vblank_state_delegate(FUNC(running_machine::watchdog_vblank), this));
|
|
save().save_item(NAME(m_watchdog_enabled));
|
|
save().save_item(NAME(m_watchdog_counter));
|
|
|
|
// allocate the gfx elements prior to device initialization
|
|
gfx_init(*this);
|
|
|
|
// initialize image devices
|
|
image_init(*this);
|
|
m_tilemap = auto_alloc(*this, tilemap_manager(*this));
|
|
crosshair_init(*this);
|
|
network_init(*this);
|
|
|
|
// initialize the debugger
|
|
if ((debug_flags & DEBUG_FLAG_ENABLED) != 0)
|
|
debugger_init(*this);
|
|
|
|
// call the game driver's init function
|
|
// this is where decryption is done and memory maps are altered
|
|
// so this location in the init order is important
|
|
ui_set_startup_text(*this, "Initializing...", true);
|
|
|
|
// register callbacks for the devices, then start them
|
|
add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(FUNC(running_machine::reset_all_devices), this));
|
|
add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(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));
|
|
|
|
// 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 & GAME_SUPPORTS_SAVE) != 0)
|
|
schedule_load("auto");
|
|
|
|
// set up the cheat engine
|
|
m_cheat = auto_alloc(*this, cheat_manager(*this));
|
|
|
|
// allocate autoboot timer
|
|
m_autoboot_timer = scheduler().timer_alloc(timer_expired_delegate(FUNC(running_machine::autoboot_callback), this));
|
|
|
|
// initialize lua
|
|
m_lua_engine.initialize();
|
|
|
|
// disallow save state registrations starting here
|
|
m_save.allow_registration(false);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// add_dynamic_device - dynamically add a device
|
|
//-------------------------------------------------
|
|
|
|
device_t &running_machine::add_dynamic_device(device_t &owner, device_type type, const char *tag, UINT32 clock)
|
|
{
|
|
// add the device in a standard manner
|
|
device_t *device = const_cast<machine_config &>(m_config).device_add(&owner, tag, type, clock);
|
|
|
|
// notify this device and all its subdevices that they are now configured
|
|
device_iterator iter(root_device());
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
if (!device->configured())
|
|
device->config_complete();
|
|
return *device;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// run - execute the machine
|
|
//-------------------------------------------------
|
|
|
|
int running_machine::run(bool firstrun)
|
|
{
|
|
int error = MAMERR_NONE;
|
|
|
|
// use try/catch for deep error recovery
|
|
try
|
|
{
|
|
// move to the init phase
|
|
m_current_phase = MACHINE_PHASE_INIT;
|
|
|
|
// if we have a logfile, set up the callback
|
|
if (options().log())
|
|
{
|
|
m_logfile = auto_alloc(*this, emu_file(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS));
|
|
file_error filerr = m_logfile->open("error.log");
|
|
assert_always(filerr == FILERR_NONE, "unable to open log file");
|
|
add_logerror_callback(logfile_callback);
|
|
}
|
|
|
|
// then finish setting up our local machine
|
|
start();
|
|
|
|
// load the configuration settings and NVRAM
|
|
bool settingsloaded = config_load_settings(*this);
|
|
nvram_load(*this);
|
|
sound().ui_mute(false);
|
|
|
|
// initialize ui lists
|
|
ui_initialize(*this);
|
|
|
|
// display the startup screens
|
|
ui_display_startup_screens(*this, firstrun, !settingsloaded);
|
|
|
|
// perform a soft reset -- this takes us to the running phase
|
|
soft_reset();
|
|
|
|
// run the CPUs until a reset or exit
|
|
m_hard_reset_pending = false;
|
|
while ((!m_hard_reset_pending && !m_exit_pending) || m_saveload_schedule != SLS_NONE)
|
|
{
|
|
g_profiler.start(PROFILER_EXTRA);
|
|
|
|
#ifdef SDLMAME_EMSCRIPTEN
|
|
//break out to our async javascript loop and halt
|
|
js_set_main_loop(m_scheduler);
|
|
#endif
|
|
|
|
// 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 != SLS_NONE)
|
|
handle_saveload();
|
|
|
|
g_profiler.stop();
|
|
}
|
|
|
|
// and out via the exit phase
|
|
m_current_phase = MACHINE_PHASE_EXIT;
|
|
|
|
// save the NVRAM and configuration
|
|
sound().ui_mute(true);
|
|
nvram_save(*this);
|
|
config_save_settings(*this);
|
|
}
|
|
catch (emu_fatalerror &fatal)
|
|
{
|
|
mame_printf_error("%s\n", fatal.string());
|
|
error = MAMERR_FATALERROR;
|
|
if (fatal.exitcode() != 0)
|
|
error = fatal.exitcode();
|
|
}
|
|
catch (emu_exception &)
|
|
{
|
|
mame_printf_error("Caught unhandled emulator exception\n");
|
|
error = MAMERR_FATALERROR;
|
|
}
|
|
catch (binding_type_exception &btex)
|
|
{
|
|
mame_printf_error("Error performing a late bind of type %s to %s\n", btex.m_actual_type.name(), btex.m_target_type.name());
|
|
error = MAMERR_FATALERROR;
|
|
}
|
|
catch (std::exception &ex)
|
|
{
|
|
mame_printf_error("Caught unhandled %s exception: %s\n", typeid(ex).name(), ex.what());
|
|
error = MAMERR_FATALERROR;
|
|
}
|
|
catch (...)
|
|
{
|
|
mame_printf_error("Caught unhandled exception\n");
|
|
error = MAMERR_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);
|
|
zip_file_cache_clear();
|
|
|
|
// close the logfile
|
|
auto_free(*this, m_logfile);
|
|
return error;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// schedule_exit - schedule a clean exit
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::schedule_exit()
|
|
{
|
|
// if we are in-game but we started with the select game menu, return to that instead
|
|
if (m_exit_to_game_select && options().system_name()[0] != 0)
|
|
{
|
|
options().set_system_name("");
|
|
ui_menu_force_game_select(*this, &render().ui_container());
|
|
}
|
|
|
|
// otherwise, exit for real
|
|
else
|
|
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 & GAME_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();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// schedule_new_driver - schedule a new game to
|
|
// be loaded
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::schedule_new_driver(const game_driver &driver)
|
|
{
|
|
m_hard_reset_pending = true;
|
|
m_new_driver_pending = &driver;
|
|
|
|
// 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 MESS and consoles or computers
|
|
// where you can have separate folders for diff
|
|
// software
|
|
//-------------------------------------------------
|
|
|
|
astring running_machine::get_statename(const char *option)
|
|
{
|
|
astring statename_str("");
|
|
if (option == NULL || option[0] == 0)
|
|
statename_str.cpy("%g");
|
|
else
|
|
statename_str.cpy(option);
|
|
|
|
// strip any extension in the provided statename
|
|
int index = statename_str.rchr(0, '.');
|
|
if (index != -1)
|
|
statename_str.substr(0, index);
|
|
|
|
// handle %d in the template (for image devices)
|
|
astring statename_dev("%d_");
|
|
int pos = statename_str.find(0, statename_dev);
|
|
|
|
if (pos != -1)
|
|
{
|
|
// if more %d are found, revert to default and ignore them all
|
|
if (statename_str.find(pos + 3, statename_dev) != -1)
|
|
statename_str.cpy("%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 end1 = statename_str.find(pos + 3, "/");
|
|
int end2 = statename_str.find(pos + 3, "%");
|
|
int end = -1;
|
|
|
|
if ((end1 != -1) && (end2 != -1))
|
|
end = MIN(end1, end2);
|
|
else if (end1 != -1)
|
|
end = end1;
|
|
else if (end2 != -1)
|
|
end = end2;
|
|
else
|
|
end = statename_str.len();
|
|
|
|
if (end - pos < 3)
|
|
fatalerror("Something very wrong is going on!!!\n");
|
|
|
|
// copy the device name to an astring
|
|
astring devname_str;
|
|
devname_str.cpysubstr(statename_str, pos + 3, end - pos - 3);
|
|
//printf("check template: %s\n", devname_str.cstr());
|
|
|
|
// verify that there is such a device for this system
|
|
image_interface_iterator iter(root_device());
|
|
for (device_image_interface *image = iter.first(); image != NULL; image = iter.next())
|
|
{
|
|
// get the device name
|
|
astring tempdevname(image->brief_instance_name());
|
|
//printf("check device: %s\n", tempdevname.cstr());
|
|
|
|
if (devname_str.cmp(tempdevname) == 0)
|
|
{
|
|
// verify that such a device has an image mounted
|
|
if (image->basename_noext() != NULL)
|
|
{
|
|
astring filename(image->basename_noext());
|
|
|
|
// setup snapname and remove the %d_
|
|
statename_str.replace(0, devname_str, filename);
|
|
statename_str.del(pos, 3);
|
|
//printf("check image: %s\n", filename.cstr());
|
|
|
|
name_found = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// or fallback to default
|
|
if (name_found == 0)
|
|
statename_str.cpy("%g");
|
|
}
|
|
}
|
|
|
|
// substitute path and gamename up front
|
|
statename_str.replace(0, "/", PATH_SEPARATOR);
|
|
statename_str.replace(0, "%g", basename());
|
|
|
|
return statename_str;
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// set_saveload_filename - specifies the filename
|
|
// for state loading/saving
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::set_saveload_filename(const char *filename)
|
|
{
|
|
// free any existing request and allocate a copy of the requested name
|
|
if (osd_is_absolute_path(filename))
|
|
{
|
|
m_saveload_searchpath = NULL;
|
|
m_saveload_pending_file.cpy(filename);
|
|
}
|
|
else
|
|
{
|
|
m_saveload_searchpath = options().state_directory();
|
|
// take into account the statename option
|
|
const char *stateopt = options().state_name();
|
|
astring statename = get_statename(stateopt);
|
|
m_saveload_pending_file.cpy(statename.cstr()).cat(PATH_SEPARATOR).cat(filename).cat(".sta");
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// schedule_save - schedule a save to occur as
|
|
// soon as possible
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::schedule_save(const char *filename)
|
|
{
|
|
// specify the filename to save or load
|
|
set_saveload_filename(filename);
|
|
|
|
// note the start time and set a timer for the next timeslice to actually schedule it
|
|
m_saveload_schedule = SLS_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(const char *filename)
|
|
{
|
|
// specify the filename to save or load
|
|
set_saveload_filename(filename);
|
|
|
|
// set up some parameters for handle_saveload()
|
|
m_saveload_schedule = SLS_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(const char *filename)
|
|
{
|
|
// specify the filename to save or load
|
|
set_saveload_filename(filename);
|
|
|
|
// note the start time and set a timer for the next timeslice to actually schedule it
|
|
m_saveload_schedule = SLS_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(const char *filename)
|
|
{
|
|
// specify the filename to save or load
|
|
set_saveload_filename(filename);
|
|
|
|
// set up some parameters for handle_saveload()
|
|
m_saveload_schedule = SLS_LOAD;
|
|
m_saveload_schedule_time = this->time();
|
|
|
|
// jump right into the load, anonymous timers can't hurt us
|
|
handle_saveload();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// add_notifier - add a notifier of the
|
|
// given type
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::add_notifier(machine_notification event, machine_notify_delegate callback)
|
|
{
|
|
assert_always(m_current_phase == MACHINE_PHASE_INIT, "Can only call add_notifier at init time!");
|
|
|
|
// exit notifiers are added to the head, and executed in reverse order
|
|
if (event == MACHINE_NOTIFY_EXIT)
|
|
m_notifier_list[event].prepend(*global_alloc(notifier_callback_item(callback)));
|
|
|
|
// all other notifiers are added to the tail, and executed in the order registered
|
|
else
|
|
m_notifier_list[event].append(*global_alloc(notifier_callback_item(callback)));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// add_logerror_callback - adds a callback to be
|
|
// called on logerror()
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::add_logerror_callback(logerror_callback callback)
|
|
{
|
|
assert_always(m_current_phase == MACHINE_PHASE_INIT, "Can only call add_logerror_callback at init time!");
|
|
m_logerror_list.append(*auto_alloc(*this, logerror_callback_item(callback)));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// logerror - printf-style error logging
|
|
//-------------------------------------------------
|
|
|
|
void CLIB_DECL running_machine::logerror(const char *format, ...)
|
|
{
|
|
// process only if there is a target
|
|
if (m_logerror_list.first() != NULL)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
vlogerror(format, arg);
|
|
va_end(arg);
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// vlogerror - vprintf-style error logging
|
|
//-------------------------------------------------
|
|
|
|
void CLIB_DECL running_machine::vlogerror(const char *format, va_list args)
|
|
{
|
|
// process only if there is a target
|
|
if (m_logerror_list.first() != NULL)
|
|
{
|
|
g_profiler.start(PROFILER_LOGERROR);
|
|
|
|
// dump to the buffer
|
|
vsnprintf(giant_string_buffer, ARRAY_LENGTH(giant_string_buffer), format, args);
|
|
|
|
// log to all callbacks
|
|
for (logerror_callback_item *cb = m_logerror_list.first(); cb != NULL; cb = cb->next())
|
|
(*cb->m_func)(*this, giant_string_buffer);
|
|
|
|
g_profiler.stop();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// rand - standardized random numbers
|
|
//-------------------------------------------------
|
|
|
|
UINT32 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 (notifier_callback_item *cb = m_notifier_list[which].first(); cb != NULL; cb = cb->next())
|
|
cb->m_func();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// handle_saveload - attempt to perform a save
|
|
// or load
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::handle_saveload()
|
|
{
|
|
UINT32 openflags = (m_saveload_schedule == SLS_LOAD) ? OPEN_FLAG_READ : (OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
|
|
const char *opnamed = (m_saveload_schedule == SLS_LOAD) ? "loaded" : "saved";
|
|
const char *opname = (m_saveload_schedule == SLS_LOAD) ? "load" : "save";
|
|
file_error filerr = FILERR_NONE;
|
|
|
|
// if no name, bail
|
|
emu_file file(m_saveload_searchpath, openflags);
|
|
if (!m_saveload_pending_file)
|
|
goto cancel;
|
|
|
|
// 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("Unable to %s due to pending anonymous timers. See error.log for details.", opname);
|
|
goto cancel;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// open the file
|
|
filerr = file.open(m_saveload_pending_file);
|
|
if (filerr == FILERR_NONE)
|
|
{
|
|
// read/write the save state
|
|
save_error saverr = (m_saveload_schedule == SLS_LOAD) ? m_save.read_file(file) : m_save.write_file(file);
|
|
|
|
// handle the result
|
|
switch (saverr)
|
|
{
|
|
case STATERR_ILLEGAL_REGISTRATIONS:
|
|
popmessage("Error: Unable to %s state due to illegal registrations. See error.log for details.", opname);
|
|
break;
|
|
|
|
case STATERR_INVALID_HEADER:
|
|
popmessage("Error: Unable to %s state due to an invalid header. Make sure the save state is correct for this game.", opname);
|
|
break;
|
|
|
|
case STATERR_READ_ERROR:
|
|
popmessage("Error: Unable to %s state due to a read error (file is likely corrupt).", opname);
|
|
break;
|
|
|
|
case STATERR_WRITE_ERROR:
|
|
popmessage("Error: Unable to %s state due to a write error. Verify there is enough disk space.", opname);
|
|
break;
|
|
|
|
case STATERR_NONE:
|
|
if (!(m_system.flags & GAME_SUPPORTS_SAVE))
|
|
popmessage("State successfully %s.\nWarning: Save states are not officially supported for this game.", opnamed);
|
|
else
|
|
popmessage("State successfully %s.", opnamed);
|
|
break;
|
|
|
|
default:
|
|
popmessage("Error: Unknown error during state %s.", opnamed);
|
|
break;
|
|
}
|
|
|
|
// close and perhaps delete the file
|
|
if (saverr != STATERR_NONE && m_saveload_schedule == SLS_SAVE)
|
|
file.remove_on_close();
|
|
}
|
|
else
|
|
popmessage("Error: Failed to open file for %s operation.", opname);
|
|
|
|
// unschedule the operation
|
|
cancel:
|
|
m_saveload_pending_file.reset();
|
|
m_saveload_searchpath = NULL;
|
|
m_saveload_schedule = SLS_NONE;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// soft_reset - actually perform a soft-reset
|
|
// of the system
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::soft_reset(void *ptr, INT32 param)
|
|
{
|
|
logerror("Soft reset\n");
|
|
|
|
// temporarily in the reset phase
|
|
m_current_phase = MACHINE_PHASE_RESET;
|
|
|
|
// set up the watchdog timer; only start off enabled if explicitly configured
|
|
m_watchdog_enabled = (config().m_watchdog_vblank_count != 0 || config().m_watchdog_time != attotime::zero);
|
|
watchdog_reset();
|
|
m_watchdog_enabled = true;
|
|
|
|
// call all registered reset callbacks
|
|
call_notifiers(MACHINE_NOTIFY_RESET);
|
|
|
|
// setup autoboot if needed
|
|
m_autoboot_timer->adjust(attotime(options().autoboot_delay(),0),0);
|
|
|
|
// now we're running
|
|
m_current_phase = MACHINE_PHASE_RUNNING;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// watchdog_reset - reset the watchdog timer
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::watchdog_reset()
|
|
{
|
|
// if we're not enabled, skip it
|
|
if (!m_watchdog_enabled)
|
|
m_watchdog_timer->adjust(attotime::never);
|
|
|
|
// VBLANK-based watchdog?
|
|
else if (config().m_watchdog_vblank_count != 0)
|
|
m_watchdog_counter = config().m_watchdog_vblank_count;
|
|
|
|
// timer-based watchdog?
|
|
else if (config().m_watchdog_time != attotime::zero)
|
|
m_watchdog_timer->adjust(config().m_watchdog_time);
|
|
|
|
// default to an obscene amount of time (3 seconds)
|
|
else
|
|
m_watchdog_timer->adjust(attotime::from_seconds(3));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// watchdog_enable - reset the watchdog timer
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::watchdog_enable(bool enable)
|
|
{
|
|
// when re-enabled, we reset our state
|
|
if (m_watchdog_enabled != enable)
|
|
{
|
|
m_watchdog_enabled = enable;
|
|
watchdog_reset();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// watchdog_fired - watchdog timer callback
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::watchdog_fired(void *ptr, INT32 param)
|
|
{
|
|
logerror("Reset caused by the watchdog!!!\n");
|
|
|
|
bool verbose = options().verbose();
|
|
#ifdef MAME_DEBUG
|
|
verbose = true;
|
|
#endif
|
|
if (verbose)
|
|
popmessage("Reset caused by the watchdog!!!\n");
|
|
|
|
schedule_soft_reset();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// watchdog_vblank - VBLANK state callback for
|
|
// watchdog timers
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::watchdog_vblank(screen_device &screen, bool vblank_state)
|
|
{
|
|
// VBLANK starting
|
|
if (vblank_state && m_watchdog_enabled)
|
|
{
|
|
// check the watchdog
|
|
if (config().m_watchdog_vblank_count != 0)
|
|
if (--m_watchdog_counter == 0)
|
|
watchdog_fired();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// logfile_callback - callback for logging to
|
|
// logfile
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::logfile_callback(running_machine &machine, const char *buffer)
|
|
{
|
|
if (machine.m_logfile != NULL)
|
|
machine.m_logfile->puts(buffer);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// start_all_devices - start any unstarted devices
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::start_all_devices()
|
|
{
|
|
// iterate through the devices
|
|
int last_failed_starts = -1;
|
|
while (last_failed_starts != 0)
|
|
{
|
|
// iterate over all devices
|
|
int failed_starts = 0;
|
|
device_iterator iter(root_device());
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
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 == NULL)
|
|
device->set_machine(*this);
|
|
|
|
// now start the device
|
|
mame_printf_verbose("Starting %s '%s'\n", device->name(), device->tag());
|
|
device->start();
|
|
}
|
|
|
|
// handle missing dependencies by moving the device to the end
|
|
catch (device_missing_dependencies &)
|
|
{
|
|
// if we're the end, fail
|
|
mame_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;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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)
|
|
debug_comment_save(*this);
|
|
|
|
// iterate over devices and stop them
|
|
device_iterator iter(root_device());
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
device->stop();
|
|
|
|
// then nuke the device tree
|
|
// global_free(m_root_device);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// presave_all_devices - tell all the devices we
|
|
// are about to save
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::presave_all_devices()
|
|
{
|
|
device_iterator iter(root_device());
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
device->pre_save();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// postload_all_devices - tell all the devices we
|
|
// just completed a load
|
|
//-------------------------------------------------
|
|
|
|
void running_machine::postload_all_devices()
|
|
{
|
|
device_iterator iter(root_device());
|
|
for (device_t *device = iter.first(); device != NULL; device = iter.next())
|
|
device->post_load();
|
|
}
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// CALLBACK ITEMS
|
|
//**************************************************************************
|
|
|
|
//-------------------------------------------------
|
|
// notifier_callback_item - constructor
|
|
//-------------------------------------------------
|
|
|
|
running_machine::notifier_callback_item::notifier_callback_item(machine_notify_delegate func)
|
|
: m_next(NULL),
|
|
m_func(func)
|
|
{
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// logerror_callback_item - constructor
|
|
//-------------------------------------------------
|
|
|
|
running_machine::logerror_callback_item::logerror_callback_item(logerror_callback func)
|
|
: m_next(NULL),
|
|
m_func(func)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// SYSTEM TIME
|
|
//**************************************************************************
|
|
|
|
//-------------------------------------------------
|
|
// system_time - constructor
|
|
//-------------------------------------------------
|
|
|
|
system_time::system_time()
|
|
{
|
|
set(0);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// set - fills out a system_time structure
|
|
//-------------------------------------------------
|
|
|
|
void system_time::set(time_t t)
|
|
{
|
|
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;
|
|
}
|