I banged my head over this for some time before coming up with a solution.
The problem is that when you hit that power button, an interrupt is sent to the login daemon, telling it to shutdown the system. You can't interrupt that in any way, or replace it with a different operation as far as I can see.
You can create a service with killall -s SIGTERM retroarch in its shutdown operation - but by then it's too late. Systemctl has already killed all user processes - including retroarch.
So what I had to do was actually run retroarch as a service itself. That was systemctl left it for its own shutdown procedure. I'm fairly new services, so I don't claim this is the perfect solution but it works for me.
This is based on "retropie" on a Raspberry Pi. If you're running some other Linux system it may or may not be helpful.
First, we need to prevent retroarch from starting in the normal way. I edited /opt/retropie/configs/all/autostart.sh, commenting out the existing lines and adding in a call to the shell bash:
bash
#/opt/retropie/supplementary/runcommand/runcommand.sh 0 _SYS_ mame-libretro ~/RetroPie/roms/mame-libretro/robotron.zip
#emulationstation #auto
Next I created a new service by adding a file /etc/systemd/system/killmame.service containing
[Unit]
Description=A service start and stop MAME cleanly
After=console-setup network-online.target multi-user.target
[Service]
User=pi
ExecStart=/home/pi/bin/start.sh
ExecStop=/home/pi/bin/killmame.sh
Type=simple
StandardInput=tty-force
TTYVHangup=yes
TTYPath=/dev/tty20
TTYReset=yes
SendSIGKILL=no
KillMode=none
[Install]
WantedBy=multi-user.target
Notes:
You'll need to create that as root (eg. sudo nano killmame.service)
killmame is a bad name for the service. It started off only intending to kill it, but morphed into a complete service - retroarch.service might be a better name
All the tty stuff is needed or retroarch won't run
The two scripts in ExecStart and ExecStop need to be created in the next step
Now we create a directory /home/pi/bin and create the two scripts in the (can do this as the pi user):
start.sh:
#!/bin/bash
cd /home/pi
export TERM=linux
export USER=pi
chvt 20
/opt/retropie/supplementary/runcommand/runcommand.sh 0 _SYS_ mame-libretro ~/RetroPie/roms/mame-libretro/robotron.zip &
bash
The command starting /opt/retropie is what I previously had in my autostart.sh. Obviously it's specific to my game, you will need to change it to whatever you had in your autostart.sh file.
You MUST have the & character at the end, putting the command into background. Otherwise systemctl will kill it in the early steps of shutdown. I'm not sure why.
killmame.sh:
#!/bin/bash
# echo checking for mame running ... > /home/pi/bin/killmame.log
# ps -deafww | grep libretro | grep robotron >> /home/pi/bin/killmame.log
killall -s SIGTERM retroarch
echo shutting down MAME at `date` >> /home/pi/bin/killmame.log
The commented-out lines 'echo' and 'ps' were for debugging - to see if my game was actually running when it came to being shut down.
Finally we need to register and enable our new service:
sudo systemctl daemon-reload
sudo systemctl start killmame
sudo systemctl enable killmame
All those should complete without output. If the 'start' fails, you'll need to figure out why before continuing.
If they all work, you can reboot. Your machine should first drop into a shell prompt, but then very soon after retroarch should take over. Hitting the kill switch should now shutdown retroarch cleanly, saving high scores.
The only glitch I've found is that if you use <ESCAPE> to exit retroarch, you'll find lots of key presses on the shell window from your game controls. Usually they're harmless but it's just possible you might accidently issue some fatal commands. Probably removing the invocation of bash from autostart.sh woud solve that, but you might then not be able to get back into a shell after exiting with escape. I generally log in remotely using ssh, so that wouldn't worry me.
I hope this helps someone as I spent hours trawling for a solution without finding one. It was only while explaining to my wife why it was impossible, that it occurred to me to actually run retroarch as a service itself.
If someone is able to tidy this up into a cleaner solution I'd be very grateful.