Skip to content

Instantly share code, notes, and snippets.

@jasonpstokes
Forked from sonicprod/RPi4.MAME.Appliance.md
Last active April 14, 2025 12:35
Show Gist options
  • Save jasonpstokes/1316209e2f41a10cfa5f4e631d102503 to your computer and use it in GitHub Desktop.
Save jasonpstokes/1316209e2f41a10cfa5f4e631d102503 to your computer and use it in GitHub Desktop.
How to make a dedicated MAME Appliance on a Raspberry Pi 4B

Note

This is my summary version of https://gist.github.com/sonicprod/f5a7bb10fb9ed1cc5124766831e120c4 specific to my use case. Credit (and a big thanks) to Sonicprod (+ others) for their hard work on the original!

Raspberry Pi 4 MAME Appliance

How to make a dedicated MAME Appliance on a Raspberry Pi 4/Pi 400.

https://www.raspberrypi.com/software/operating-systems/#raspberry-pi-os-64-bit

Download Raspberry Pi OS and write the image to an SD Card.

Activate SSH on initial boot: Create ssh. file in root of sdcard, or

sudo touch $(findmnt -t vfat PARTUUID=0ee3e8a8-01 --output=target --noheadings)/ssh

Boot Raspberry Pi

Update system packages:

sudo apt update && sudo apt upgrade -y
sudo apt clean -y && sudo apt autoclean -y

System customisations

Configure Time zone and locale

sudo dpkg-reconfigure tzdata
sudo dpkg-reconfigure locales

Configure NTP

sudo nano /etc/systemd/timesyncd.conf

Create aliases

nano ~/.bash_aliases
alias update='sudo apt update && sudo apt upgrade -y && sudo apt clean -y && sudo apt autoclean -y'
alias cputemp='/usr/bin/vcgencmd measure_temp'

# Aliases to switch between Arcade Mode and Service Mode
alias arcademode='sudo systemctl enable mame-autostart.service'
alias servicemode='sudo systemctl disable mame-autostart.service'
alias mode='echo -n "The system is currently in "; systemctl -q is-active mame-autostart.service && echo -n ARCADE || echo -n SERVICE; echo " mode."'

Remove login disclaimer:

sudo rm /etc/motd

Display notices at login

sudo nano /etc/issue
S E R V I C E      M O D E

IP Address: \4 \6
sudo nano /etc/bash.bashrc
echo '----------------------------------------------------------------------'
echo "The system is currently in $(systemctl -q is-active mame-autostart.service && echo ARCADE || echo SERVICE) mode."

Adjust pi account privileges

sudo usermod -a -G render pi

Enable VC4 fake KMS mode driver

sudo sed -i 's/dtoverlay=vc4-kms-v3d/dtoverlay=vc4-fkms-v3d/g' /boot/firmware/config.txt

Remove wireless and network services

sudo apt remove -y bluez pi-bluetooth nfs-common avahi-daemon modemmanager && sudo apt autoremove -y

sudo rm /var/lib/man-db/auto-update

sudo systemctl disable bluetooth.service
sudo nano /boot/firmware/config.txt
# Disable Bluetooth
dtoverlay=disable-bt

Pi performance (overclocking) and video configuration

sudo nano /boot/firmware/config.txt
# Overclocking
arm_freq=2000
over_voltage=6

# Video
disable_splash=1
cec_osd_name=Arcade
gpu_mem=128

Switch CPU Governor to Performance

sudo apt install cpufrequtils -y
grep -q GOVERNOR= /etc/init.d/cpufrequtils && sudo sed -i "s/GOVERNOR=.*$/GOVERNOR=\"performance\"/g" /etc/init.d/cpufrequtils

sudo systemctl daemon-reload
sudo systemctl restart cpufrequtils.service

Disable init script that resets Governor to ondemand

sudo systemctl disable raspi-config

Configure Hostname

sudo nano /etc/hostname
sudo nano /etc/hosts

Reboot Pi with new config

sudo reboot

Test

echo Clock Speed=$(($(/usr/bin/vcgencmd measure_clock arm | awk -F '=' '{print $2}')/1000000)) MHz

cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

Compile SDL2 (optimised, without X11)

mkdir ~/scripts

nano ~/scripts/sdl2-latest.sh
#!/bin/bash

# This script build the latest SDL2 version without X11.

function sdl2-latest {
  CHECKURL=https://github.com/libsdl-org/SDL/releases/latest
  HTMLTAG='<title>Release '

  LATESTSDL2VER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $2}')

  if [ -z $LATESTSDL2VER ]; then echo ERROR; exit; fi   # We make sure wget was successful

  echo $LATESTSDL2VER
  }

function sdl2-ttf-latest {
  CHECKURL=https://github.com/libsdl-org/SDL_ttf/releases/latest
  HTMLTAG='<title>Release '

  LATESTSDL2TTFVER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $2}')

  if [ -z $LATESTSDL2TTFVER ]; then echo ERROR; exit; fi   # We make sure wget was successful

  echo $LATESTSDL2TTFVER
  }

VERSION=$(sdl2-latest)
TTFVERSION=$(sdl2-ttf-latest)

if [ "$(sdl2-config --version)" == "$VERSION" ]; then
  echo SDL2 is already at the latest version \($VERSION\).
  exit
else
  if [ "${1,,}" != "nodep" ]; then
    echo Installing SDL2 dependencies...
    sudo apt install libfreetype6-dev libdrm-dev libgbm-dev libudev-dev libdbus-1-dev libasound2-dev liblzma-dev libjpeg-dev libtiff-dev libwebp-dev -y
    echo OpenGL ES 2 dependencies...
    sudo apt install libgles2-mesa-dev -y
  fi
  echo Build dependencies...
  sudo apt install build-essential -y
  cd ~
  echo Buiding SDL2 $VERSION...
  # Based from "Compile SDL2 from source"
  # https://github.com/midwan/amiberry/wiki/Compile-SDL2-from-source
  wget https://libsdl.org/release/SDL2-${VERSION}.zip
  unzip SDL2-${VERSION}.zip
  rm SDL2-${VERSION}.zip
  cd SDL2-${VERSION}
  ./configure --disable-video-opengl --disable-video-opengles1 --disable-video-x11 --disable-pulseaudio --disable-esd --disable-video-wayland --disable-video-rpi --disable-video-vulkan --enable-video-kmsdrm --enable-video-opengles2 --enable-alsa --disable-joystick-virtual --enable-arm-neon --enable-arm-simd

  [ $(uname -m) == "armv7l" ] && make -j $(nproc) CFLAGS='-mtune=cortex-a72 -mfpu=neon-fp-armv8 -mfloat-abi=hard'
  [ $(uname -m) == "aarch64" ] && make -j $(nproc) CFLAGS='-mcpu=cortex-a72'

  sudo make install

  # SDL2_ttf
  wget https://libsdl.org/projects/SDL_ttf/release/SDL2_ttf-${TTFVERSION}.tar.gz
  tar zxvf SDL2_ttf-${TTFVERSION}.tar.gz
  rm SDL2_ttf-${TTFVERSION}.tar.gz
  cd SDL2_ttf-${TTFVERSION}
  ./configure
  make -j $(nproc)
  sudo make install
  sudo ldconfig -v

  cd ~
  sudo rm -R SDL2-${VERSION}
  sudo rm -R SDL2_ttf-${TTFVERSION}
fi
chmod +x ~/scripts/sdl2-latest.sh && ~/scripts/sdl2-latest.sh

Compile MAME (optimized, without X11)

nano ~/scripts/mame-updater.sh
#!/bin/bash

# This script update MAME to the latest version or a specific version.

# GCC compiler optimization for ARM-based systems : https://gist.github.com/fm4dd/c663217935dc17f0fc73c9c81b0aa845
#ARCHOPTS='-mcpu=cortex-a72 -mtune=cortex-a72 -mfpu=neon-fp-armv8 -mfloat-abi=hard -mneon-for-64bits -funsafe-math-optimizations -munaligned-access -fexpensive-optimizations'

# A tester.....
ARCHOPTS32='-march=armv8-a+crc+simd -mcpu=cortex-a72 -mtune=cortex-a72 -mfpu=neon-fp-armv8 -mfloat-abi=hard -funsafe-math-optimizations -fprefetch-loop-arrays -fexpensive-optimizations'
ARCHOPTS64='-march=armv8-a+crc+simd -mcpu=cortex-a72 -mtune=cortex-a72 -funsafe-math-optimizations -fprefetch-loop-arrays -fexpensive-optimizations'

MAKEOPTS='TARGETOS=linux NO_X11=1 NOWERROR=1 NO_USE_XINPUT=1 NO_USE_XINPUT_WII_LIGHTGUN_HACK=1 NO_OPENGL=1 USE_QTDEBUG=0 DEBUG=0 REGENIE=1 NO_BGFX=1 FORCE_DRC_C_BACKEND=1 NO_USE_PORTAUDIO=1 SYMBOLS=0'
MAXTHREAD=2    # From MAME version 0.227 and up, we should use a max. of 3 threads to avoid an out of memory error.

secs_to_human() {
    echo "$(( ${1} / 3600 ))h $(( (${1} / 60) % 60 ))m $(( ${1} % 60 ))s"
    }

fs_unlock() {   # Put root filesystem in read/write mode
    for o in $(findmnt -n -o OPTIONS / | sed 's/,/ /g'); do
        [ "${o,,}" = ro ] && sudo mount -o remount,rw /
    done
    }

fs_lock() {     # Put root filesystem in read-only mode
    for o in $(findmnt -n -o OPTIONS / | sed 's/,/ /g'); do
        [ "${o,,}" = rw ] && sudo mount -o remount,ro /
    done
    }

set_env() {     # Make an environment variable persistent
    local KEY=$(echo $1 | awk -F '=' '{print $1}')
    local VALUE=$(echo $1 | awk -F '=' '{print $2}')
    grep -q $KEY= /etc/environment && sudo sed -i "s/$KEY=.*$/$KEY=$VALUE/g" /etc/environment || echo "$KEY=$VALUE" | sudo tee -a /etc/environment
    }

mame-latest() {         # Get the latest version of MAME
  CHECKURL=https://github.com/mamedev/mame/releases/latest
  HTMLTAG='<title>Release MAME'

  LATESTMAMEVER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $3}')

  if [ -z $LATESTMAMEVER ]; then echo ERROR; exit; fi  # We make sure wget was successful

  echo $LATESTMAMEVER
  }

if (systemctl -q is-active mame-autostart.service) then
    echo "The system must be put in SERVICE Mode first."
    exit
fi

if [ ! $1 ]; then
    echo Usage: $0 VER | Latest
    echo '  Where VER is the 4-digit version number of MAME to update (for example: 0224).'
    echo '  OR use the argument Latest to update to the latest available MAME version.'
else
    if [ $(free -g | awk '/^Mem:/{print $2}') -lt 3 ]; then
        echo You need 4 GB of RAM to compile MAME with multithread support.
        exit
    fi

    if [ "${1,,}" == "latest" ]; then
      MAMEVER=$(mame-latest)
      MAMEVER=${MAMEVER//./}    # Remove the dot
    else
      MAMEVER=$1
    fi
    MAMESRCPATH=/home/pi/mame${MAMEVER}
    SCRIPTPATH=${0%/*}

    echo Installing/uptading to MAME $MAMEVER...
    fs_unlock

    # Install MAME dependencies...
    sudo apt install fontconfig libfontconfig-dev libx11-dev libpulse-dev -y

    if [ ! -d $MAMESRCPATH ]; then
        cd ~
        if [ ! -f ~/mame${MAMEVER}.zip ]; then    # Not already downloaded
            wget https://github.com/mamedev/mame/archive/mame${MAMEVER}.zip
        fi

        if [ -f ~/mame${MAMEVER}.zip ]; then      # If download successful, uncompress ...
            unzip mame${MAMEVER}.zip
            mv mame-mame${MAMEVER} mame${MAMEVER}
            rm mame${MAMEVER}.zip
        fi
    fi

    if [ ! -x $MAMESRCPATH/mame ]; then         # We build only if not already built
        # Build of MAME

        # Dependencies
        # Swap requirement
        sudo apt install dphys-swapfile -y
        if [ "$(grep '^CONF_SWAPSIZE' /etc/dphys-swapfile | cut -d= -f2)" != "2048" ]; then
            sudo sed -i "s/^#\{0,1\}\s*CONF_SWAPSIZE=.*$/CONF_SWAPSIZE=2048/g" /etc/dphys-swapfile
        fi
        # The swap value should be 2048

        # Activate the swap...
        sudo systemctl stop dphys-swapfile.service
        sudo systemctl start dphys-swapfile.service
        # Build dependencies
        sudo apt install build-essential -y

        cd $MAMESRCPATH
        [ "$(uname -m)" == "armv7l" ]  && echo MAKE CMDLINE=make -j$MAXTHREAD ARCHOPTS=\"$ARCHOPTS32\" $MAKEOPTS PLATFORM=arm
        [ "$(uname -m)" == "aarch64" ] && echo MAKE CMDLINE=make -j$MAXTHREAD ARCHOPTS=\"$ARCHOPTS64\" $MAKEOPTS PLATFORM=arm64 PTR64=1
        BUILDSTART=$(date +%s)
        echo Build start time: $(date +"%T")
        echo -----------------------------------------------------------------------------------
        echo Please wait until the build is completed \(about 10 hours\)...
        echo -----------------------------------------------------------------------------------
        [ "$(uname -m)" == "armv7l" ]  && make -j$MAXTHREAD ARCHOPTS="$ARCHOPTS32" $MAKEOPTS PLATFORM=arm
        [ "$(uname -m)" == "aarch64" ] && make -j$MAXTHREAD ARCHOPTS="$ARCHOPTS64" $MAKEOPTS PLATFORM=arm64 PTR64=1

        echo Build time took: $(secs_to_human "$(($(date +%s) - ${BUILDSTART}))").

        if [ -x $MAMESRCPATH/mame ]; then
            echo -----------------------------------------------------------------------------------
            echo Build Success!
            date +"%T"
            echo -----------------------------------------------------------------------------------
            # Successful build!
            # We make sure the MAME environment variable is persistent...
            set_env SDL_VIDEODRIVER=kmsdrm
            set_env SDL_RENDER_DRIVER=opengles2
            set_env SDL_RENDER_VSYNC=1
            set_env SDL_GRAB_KEYBOARD=1
            set_env SDL_VIDEO_GLES2=1

            # Install MAME True-Type Liberation Sans font
            if [ ! -d /usr/share/fonts/truetype/liberation-fonts ]; then
              echo Installing Liberation Sans font...
              cd ~
              wget -q https://dl.dafont.com/dl/?f=liberation_sans -O liberation_sans.zip
              if [ -f liberation_sans.zip ]; then
                sudo mkdir /usr/share/fonts/truetype/liberation-fonts
                sudo unzip -o /home/pi/liberation_sans.zip *.ttf -d /usr/share/fonts/truetype/liberation-fonts
                rm liberation_sans.zip
                sudo fc-cache -f -v
              fi
            fi

            # MAME binary symlink creation or update
            if [ -L ~/mame ]; then rm ~/mame; fi
            ln -s $MAMESRCPATH ~/mame

            if [ ! -L ~/.mame ] && [ ! -d ~/.mame ]; then   # MAME data path symlink creation
                [ -d /data/mame ] && ln -s /data/mame ~/.mame || mkdir ~/.mame
            fi

            if [ ! -f ~/.mame/mame.ini ]; then  # If mame.ini does not exist, let's create it
                cd ~/.mame
                $MAMESRCPATH/mame \
                  -hashpath '$HOME/mame/hash' \
                  -languagepath '$HOME/mame/language' \
                  -pluginspath '$HOME/mame/plugins' \
                  -artpath '$HOME/.mame/artwork' \
                  -ctrlrpath '$HOME/.mame/ctrlr' \
                  -inipath '$HOME/.mame/ini' \
                  -homepath '$HOME/.mame/lua' \
                  -rompath '$HOME/.mame/roms' \
                  -cfg_directory '$HOME/.mame/cfg' \
                  -diff_directory '$HOME/.mame/diff' \
                  -input_directory '$HOME/.mame/inp' \
                  -nvram_directory '$HOME/.mame/nvram' \
                  -snapshot_directory '$HOME/.mame/snap' \
                  -state_directory '$HOME/.mame/sta' \
                  -skip_gameinfo \
                  -video accel \
                  -videodriver kmsdrm \
                  -renderdriver opengles2 \
                  -audiodriver alsa \
                  -samplerate 22050 \
                  -createconfig
            fi

            # Freeing some space...
            for f in src build 3rdparty roms
            do
                rm -Rf $MAMESRCPATH/$f
            done

            # Removing swap...
            sudo systemctl stop dphys-swapfile.service
            sudo rm /var/swap
            sudo apt remove dphys-swapfile build-essential -y
            sudo apt autoremove -y
        else
            echo -----------------------------------------------------------------------------------
            echo Build FAILED.
            date +"%T"
            echo -----------------------------------------------------------------------------------
        fi
    fi
    fs_lock
fi
chmod +x ~/scripts/mame-updater.sh

# Compile latest version of MAME (~11 hours):
scripts/mame-updater.sh latest > build.txt 2>&1

Set up MAME configuration

cd ~/.mame

mkdir artwork cabinets cfg cpanels ctrlr diff flyers hi history icons ini inp lua marquees memcard pcb nvram roms snap sta titles ui video

mv mame.ini ui.ini plugin.ini hiscore.ini ini/
ln -s ./ini/mame.ini mame.ini

[ -f ./lua/hiscore/plugin.cfg ] && mv ./lua/hiscore/plugin.cfg ./hi
rmdir ./lua/hiscore
ln -s ./hi ./lua/hiscore

mv ~/mame/artwork/* ~/.mame/artwork/
mv ~/mame/ctrlr/* ~/.mame/ctrlr/
mv ~/mame/roms/* ~/.mame/roms/
mv ~/mame/snap/* ~/.mame/snap/

cd ~/mame; rm -R artwork ctrlr nvram history roms snap
nano ~/.attract/emulators/mame.cfg
artwork    marquee         $HOME/.mame/marquees
artwork    snap            $HOME/.mame/video;$HOME/.mame/snap
nano ~/.mame/ini/plugin.ini
#
# PLUGINS OPTIONS
#
cheat                     0
cheatfind                 0
console                   0
data                      1
dummy                     0
gdbstub                   0
hiscore                   1
layout                    0
portname                  0
timer                     0
nano ~/.mame/ini/hiscore.ini
hi_path                   $HOME/.mame/hi
nano ~/.mame/ini/ui.ini

Adjust the paths below by specifying the prefix $HOME/.mame/ followed by the folder name:

historypath               $HOME/.mame/history
cabinets_directory        $HOME/.mame/cabinets
cpanels_directory         $HOME/.mame/cpanels
pcbs_directory            $HOME/.mame/pcb
flyers_directory          $HOME/.mame/flyers
titles_directory          $HOME/.mame/titles
marquees_directory        $HOME/.mame/marquees
icons_directory           $HOME/.mame/icons
ui_path                   $HOME/.mame/ui

[Optional] MAME customisations

nano ~/.mame/ini/mame.ini
effect                    scanlines

Prevent config being overwritten / resetting to default (and artwork and videos not working)

chmod 0444 ~/.attract/emulators/mame.cfg

...or SFTP artwork and ini's from backup.

MAME version check script

nano ~/scripts/mame-version-check.sh
#!/bin/bash

function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }

# This function check the latest available version of MAME and display a notice if the current version is older.
function mame-check {
  CHECKURL=https://github.com/mamedev/mame/releases/latest
  HTMLTAG='<title>Release MAME'

  [ -x /home/pi/mame/mame ] && export MAMEVER=$(/home/pi/mame/mame -version | cut -d' ' -f1)
  if [ ! -z $MAMEVER ]; then
    LATESTMAMEVER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $3}')

    if [ -z $LATESTMAMEVER ]; then echo $MAMEVER ERROR; exit; fi  # We make sure wget was successful

    if [ $(version $LATESTMAMEVER) -gt $(version $MAMEVER) ]; then
      echo $MAMEVER $LATESTMAMEVER
    else
      echo $MAMEVER Latest
    fi
  fi
  }

echo '+---------------+-----------+-----------+'
echo '| EMULATOR      | CURRENT   | LATEST    |'
echo '+---------------+-----------+-----------+'
[ -x $HOME/mame/mame ]       && echo -n '| MAME          | '; mame-check    | awk '{ printf "%-9s | %-9s |\n", $1, $2}'
echo '+---------------+-----------+-----------+'
chmod +x ~/scripts/mame-version-check.sh

Compile SFML and Attract-Mode Plus with Arcade Flow

nano ~/scripts/attractplus-build.sh
#!/bin/bash

sudo apt install -y build-essential cmake libxrandr-dev libxcursor-dev libudev-dev libfreetype-dev libopenal-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev

CHECKURL=https://github.com/SFML/SFML/releases/latest
HTMLTAG='<title>Release SFML'
LATEST_SFML_VER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $3}')

wget https://github.com/SFML/SFML/archive/refs/tags/$LATEST_SFML_VER.zip -P ~
unzip ~/$LATEST_SFML_VER.zip
cd ~/SFML-$LATEST_SFML_VER/
mkdir build
cd build
cmake .. -DSFML_USE_DRM=TRUE
make -j $(nproc)
sudo make install
sudo ldconfig -v
rm ~/$LATEST_SFML_VER.zip
rm -R ~/SFML-$LATEST_SFML_VER/

sudo apt install -y libavutil-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libswresample-dev libgl1-mesa-dev libglu1-mesa-dev libcurl4-openssl-dev libopenal-dev

CHECKURL=https://github.com/oomek/attractplus/releases/latest
HTMLTAG='<title>Release Attract-Mode Plus'
LATEST_AMP_VER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $4}')

wget https://github.com/oomek/attractplus/archive/refs/tags/$LATEST_AMP_VER.zip -P ~
unzip ~/$LATEST_AMP_VER.zip
cd ~/attractplus-$LATEST_AMP_VER/
make -j $(nproc) USE_DRM=1 USE_MMAL=1 USE_GLES=1
sudo make install
mv ./config ~/.attract
rm ~/$LATEST_AMP_VER.zip
rm -R ~/attractplus-$LATEST_AMP_VER/

CHECKURL=https://github.com/zpaolo11x/Arcadeflow/releases/latest
HTMLTAG='<title>Release Arcadeflow'
LATEST_AF_VER=$(wget -q -O - $CHECKURL | grep "$HTMLTAG" | awk '{print $3}')

wget https://github.com/zpaolo11x/Arcadeflow/archive/refs/tags/$LATEST_AF_VER.zip -P ~
rm -R ~/.attract/layouts/Arcadeflow/
unzip ~/$LATEST_AF_VER.zip -d ~/.attract/layouts/
mv ~/.attract/layouts/Arcadeflow-$LATEST_AF_VER ~/.attract/layouts/Arcadeflow
rm ~/$LATEST_AF_VER.zip
chmod +x ~/scripts/attractplus-build.sh && ~/scripts/attractplus-build.sh

Configure start-up environment

Front-end startup script

nano ~/scripts/autostart.sh
#!/bin/bash

until /usr/local/bin/attractplus > ~/.mame/run.log 2>&1; do
  sleep 1
done

find ~/.mame/log.txt -size +2M -delete
chmod +x ~/scripts/autostart.sh

Front-end auto-start

sudo systemctl edit mame-autostart.service --force --full
[Unit]
Description=MAME Appliance Autostart service
[email protected] rng-tools.service cron.service
Requires=local-fs.target
After=local-fs.target

[Service]
User=pi
Group=pi
PAMName=login
Type=simple
ExecStart=/home/pi/scripts/autostart.sh
Restart=on-abort
RestartSec=5
TTYPath=/dev/tty1
StandardInput=tty

[Install]
WantedBy=multi-user.target
Also=shutdown.service
sudo systemctl disable mame-autostart.service

Reactivation of the cursor in the console when getty@tty1 is activated (Service Mode):

nano ~/.bashrc
setterm --cursor on

Automatic system shutdown on front-end exit

sudo systemctl edit shutdown.service --force --full
[Unit]
Description=Shutdown and poweroff service
After=mame-autostart.service

[Service]
TTYPath=/dev/tty1
ExecStart=/sbin/poweroff
StandardInput=tty

[Install]
WantedBy=multi-user.target
sudo systemctl disable shutdown.service

Re-configure fstab

sudo nano /boot/firmware/cmdline.txt

change root=PARTUUID=xxxxxxxx to root=/dev/mmcblk0p2

sudo nano /etc/fstab
/dev/mmcblk0p1        /boot           vfat    defaults,rw                 0    2
/dev/mmcblk0p2        /               ext4    defaults,noatime,rw         0    1

Hide OS output on console

sudo nano /boot/firmware/cmdline.txt

change console=tty1 to console=tty3

add:

logo.nologo vt.global_cursor_default=0 quiet init=/bin/systemd

Display splash screens on boot and shutdown

sudo apt-get install fim -y

mkdir ~/splash

copy mame.jpg into ~/splash and symlink

ln -s ~/splash/mame.jpg ~/splash/mame-boot.jpg
ln -s ~/splash/mame.jpg ~/splash/mame-shutdown.jpg

Create services:

sudo systemctl edit mame-bootsplash.service --force --full
[Unit]
Description=MAME Boot Splash Screen service
DefaultDependencies=no
After=local-fs.target

[Service]
Type=simple
StandardInput=tty
StandardOutput=tty
ExecStart=/usr/bin/fim -q --no-history /home/pi/splash/mame-boot.jpg
SuccessExitStatus=42

[Install]
WantedBy=sysinit.target
sudo systemctl enable mame-bootsplash.service

sudo systemctl edit mame-shutdownsplash.service --force --full
[Unit]
Description=MAME Shutdown/Poweroff/Reboot Splash Screen service
DefaultDependencies=no
Before=halt.target
Conflicts=mame-bootsplash.service

[Service]
Type=simple
ExecStart=/usr/bin/fim -q --no-history /home/pi/splash/mame-shutdown.jpg
SuccessExitStatus=42

[Install]
WantedBy=reboot.target halt.target poweroff.target
sudo systemctl enable mame-shutdownsplash.service

sudo systemctl start mame-shutdownsplash.service

Download some games to test

cd ~/.mame/roms
wget -i - << 'EOF'
https://archive.org/download/mame-merged/mame-merged/dkong.zip
https://archive.org/download/mame-merged/mame-merged/galaga.zip
https://archive.org/download/mame-merged/mame-merged/puckman.zip
https://archive.org/download/mame-merged/mame-merged/raiden2.zip
https://archive.org/download/mame-merged/mame-merged/simpsons.zip
EOF

Cleanup OS

sudo apt remove -y build-essential cmake

sudo apt autoremove --purge && sudo apt autoclean && sudo apt clean

arcademode

sudo reboot

Note

Now is a good time to backup the SD card in another machine, and shrink/compress the img with PiShrink (https://github.com/Drewsif/PiShrink)

Setup f2fs /data partition

Create separate read-write partition and move atract and mame data there

In Service mode:

nano ~/scripts/move-data.sh
#!/bin/bash

# Expand root parition and create /data parition
sudo fdisk /dev/mmcblk0 << 'EOF'
d
2
n
p
2
1056768
+15G

n
p
3
32514048
+5G

w
EOF

sudo partprobe /dev/mmcblk0

# Resize root file system
sudo e2fsck -f /dev/mmcblk0p2
sudo resize2fs /dev/mmcblk0p2
sync

sudo apt -y install f2fs-tools

# Format and mount new data parition
sudo wipefs /dev/mmcblk0p3
sudo mkfs.f2fs -l data /dev/mmcblk0p3 -f

sudo mkdir /data
sudo mount -t f2fs -o rw /dev/mmcblk0p3 /data
sudo mount -o remount,rw /data

sudo su -c "echo '/dev/mmcblk0p3        /data           f2fs    defaults,noatime            0    2' >> /etc/fstab"

echo 'Creating /data...'
cd /data
sudo mkdir mame attract
sudo chown -R pi:pi /data
sudo chmod -R 3774 /data

# move MAME configuration
echo 'Moving .mame to /data/...'
mv -b ~/.mame/* /data/mame/
cd ~; rmdir .mame
ln -s /data/mame ~/.mame

# move Attract-Mode Plus configuration
echo 'Moving .attract to /data/...'
mv -b ~/.attract/* /data/attract/
cd ~; rmdir .attract
ln -s /data/attract ~/.attract
chmod +x ~/scripts/move-data.sh && ~/scripts/move-data.sh

Make Root FileSystem read-only

nano ~/scripts/read-only-rootfs.sh
#!/bin/bash

# Check /etc/fstab to see if this script has already been executed
awk '/\/ /{print $4}' /etc/fstab | grep -q ro, && awk '/\/boot/{print $4}' /etc/fstab | grep -q ro, && echo 'This script has already been executed and your system is already in read-only mode.' && exit

# From: https://www.dzombak.com/blog/2024/03/Running-a-Raspberry-Pi-with-a-read-only-root-filesystem.html

# Migrate to ntp instead of systemd-timesyncd
sudo apt install -y ntp

sudo sh -c 'echo "driftfile /var/tmp/ntp.drift" >> /etc/ntp.conf'

# sudo systemctl edit ntp
sudo mkdir -p /etc/systemd/system/ntpsec.service.d/
sudo sh -c 'tee -a /etc/systemd/system/ntpsec.service.d/override.conf << "EOF"
[Service]
PrivateTmp=false
EOF'

# Move fake-hwclock to rw partition
# From: https://www.dzombak.com/blog/2024/06/Moving-fake-hwclock-to-a-separate-partition-on-a-read-only-Raspberry-Pi.html
mkdir /data/system
sudo chmod 0777 /data/system
sudo sudo sh -c 'echo "FILE=/data/system/fake-hwclock.data" >> /etc/default/fake-hwclock'

sudo rm /etc/cron.hourly/fake-hwclock
sudo tee -a /etc/cron.hourly/fake-hwclock << 'EOF'
#!/bin/sh
#
# Simple cron script - save the current clock periodically
# https://www.dzombak.com/blog/2024/06/Moving-fake-hwclock-to-a-separate-partition-on-a-read-only-Raspberry-Pi.html
#

if (command -v fake-hwclock >/dev/null 2>&1) ; then
  PARAM=/etc/default/fake-hwclock
  if [ -f "$PARAM" ]; then
    set -o allexport
    . "$PARAM"
    set +o allexport
  fi

  fake-hwclock save
fi
EOF

# sudo systemctl edit fake-hwclock.service
sudo mkdir -p /etc/systemd/system/fake-hwclock.service.d
sudo sh -c 'tee -a /etc/systemd/system/fake-hwclock.service.d/override.conf << "EOF"
[Unit]
RequiresMountsFor=/data/system/fake-hwclock.data
EOF'

# NetworkManager
sudo apt install -y resolvconf
[ ! -L /etc/resolv.conf ] && sudo rm /etc/resolv.conf && sudo ln -s /run/resolvconf/resolv.conf /etc/resolv.conf
sudo rm -rf /var/lib/dhcp && sudo ln -s /var/run /var/lib/dhcp
sudo rm -rf /var/lib/NetworkManager && sudo ln -s /var/run /var/lib/NetworkManager

# Update the systemd random seed
[ -f /var/lib/systemd/random-seed ] && sudo mv /var/lib/systemd/random-seed /tmp/systemd-random-seed
[ ! -L /var/lib/systemd/random-seed ] && sudo ln -s /tmp/systemd-random-seed /var/lib/systemd/random-seed

# Disable systemd-rfkill
sudo systemctl disable systemd-rfkill.service
sudo systemctl mask systemd-rfkill.socket

# Move temporary folders to tmpfs
sudo tee -a /etc/fstab << 'EOF'
tmpfs  /tmp      tmpfs  defaults,noatime,nosuid,nodev  0  0
tmpfs  /var/tmp  tmpfs  defaults,noatime,nosuid,nodev  0  0
tmpfs  /var/lib/logrotate  tmpfs  defaults,noatime,nosuid,nodev,noexec,size=1m,mode=0755  0  0
tmpfs  /var/lib/sudo  tmpfs  defaults,noatime,nosuid,nodev,noexec,size=1m,mode=0700  0  0
EOF

# Disable journald
sudo sed -i '/Storage=/c\Storage=none' /etc/systemd/journald.conf

# back to Sonicprod's script

# Shell commands to switch between RO and RW modes
grep -q "alias ro=" /etc/bash.bashrc || sudo tee -a /etc/bash.bashrc << 'EOF'
set_bash_prompt() {
    fs_mode=$(mount | sed -n -e "s/^\/dev\/.* on \/ .*(\(r[w|o]\).*/\1/p")
    PS1='\[\033[01;32m\]\u@\h${fs_mode:+($fs_mode)}\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
    }

alias ro='sudo mount -o remount,ro / ; sudo mount -o remount,ro /boot'
alias rw='sudo mount -o remount,rw / ; sudo mount -o remount,rw /boot'

PROMPT_COMMAND=set_bash_prompt
EOF

# Disable swap and filesystem check and set fs to read-only
sudo sed -ie 's/^console=serial0,115200.*$/& fsck.mode=skip noswap ro/g' /boot/cmdline.txt

# /etc/fstab: Add read-only mode to /boot or /boot/firmware filesystems
sudo sed -i '/\/boot/{/ro/!s/\S\S*/&,ro/4}' /etc/fstab
# /etc/fstab: Add read-only mode to / root filesystem
sudo sed -i '/\S\s\s*\/\s\s*/{/\(ro,\|,ro\)/!s/\S\S*/&,ro/4}' /etc/fstab

# Disable auto-update daemons
sudo systemctl stop    systemd-tmpfiles-clean.timer apt-daily.timer apt-daily-upgrade.timer man-db.timer systemd-tmpfiles-clean.service apt-daily-upgrade.service
sudo systemctl disable systemd-tmpfiles-clean.timer apt-daily.timer apt-daily-upgrade.timer man-db.timer systemd-tmpfiles-clean.service apt-daily-upgrade.service
sudo systemctl mask    systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service apt-daily-upgrade.service

# Put file system back to read-only on log out
sudo sudo sh -c 'echo "sudo mount -o remount,ro /boot\nsudo mount -o remount,ro /" >> /etc/bash.bash_logout'

# Alias ajustments
sed -ie "s/^alias arcademode=.*$/alias arcademode=\'rw; sudo systemctl enable mame-autostart.service; ro\'/g" ~/.bash_aliases
sed -ie "s/^alias servicemode=.*$/alias servicemode=\'rw; sudo systemctl disable mame-autostart.service; ro\'/g" ~/.bash_aliases

# Systemd daemon reload to update changes
sudo systemctl daemon-reload

echo ---------------------------------------------------------------------
echo Completed.
echo The system will reboot NOW.
echo ---------------------------------------------------------------------

sudo reboot
chmod +x ~/scripts/read-only-rootfs.sh && ~/scripts/read-only-rootfs.sh

Two aliases (rw and ro) are created to switch from read-write to read-only mode and vice-versa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment