Saturday, August 17, 2019

How to modify MAME to shutdown if idle too long

So if you read my last post, you know that I am working on a MAME/Daphne cab that my kids may use from time to time.  One problem I noticed is that some games (like Teenage Mutant Ninja Turtles) will sit on the start screen indefinitely if you insert a coin.  I can't trust my kids to shut off the games while I'm at work, so I need a way to protect my CRT from being destroyed.  The solution I came up with was to modify MAME to shutdown after no input has been received for 5 minutes.

I modified MAME v0.211 which as of this writing is almost the latest version (but not quite).  As I am running MAME on a Raspberry Pi, I dug into the SDL code to add this hack.  I tried to find a more universal solution that would also work on Windows but couldn't really figure it out after an hour or so of digging so just gave up.

I ended up using global variables which are normally a big no-no in my book, but since building MAME takes so long on the Pi, I wanted a solution that would avoid modifying any .h files (and thus force me to have to potentially rebuild MAME from scratch depending on which .h files I changed).

Here are the relevant files that I modified and the changes that I made:

src/emu/video.cpp:


// START MPO
extern u32 g_last_time_seconds_when_input_received;
// END MPO
//-------------------------------------------------
//  recompute_speed - recompute the current
//  overall speed; we assume this is called only
//  if we did not skip a frame
//-------------------------------------------------
void video_manager::recompute_speed(const attotime &emutime)
{
...
and a little ways further down

        // if we're past the "time-to-execute" requested, signal an exit
        if (m_seconds_to_run != 0 && emutime.seconds() >= m_seconds_to_run)
        {
                // create a final screenshot
                emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CR$
                osd_file::error filerr = file.open(machine().basename(), PATH_SEPARATOR "final.png");
                if (filerr == osd_file::error::NONE)
                        save_snapshot(nullptr, file);
                //printf("Scheduled exit at %f\n", emutime.as_double());
                // schedule our demise
                machine().schedule_exit();
        }
        // START MPO
        u32 uTimeSeconds = machine().time().seconds();
        // if we've just barely started up, then we need to seed the value
        if (g_last_time_seconds_when_input_received == 0)
        {
                g_last_time_seconds_when_input_received = uTimeSeconds;
        }
        else
        {
                u32 diff = uTimeSeconds - g_last_time_seconds_when_input_received;
                // If the system hasn't received input lately, then we need to shut down
                // hard-coded since I am just doing a one-off feature for my own use
                if (diff > 300)
                { 
                        machine().schedule_exit();
                }
        }
        // END MPO

src/frontend/mame/ui/ui.cpp:

void mame_ui_manager::display_startup_screens(bool first_time)
{
        const int maxstate = 3;
//      int str = machine().options().seconds_to_run(); // MPO        bool show_gameinfo = !machine().options().skip_gameinfo();
        bool show_warnings = true, show_mandatory_fileman = true;
        // disable everything if we are using -str for 300 or fewer seconds, or if we're the empty driver,
        // or if we are debugging
// START MPO
//      if (!first_time || (str > 0 && str < 60*5) || &machine().system() == &GAME_NAME(___empty) || (machine().debug_fla$
        // suppress start-up dialogs
// END MPO
                show_gameinfo = show_warnings = show_mandatory_fileman = false;


src/osd/modules/input/input_sdl.cpp:

// START MPO, ugly global hack to avoid having to rebuild the entire code base (keeping changes inside of .cpp files only)
u32 g_last_time_seconds_when_input_received = 0;
// END MPO
//============================================================
//  sdl_keyboard_device
//============================================================
class sdl_keyboard_device : public sdl_device
{
public:
        keyboard_state keyboard;
        sdl_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module)
                : sdl_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module),
                keyboard({{0}})
        {
        }
        void process_event(SDL_Event &sdlevent) override
        {
                switch (sdlevent.type)
                {
                case SDL_KEYDOWN:
                        keyboard.state[sdlevent.key.keysym.scancode] = 0x80;
                        if (sdlevent.key.keysym.sym < 0x20)
                                machine().ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), sdlev$
                        // START MPO
                        // We've received keyboard input (which is all we care about), so reset the timer
                        g_last_time_seconds_when_input_received = machine().time().seconds();
                        // END MPO
                        break;