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;


Monday, July 29, 2019

How I created my MAME cab

Greetings all,

I have eight arcade game cabinets.  My wife informed me that I had reached my limit.  So in order to have more games, I needed to break down and build a MAME cab (which I hope will evolve into a MAME/Daphne cab in the future).

For whatever reason, the look of the monitor was really important to me.  So I found an existing 25" CRT monitor installed inside of a D&D "Tower of Doom" cab that had a control panel with a ton of buttons on it.  Not only did I like this game, but having 4 joysticks with 4 buttons each was what I needed for a good MAME experience.  The monitor had been tube swapped and recapped so was looking great.  So I brought it home and began working on it to MAME-ize it.

Another important factor for me was to be able to run JAMMA games inside of it in the future if I chose, so I didn't want to mess with the JAMMA harness or the extra kick harness (which was configured for D&D Tower of Doom).

Here are the links that I used for research:
https://ryanfb.github.io/etc/2017/11/11/converting_a_jamma_arcade_cabinet_for_mame.html
https://wiki.arcadeotaku.com/w/Kick_Harness#CPS2_-_3.2F4_Player_Harness
http://bencao74.blogspot.com/2017/05/retropie-15khz-crt-tutorial-for.html
https://www.retrorgb.com/rpi240p.html
https://choccyhobnob.com/compiling-mame-on-raspberry-pi/

I purchased a JPAC and an IPAC4 (which I probably didn't need) in order to get full inputs.  In hindsight, I probably could've got an IPAC2 (or whatever it's called) since I only needed to wire up controls for players 3 and 4.  However, despite the IPAC4 being overkill, it also made setup easier because I didn't need to remap the player 3 and player 4 inputs.




I also purchased a gert666 adapter and the latest Raspberry Pi (3b+ at the time of this writing, the 4 is still hard to obtain).



For audio, I went with this amp which may not have been the best choice because it's a stereo amp and JAMMA cabs are only mono.  I also added this 3.5mm to RCA and this ground loop isolator which was very necessary to reduce unwanted buzz/hum.  I still get some minor hum through my speakers, so I don't really know if my audio choice is to blame or not, but it mostly works and I don't notice the hum while playing games.  I powered the amp through the existing 12V line on the JAMMA power supply that came with the cab.  Not sure if this was the best choice.  I've got a lot to learn about analog audio.


To hook up players 3 and players 4, I referred to the kick harness section for D&D at this link.  I wired up my own connector to match what is on the D&D board, and secured the wires with heat shrink and a glue gun.  Now I feel safe that this thing isn't going to come loose, and if I ever acquire a real D&D JAMMA, I can plug it in and play without any other configuration.



Configuring the software for the Pi was probably the hardest part.

I installed the latest Raspbian ("Buster" at the time of this writing), enabled SSH/Wifi and disabled video overscan.

I tried RetroPi but was unsatisfied with the old version of MAME they included and the way they force input mapping.  So I decided to build the latest MAME from scratch instead.

At the time of this writing, the latest MAME version is 0.211 .

There are a few problems with building this version of MAME on the Pi but I was able to work around them:

  1. Uncomment "NOWERROR = 1" to not fail on warnings
  2. Uncomment "NO_USE_XINPUT = 1"
  3. Uncomment "NO_USE_XINPUT_WII_LIGHTGUN_HACK = 1"
Follow the build instructions here.   When building SDL, you only need to build SDL and SDL_ttf, not the rest of the optional libraries.  In order to build SDL_ttf, you need the freetype-config script which is apparently not included with Raspbian Buster anymore, so I manually downloaded and installed freetype ( freetype-2.10.0 at the time of this writing ) which seemed to work and gave me the needed script.  NOTE that I just did the usual "./configure; make; sudo make install".  I did not uninstall the existing libfreetype6-dev Debian package, which might've caused conflicts but I got lucky.

I tried for several days to get cross-compiling working for MAME but eventually gave up and just followed choccyhobnob's instructions.  Building MAME on the Pi 3 takes forever (like 24-36 hours, literally).  I concluded that letting your pi churn away this long is probably still faster than messing around with cross compilation, so I'd just plan on waiting this long.

NOTE that you need to add "-video accel" to the mame command line to enable the hardware acceleration.  It seems to default to unaccelerated software mode which is too slow.


Once I had the software ready to go, I added this to /boot/config.txt to make the video output 15 khz 320x240 :

# MPO
disable_audio_dither=1
dtparam=audio=on
dtoverlay=vga666
enable_dpi_lcd=1
display_default_lcd=1
dpi_group=2
dpi_mode=87
audio_pwm_mode = 2
hdmi_timings=320 1 16 30 34 240 1 2 3 22 0 0 0 60 0 6400000 1 #240p

I then plugged the gert666 VGA adapter onto the pi, plugged its VGA output into the JPAC VGA input, then plugged the JPAC into the JAMMA harness.  I powered on the cab and got a nice big boot screen.



Running MAME in this res is problematic because it will downscale some games (like Capcom's 384x224) which looks pretty crappy.

I found a neat solution to this problem here.

By running games at 1600x240 or 1600x224 and turning off bilinear filtering, the display looks pretty dang good and you don't need to mess around with a ton of different configs (such as 320x224 or 384x224).  Besides, I couldn't get 384x224 to work on my monitor anyway, so I had to use 1600x224.

I created a few scripts to launch games at the proper resolution.  I haven't yet tried to setup a frontend because I've been trying to get the games looking great first.

My script (to_224.sh) to convert display to 1600x224:

#!/bin/bash

vcgencmd hdmi_timings 1600 1 70 150 215 224 1 10 14 16 0 0 0 60 0 32000000 1
fbset -depth 8 && fbset -depth 16 -xres 1600 -yres 224

My script (to_240.sh) to convert display to 1600x240:

#!/bin/bash

vcgencmd hdmi_timings 1600 1 70 150 215 240 1 4 3 15 0 0 0 60 0 32000000 1
fbset -depth 8 && fbset -depth 16 -xres 1600 -yres 240

My script to convert back to 320x240:

#!/bin/bash

vcgencmd hdmi_timings 320 1 16 30 34 240 1 2 3 22 0 0 0 60 0 6400000 1
fbset -depth 8 && fbset -depth 16 -xres 320 -yres 240

My script to launch Rastan (320x240) :

#!/bin/bash

./to_240.sh
cd code/mame
./mame rastan -video accel -noka -nofilter



My script to launch Golden Axe (320x224):

#!/bin/bash

./to_224.sh
cd code/mame
./mame goldnaxe -video accel -noka -nofilter



My script to launch Teenage Mutant Ninja Turtles (224 lines) :

#!/bin/bash

./to_224.sh
cd code/mame
./mame tmnt -video accel -noka -nofilter



My script to launch D&D Tower of Doom (384x224):

#!/bin/bash

./to_224.sh
cd code/mame
./mame ddtod -video accel -noka -nofilter




You get the idea.

Adding -noka tells MAME to fill the whole screen.  -nofilter tells MAME to disable bilinear filtering to give it that authentic CRT look.  The 1600 pixel width is enough so that the nearest neighbor scaling isn't noticeable (at least to my eyes).

Performance is very good all things considered.  All of the games I mentioned play at full speed except during some busy parts of the game.  Rastan looks especially good.  You'd never know that you weren't playing on the original JAMMA board.

I hope this helps someone :)

PS - You may notice black bars above and below my display.  This is due to the way I've adjusted the monitor pots.  There is some problems with the monitor when certain bright colors are displayed at the top and I decided I'd rather have the black bars rather than have to see the problems.  I don't think this is an issue with my Pi/JPAC config.

Monday, June 17, 2019

Dexter Sony LDP-1450 bug finally found and fixed!

Some of you know that I had to roll back a Dexter firmware upgrade a while ago due to that game Crime Patrol not working correctly with the update.

Well, I've been working behind the scenes to figure out what the bug was and pulling my hair out in frustration looking at my code.

It turns out that the bug had nothing to do with my changes that caused the problem but instead was a buffer overflow bug in some other code that hadn't been a problem by sheer luck :)

Once I added some new serial port code, this buffer overflow was overwriting my memory.  I finally had to attach an AVR Dragon (AVR programmer and debugger) and set a memory breakpoint to catch the problem.

Here is the code that caused the problem.  It's part of the LDP-1000/1450 interpreter:

uint16_t g_ldp1000i_tx_buf[12]; // this should be small enough so as to not waste space but big enough to hold things like current frame number
uint16_t *g_ldp1000i_pTxBufStart = 0;
uint16_t *g_ldp1000i_pTxBufEnd = 0;
const uint16_t *g_ldp1000i_pTxBufLastGoodPtr = (g_ldp1000i_tx_buf + sizeof(g_ldp1000i_tx_buf) - 1);

The fix is to change the last line to:

const uint16_t *g_ldp1000i_pTxBufLastGoodPtr = (g_ldp1000i_tx_buf + (sizeof(g_ldp1000i_tx_buf)/sizeof(uint16_t)) - 1);

The problem with working in C is that these types of mistakes are easy to make and the compiler often won't even warn you about it.  This is the price of working with lower level languages :o

Using the sizeof keyword is kind of dangerous because it always returns a value in bytes which doesn't work when your pointer is a uint16_t :)  I still use sizeof all over the place, though, because most of my pointers are uint8_t's :)

For future reference, when adding to a pointer, if you are going to use sizeof, you need to divide by the size (in bytes) of the element that makes up the array.  In this case, the TX buffer is 12 2-byte elements, so sizeof would return a 24 (12*2 is 24).  But when adding, I actually wanted to just add 12, not 24, because I wanted to add the number of elements, not the number of bytes.  So I needed to divide by the number of bytes per element (2 in this case).

After some testing, I expect to finally release some new firmware for Dexter in preparation for officially supporting the European version of Dragon's Lair (woohoo!).

Friday, May 24, 2019

How to Dockerize ld-decode (rev5)

If you are interested in the Domesday Duplicator, you may have come across the problem of installing the ld-decode software.  This is much easier using Docker because you can just use the Dockerfile that I already created.

Step 1:

Save the following text to a file called 'Dockerfile', in an empty subdirectory.

# known to work with 18.04, other versions may have incompatibilities
FROM ubuntu:18.04

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && \
    apt-get -y -qq install apt-utils sudo && \
        apt-get -y -qq install locales && locale-gen en_US.UTF-8 && \
    apt-get -y -qq install git \
        libopencv-dev \
        libfann-dev \
        python3-pip \
        python3-tk \
        clang \
        imagemagick wget

# more schlop to install
RUN apt-get -y -qq install libqt5charts5 libqt5charts5-dev qt5-default unzip

# create a normal user so we're not running as root
RUN export uid=1000 gid=1000 && \
    mkdir -p /home/developer && \
    echo "developer:x:${uid}:${gid}:Developer,,,:/home/developer:/bin/bash" >> /etc/passwd && \
    echo "developer:x:${uid}:" >> /etc/group && \
    echo "developer ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/developer && \
    chmod 0440 /etc/sudoers.d/developer && \
    chown ${uid}:${gid} -R /home/developer

# switch to user so it installs from the user's context
USER developer
ENV HOME /home/developer

# python3 schlop
RUN pip3 install numpy pandas scipy matplotlib

# set user's home directory as current directory because that's where we'll do work
WORKDIR /home/developer

# clone and build the rev5 commit
RUN wget https://github.com/happycube/ld-decode/archive/8e8b9f2c18136a1bcdbd3d659d72e42e23135771.zip
RUN unzip 8e8b9f2c18136a1bcdbd3d659d72e42e23135771.zip
RUN mv ld-decode-8e8b9f2c18136a1bcdbd3d659d72e42e23135771 ld-decode
RUN rm 8e8b9f2c18136a1bcdbd3d659d72e42e23135771.zip

RUN cd ld-decode && make && cd tools && qmake && make all && sudo make install



Step 2:

Build docker image using this command:

docker build -t lddecode_rev5 -f Dockerfile .

Step 3:

Run docker image using this command:

docker run --rm -v $HOME:/shared -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY -e "QT_X11_NO_MITSHM=1" -ti lddecode_rev5 /bin/bash

From inside the image, the /shared folder will point to your native host's home directory.

Follow instructions here for usage.  Also, check out the "ld-analyse" UI app, it is very slick.

The nice thing about using the docker container is that you don't have to worry about installing a bunch of ld-decode dependencies to your native host operating system.  To uninstall, you simply remove the docker image.  Also, you can run more than one container in parallel if desired.

Friday, March 1, 2019

My ironic story involving a Triangular Scale

So I was mentioning that I had mis-measured the dimensions on the Dragon's Lair ROM board (see previous blog post) and Shaun Wood suggested that I look into getting a Triangular Scale.

He linked to this one: https://www.staples.com/Staedtler-Mars-12-Architect-s-Triangular-Scale/product_274746

In case the link expires, it is showing a "Staedtler Mars 12" Architect Triangular Scale (987 19-31BK)".

The picture (if you click on it) clearly shows that an inch is divided into tenths which makes for convenient PCB measuring since PCB manufacturers often use tenths of inches as their scale.

Well, I tried to find this piece on Amazon for free shipping and couldn't.  So I just googled it and found that an Office Depot local to me (on my way home, in fact!) carried it.  So I thought "Great!"

I went to the Office Depot, walked inside and quickly turned down a random isle so that I wouldn't attract attention.  (For some reason, I kinda get nervous about an employee walking up to me and asking if I need help finding anything.)  So I went down a random isle, pulled out my phone, and pulled up the link that Shaun Wood had provided.  I took note that it was the model "98719-31BK" then said to myself "Ok, now to find where it is in this store!"

I looked up from my phone and literally right in front of me was what I was looking for.  Haha!  I've never had that happen before: going down a random isle and winding up exactly where I wanted to be.  That's the first irony of the story.

The second irony is that as I examined the "98719-31BK", I noticed that it was divided into 16th's , not 10th's.  "What the heck?" I muttered to myself, as I looked at the back.  On the back of the packaging, it said that the 98719-31BK had standard 'ruler' type divisions such as 1/8, 1/4, 3/16, 3/8, 1/2, etc.  And I also noted that the similarly branded 98719-34BK was the one I actually wanted, which was divided into 1/10, 1/20, 1/30, etc.

"Oh, great," I grumbled.  "I came all this way to find the 98719-31BK only to learn that it's not the one I even want!"

Then I looked up and right next to the 98719-31BK was hanging a 98719-34BK, the very item that I wanted!

I felt kind of bemused.  Shaun inadvertently links me to the wrong item but it has the right picture.  I go to the Office Depot to get the wrong item (unknowingly) only to find out that they have the right item right next to the wrong item.  And as a bonus, I randomly walk right up to it without even paying attention to where I am going.  It felt awesome and kind of bewildering all at the same time.

That's all my story.  Good day.

Friday, February 22, 2019

Been learning KiCad (Dragon's Lair ROM PCB created to help me learn)

After I discovered that my Eagle v7 license only allows PCB sizes of something like 6" x 4" (I was disgusted), I decided to learn KiCad so that I could make larger PCBs.

Needing a simple and easy PCB to start with, I decided to reproduce the Dragon's Lair ROM board which is very basic.  All it has is a connector, five EPROM sockets, and some capacitors.

Here's what I ended up with.  I am pretty happy overall with the tool, although I did a find at least one feature that Eagle has that KiCad doesn't have that I am missing.  Still, I think KiCad may be the overall winner between the two considering how far astray Eagle has gone these days.



(here's the original for reference)

Monday, February 11, 2019

Added a new molex connector to my Electrohome G07

Someone had cut off the original molex power connector for this G07.  I studied the connector for another G07 that I have in my Dragon's Lair and found what I believe is a compatible replacement.

A small victory, but at least it was a mini project completed.


Tuesday, January 1, 2019

Digilent Discovery Quick Howto

When I haven't used my Digilent Digital Discovery logic analyzer for a while, I kinda forget how to use it.  This logic analyzer is really nice because it's relatively cheap but can capture 32 channels (!!) and has some pretty decent software to help me visualize my data.  The only real downside that I've found is that it doesn't stream so you can't have arbitrarily large samples.  This is the one killer feature of the Saleae which prevents me from saying that the Digital Discovery wins in every category.  However, it does have a decent buffer size especially for 'slower' capture speeds like 1 MHz which is usually plenty for the old tech that I like to study.

Without further ado, here are my notes (to myself) for how to set up the software to make the logic analyzer do what I want.  And a bonus youtube video at the end illustrating all of it.

----------------------

Click "Logic"

Change mode to "Record"

Click + to add channels
If adding multiple lines, choose Bus, otherwise Signal

If using trigger, change trigger dropdown to 'normal'.
Else, set it to 'none'.

Click "Config"

Choose Rate (I like 1 MHz minimum)
Uncheck 'noise'
Set 'base' to how much time you want to capture. 'Samples' will be auto computed.  Note, streaming doesn't seem to be supported, so the higher the sample rate, the smaller the sample size.

If using trigger,
Set trigger to 10% or less.  10% will capture 10% of data before the trigger happened which is often the most useful for me.