#!/bin/bash # https://github.com/mrchrisster/MiSTer_SAM/ # Copyright (c) 2021 by mrchrisster and Mellified # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. ## Description # This cycles through arcade and console cores periodically # Games are randomly pulled from their respective folders # ======== Credits ======== # Original concept and implementation: mrchrisster # Additional development and script layout: Mellified # # Thanks for the contributions and support: # pocomane, kaloun34, redsteakraw, RetroDriven, woelper, LamerDeluxe, InquisitiveCoder, Sigismond, venice #!/bin/bash export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/media/fat/linux:/media/fat/Scripts:/media/fat/Scripts/.MiSTer_SAM:. #======== INI VARIABLES ======== # Change these in the INI file #======== GLOBAL VARIABLES ========= declare -g mrsampath="/media/fat/Scripts/.MiSTer_SAM" declare -g misterpath="/media/fat" # Save our PID and process declare -g sampid="${$}" declare -g samprocess="$(basename -- ${0})" #======== DEBUG VARIABLES ======== samquiet="Yes" samdebug="No" samtrace="No" #======== LOCAL VARIABLES ======== declare -i coreretries=3 declare -i romloadfails=0 mralist="/tmp/.SAMmras" gametimer=120 corelist="arcade,fds,gba,genesis,megacd,neogeo,nes,snes,tgfx16,tgfx16cd,psx" skipmessage="Yes" usezip="Yes" loopall="Yes" disablebootrom="Yes" listenmouse="Yes" listenkeyboard="Yes" listenjoy="Yes" repository_url="https://github.com/mrchrisster/MiSTer_SAM" branch="main" counter=0 userstartup="/media/fat/linux/user-startup.sh" userstartuptpl="/media/fat/linux/_user-startup.sh" # ======== TTY2OLED ======= ttyenable="No" ttydevice="/dev/ttyUSB0" ttypicture="/media/fat/tty2oled/pics" ttypicture_pri="/media/fat/tty2oled/pics_pri" #======== CORE PATHS ======== arcadepath="/media/fat/_arcade" fdspath="/media/fat/games/NES" gbapath="/media/fat/games/GBA" genesispath="/media/fat/games/Genesis" megacdpath="/media/fat/games/MegaCD" neogeopath="/media/fat/games/NeoGeo" nespath="/media/fat/games/NES" snespath="/media/fat/games/SNES" tgfx16path="/media/fat/games/TGFX16" tgfx16cdpath="/media/fat/games/TGFX16-CD" psxpath="/media/fat/games/PSX" # ======== CONSOLE WHITELISTS ======== fdswhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_fds.txt" gbawhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_gba.txt" genesiswhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_genesis.txt" megacdwhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_megacd.txt" neogeowhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_neogeo.txt" neswhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_nes.txt" sneswhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_snes.txt" tgfx16whitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_tgfx16.txt" tgfx16cdwhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_tgfx16cd.txt" psxwhitelist="/media/fat/Scripts/MiSTer_SAM_whitelist_psx.txt" #======== EXCLUDE LISTS ======== arcadeexclude="First Bad Game.mra Second Bad Game.mra Third Bad Game.mra" fdsexclude="First Bad Game.gba Second Bad Game.gba Third Bad Game.gba" gbaexclude="First Bad Game.gba Second Bad Game.gba Third Bad Game.gba" genesisexclude="First Bad Game.gen Second Bad Game.gen Third Bad Game.gen" megacdexclude="First Bad Game.chd Second Bad Game.chd Third Bad Game.chd" neogeoexclude="First Bad Game.neo Second Bad Game.neo Third Bad Game.neo" nesexclude="First Bad Game.nes Second Bad Game.nes Third Bad Game.nes" snesexclude="First Bad Game.sfc Second Bad Game.sfc Third Bad Game.sfc" tgfx16exclude="First Bad Game.pce Second Bad Game.pce Third Bad Game.pce" tgfx16cdexclude="First Bad Game.chd Second Bad Game.chd Third Bad Game.chd" psxexclude="First Bad Game.chd Second Bad Game.chd Third Bad Game.chd" # ======== CORE CONFIG ======== function init_data() { # Core to long name mappings declare -gA CORE_PRETTY=( \ ["arcade"]="MiSTer Arcade" \ ["fds"]="Nintendo Disk System" \ ["gba"]="Nintendo Game Boy Advance" \ ["genesis"]="Sega Genesis / Megadrive" \ ["megacd"]="Sega CD / Mega CD" \ ["neogeo"]="SNK NeoGeo" \ ["nes"]="Nintendo Entertainment System" \ ["snes"]="Super Nintendo Entertainment System" \ ["tgfx16"]="NEC TurboGrafx-16 / PC Engine" \ ["tgfx16cd"]="NEC TurboGrafx-16 CD / PC Engine CD" \ ["psx"]="Sony Playstation" \ ) # Core to file extension mappings declare -gA CORE_EXT=( \ ["arcade"]="mra" \ ["fds"]="fds" \ ["gba"]="gba" \ ["genesis"]="md" \ ["megacd"]="chd" \ ["neogeo"]="neo" \ ["nes"]="nes" \ ["snes"]="sfc" \ ["tgfx16"]="pce" \ ["tgfx16cd"]="chd" \ ["psx"]="chd" \ ) # Core to path mappings declare -gA CORE_PATH=( \ ["arcade"]="${arcadepath}" \ ["fds"]="${fdspath}" \ ["gba"]="${gbapath}" \ ["genesis"]="${genesispath}" \ ["megacd"]="${megacdpath}" \ ["neogeo"]="${neogeopath}" \ ["nes"]="${nespath}" \ ["snes"]="${snespath}" \ ["tgfx16"]="${tgfx16path}" \ ["tgfx16cd"]="${tgfx16cdpath}" \ ["psx"]="${psxpath}" \ ) # Can this core use ZIPped ROMs declare -gA CORE_ZIPPED=( \ ["arcade"]="No" \ ["fds"]="Yes" \ ["gba"]="Yes" \ ["genesis"]="Yes" \ ["megacd"]="No" \ ["neogeo"]="Yes" \ ["nes"]="Yes" \ ["snes"]="Yes" \ ["tgfx16"]="Yes" \ ["tgfx16cd"]="No" \ ["psx"]="No" \ ) # Can this core skip Bios/Safety warning messages declare -gA CORE_SKIP=( \ ["arcade"]="No" \ ["fds"]="Yes" \ ["gba"]="No" \ ["genesis"]="No" \ ["megacd"]="Yes" \ ["neogeo"]="No" \ ["nes"]="No" \ ["snes"]="No" \ ["tgfx16"]="No" \ ["tgfx16cd"]="Yes" \ ["psx"]="No" \ ) # Core to folder mapping declare -gA CORE_LAUNCH=( \ ["arcade"]="arcade" \ ["fds"]="nes" \ ["gba"]="gba" \ ["genesis"]="genesis" \ ["megacd"]="megacd" \ ["neogeo"]="neogeo" \ ["nes"]="nes" \ ["snes"]="snes" \ ["tgfx16"]="tgfx16" \ ["tgfx16cd"]="tgfx16" \ ["psx"]="psx" \ ) # MGL core name settings declare -gA MGL_CORE=( \ ["arcade"]="arcade" \ ["fds"]="nes" \ ["gba"]="gba" \ ["genesis"]="genesis" \ ["megacd"]="megacd" \ ["neogeo"]="neogeo" \ ["nes"]="nes" \ ["snes"]="snes" \ ["tgfx16"]="turbografx16" \ ["tgfx16cd"]="turbografx16" \ ["psx"]="psx" \ ) # MGL delay settings declare -gA MGL_DELAY=( \ ["arcade"]="2" \ ["fds"]="2" \ ["gba"]="2" \ ["genesis"]="1" \ ["megacd"]="1" \ ["neogeo"]="1" \ ["nes"]="2" \ ["snes"]="2" \ ["tgfx16"]="1" \ ["tgfx16cd"]="1" \ ["psx"]="1" \ ) # MGL index settings declare -gA MGL_INDEX=( \ ["arcade"]="0" \ ["fds"]="0" \ ["gba"]="0" \ ["genesis"]="0" \ ["megacd"]="0" \ ["neogeo"]="1" \ ["nes"]="0" \ ["snes"]="0" \ ["tgfx16"]="0" \ ["tgfx16cd"]="0" \ ["psx"]="1" \ ) # MGL type settings declare -gA MGL_TYPE=( \ ["arcade"]="f" \ ["fds"]="f" \ ["gba"]="f" \ ["genesis"]="f" \ ["megacd"]="s" \ ["neogeo"]="f" \ ["nes"]="f" \ ["snes"]="f" \ ["tgfx16"]="f" \ ["tgfx16cd"]="s" \ ["psx"]="s" \ ) } #========= PARSE INI ========= # Read INI if [ -f "${misterpath}/Scripts/MiSTer_SAM.ini" ]; then source "${misterpath}/Scripts/MiSTer_SAM.ini" fi # Setup corelist corelist="$(echo ${corelist} | tr ',' ' ')" # Create array of coreexclude list names declare -a coreexcludelist for core in ${corelist}; do coreexcludelist+=( "${core}exclude" ) done # Iterate through coreexclude lists and make list into array for excludelist in ${coreexcludelist[@]}; do readarray -t ${excludelist} <<<${!excludelist} done # Create folder exclude list fldrex=$(for f in "${folderexclude[@]}"; do echo "-o -iname *$f*" ; done) # Create folder exclude list for zips fldrexzip=$(printf "%s," "${folderexclude[@]}" && echo "") # Remove trailing slash from paths for var in mrsampath misterpath mrapathvert mrapathhoriz arcadepath fdspath gbapath genesispath megacdpath neogeopath nespath snespath tgfx16path tgfx16cdpath psxpath; do declare -g ${var}="${!var%/}" done #======== SAM MENU ======== function sam_premenu() { echo "+---------------------------+" echo "| MiSTer Super Attract Mode |" echo "+---------------------------+" echo " SAM Configuration:" if [ $(grep -c "mistersam" ${userstartup}) = "0" ]; then echo " -SAM autoplay DISABLED" else echo " -SAM autoplay ENABLED" fi echo " -Start after ${samtimeout} sec. idle" echo " -Start only on the menu: ${menuonly^}" echo " -Show each game for ${gametimer} sec." echo "" echo " Press UP to open menu" echo " Press DOWN to start SAM" echo "" echo " Or wait for" echo " auto-configuration" echo "" for i in {5..1}; do echo -ne " Updating SAM in ${i}...\033[0K\r" premenu="Default" read -r -s -N 1 -t 1 key if [[ "${key}" == "A" ]]; then premenu="Menu" break elif [[ "${key}" == "B" ]]; then premenu="Start" break elif [[ "${key}" == "C" ]]; then premenu="Default" break fi done parse_cmd ${premenu} } function sam_menu() { dialog --clear --no-cancel --ascii-lines --no-tags \ --backtitle "Super Attract Mode" --title "[ Main Menu ]" \ --menu "Use the arrow keys and enter \nor the d-pad and A button" 0 0 0 \ Start "Start SAM now" \ Skip "Skip game" \ Stop "Stop SAM" \ Single "Games from only one core" \ Favorite "Favorite Game. Copy current game to _Favorites folder" \ Utility "Update and Monitor" \ Config "Configure INI Settings" \ Reset "Reset or uninstall SAM" \ Autoplay "Autoplay Configuration" \ Cancel "Exit now" 2>"/tmp/.SAMmenu" menuresponse=$(<"/tmp/.SAMmenu") clear if [ "${samquiet,,}" == "no" ]; then echo " menuresponse: ${menuresponse}"; fi parse_cmd ${menuresponse} } function sam_singlemenu() { declare -a menulist=() for core in ${corelist}; do menulist+=( "${core^^}" ) menulist+=( "${CORE_PRETTY[${core,,}]} games only" ) done dialog --clear --no-cancel --ascii-lines --no-tags \ --backtitle "Super Attract Mode" --title "[ Single System Select ]" \ --menu "Which system?" 0 0 0 \ "${menulist[@]}" \ Back 'Previous menu' 2>"/tmp/.SAMmenu" menuresponse=$(<"/tmp/.SAMmenu") clear if [ "${samquiet,,}" == "no" ]; then echo " menuresponse: ${menuresponse}"; fi parse_cmd ${menuresponse} } function sam_utilitymenu() { dialog --clear --no-cancel --ascii-lines --no-tags \ --backtitle "Super Attract Mode" --title "[ Utilities ]" \ --menu "Select an option" 0 0 0 \ Update "Update SAM to latest" \ Monitor "Display messages (ssh only)" \ Back 'Previous menu' 2>"/tmp/.SAMmenu" menuresponse=$(<"/tmp/.SAMmenu") clear if [ "${samquiet,,}" == "no" ]; then echo " menuresponse: ${menuresponse}"; fi parse_cmd ${menuresponse} } function sam_resetmenu() { dialog --clear --no-cancel --ascii-lines --no-tags \ --backtitle "Super Attract Mode" --title "[ Reset ]" \ --menu "Select an option" 0 0 0 \ Deleteall "Reset/Delete all files" \ Updateenable "Reinstall SAM. No Autoplay" \ Back 'Previous menu' 2>"/tmp/.SAMmenu" menuresponse=$(<"/tmp/.SAMmenu") clear if [ "${samquiet,,}" == "no" ]; then echo " menuresponse: ${menuresponse}"; fi parse_cmd ${menuresponse} } function sam_autoplaymenu() { dialog --clear --no-cancel --ascii-lines --no-tags \ --backtitle "Super Attract Mode" --title "[ Configure Autoplay ]" \ --menu "Select an option" 0 0 0 \ Enable "Enable Autoplay" \ Disable "Disable Autoplay" \ Back 'Previous menu' 2>"/tmp/.SAMmenu" menuresponse=$(<"/tmp/.SAMmenu") clear if [ "${samquiet,,}" == "no" ]; then echo " menuresponse: ${menuresponse}"; fi parse_cmd ${menuresponse} } function sam_configmenu() { dialog --clear --ascii-lines --no-cancel \ --backtitle "Super Attract Mode" --title "[ INI Settings ]" \ --msgbox "Here you can configure the INI settings for SAM.\n\nUse TAB to switch between editing, the OK and Cancel buttons." 0 0 dialog --clear --ascii-lines \ --backtitle "Super Attract Mode" --title "[ INI Settings ]" \ --editbox "${misterpath}/Scripts/MiSTer_SAM.ini" 0 0 2>"/tmp/.SAMmenu" if [ -s "/tmp/.SAMmenu" ] && [ "$(diff -wq "/tmp/.SAMmenu" "${misterpath}/Scripts/MiSTer_SAM.ini")" ]; then cp -f "/tmp/.SAMmenu" "${misterpath}/Scripts/MiSTer_SAM.ini" dialog --clear --ascii-lines --no-cancel \ --backtitle "Super Attract Mode" --title "[ INI Settings ]" \ --msgbox "Changes saved!" 0 0 fi parse_cmd menu } function parse_cmd() { if [ ${#} -gt 2 ]; then # We don't accept more than 2 parameters sam_help elif [ ${#} -eq 0 ]; then # No options - show the pre-menu sam_premenu else # If we're given a core name then we need to set it first nextcore="" for arg in ${@}; do case ${arg,,} in arcade | fds | gba | genesis | megacd | neogeo | nes | snes | tgfx16 | tgfx16cd | psx) echo " ${CORE_PRETTY[${arg,,}]} selected!" nextcore="${arg,,}" ;; esac done # If the one command was a core then we need to call in again with "start" specified if [ ${nextcore} ] && [ ${#} -eq 1 ]; then # Move cursor up a line to avoid duplicate message echo -n -e "\033[A" # Re-enter this function with start added parse_cmd ${nextcore} start return fi while [ ${#} -gt 0 ]; do case ${1,,} in default) # Default is split because sam_update relaunches itself sam_update defaultb break ;; defaultb) sam_update sam_enable quickstart sam_start break ;; softstart) # Start as from init env_check ${1,,} echo "Starting SAM in the background." tmux new-session -x 180 -y 40 -n "-= SAM Monitor -- Detach with ctrl-b d =-" -s SAM -d ${misterpath}/Scripts/MiSTer_SAM_on.sh softstart_real break ;; start) # Start as a detached tmux session for monitoring env_check ${1,,} # Terminate any other running SAM processes there_can_be_only_one echo "Starting SAM in the background." tmux new-session -x 180 -y 40 -n "-= SAM Monitor -- Detach with ctrl-b d =-" -s SAM -d ${misterpath}/Scripts/MiSTer_SAM_on.sh start_real ${nextcore} break ;; start_real) # Start SAM immediately env_check ${1,,} tty_init sam_start ${nextcore} break ;; softstart_real) # Start SAM immediately env_check ${1,,} tty_init counter=${samtimeout} sam_start ${nextcore} break ;; skip | next) # Load next game - stops monitor echo " Skipping to next game..." tmux send-keys -t SAM C-c ENTER #break ;; stop) # Stop SAM immediately there_can_be_only_one tty_exit echo " Thanks for playing!" break ;; update) # Update SAM sam_update break ;; updateenable) # Update SAM and enable Autostart sam_update sam_enable quickstart break ;; enable) # Enable SAM autoplay mode env_check ${1,,} sam_enable quickstart break ;; disable) # Disable SAM autoplay sam_disable break ;; monitor) # Warn user of changes sam_monitor_new break ;; arcade | fds | gba | genesis | megacd | neogeo | nes | snes | tgfx16 | tgfx16cd | psx) : # Placeholder since we parsed these above ;; single) sam_singlemenu break ;; utility) sam_utilitymenu break ;; autoplay) sam_autoplaymenu break ;; favorite) mglfavorite break ;; reset) sam_resetmenu break ;; config) sam_configmenu break ;; back) sam_menu break ;; menu) sam_menu break ;; cancel) # Exit echo " It's pitch dark; You are likely to be eaten by a Grue." break ;; deleteall) deleteall break ;; help) sam_help break ;; *) echo " ERROR! ${1} is unknown." echo " Try $(basename -- ${0}) help" echo " Or check the Github readme." break ;; esac shift done fi } #======== SAM COMMANDS ======== function sam_start() { # sam_start (core) # If the MCP isn't running we need to start it in monitoring only mode if [ -z "$(pidof MiSTer_SAM_MCP)" ]; then ${mrsampath}/MiSTer_SAM_MCP monitoronly & fi # Start SAM looping through cores and games loop_core ${1} } function sam_update() { # sam_update (next command) # Ensure the MiSTer SAM data directory exists mkdir --parents "${mrsampath}" &>/dev/null if [ ! "$(dirname -- ${0})" == "/tmp" ]; then # Warn if using non-default branch for updates if [ ! "${branch}" == "main" ]; then echo "" echo "*******************************" echo " Updating from ${branch}" echo "*******************************" echo "" fi # Download the newest MiSTer_SAM_on.sh to /tmp get_samstuff MiSTer_SAM_on.sh /tmp if [ -f /tmp/MiSTer_SAM_on.sh ]; then if [ ${1} ]; then echo " Continuing setup with latest" echo " MiSTer_SAM_on.sh..." /tmp/MiSTer_SAM_on.sh ${1} exit 0 else echo " Launching latest" echo " MiSTer_SAM_on.sh..." /tmp/MiSTer_SAM_on.sh update exit 0 fi else # /tmp/MiSTer_SAM_on.sh isn't there! echo " SAM update FAILED" echo " No Internet?" exit 1 fi else # We're running from /tmp - download dependencies and proceed cp --force "/tmp/MiSTer_SAM_on.sh" "/media/fat/Scripts/MiSTer_SAM_on.sh" get_partun get_mbc get_inputmap get_samstuff .MiSTer_SAM/MiSTer_SAM_init get_samstuff .MiSTer_SAM/MiSTer_SAM_MCP get_samstuff .MiSTer_SAM/MiSTer_SAM_joy.py get_samstuff .MiSTer_SAM/MiSTer_SAM_keyboard.sh get_samstuff .MiSTer_SAM/MiSTer_SAM_mouse.sh get_samstuff MiSTer_SAM_off.sh /media/fat/Scripts if [ -f /media/fat/Scripts/MiSTer_SAM.ini ]; then echo " MiSTer SAM INI already exists... SKIPPED!" else get_samstuff MiSTer_SAM.ini /media/fat/Scripts fi fi echo " Update complete!" return } function sam_enable() { # Enable autoplay echo -n " Enabling SAM Autoplay..." # Awaken daemon # Check for and delete old fashioned scripts to prefer /media/fat/linux/user-startup.sh # (https://misterfpga.org/viewtopic.php?p=32159#p32159) if [ -f /etc/init.d/S93mistersam ] || [ -f /etc/init.d/_S93mistersam ]; then mount | grep "on / .*[(,]ro[,$]" -q && RO_ROOT="true" [ "$RO_ROOT" == "true" ] && mount / -o remount,rw sync rm /etc/init.d/S93mistersam &>/dev/null rm /etc/init.d/_S93mistersam &>/dev/null sync [ "$RO_ROOT" == "true" ] && mount / -o remount,ro fi # Add new startup way if [ ! -e ${userstartup} ] && [ -e /etc/init.d/S99user ]; then if [ -e ${userstartuptpl} ]; then echo "Copying ${userstartuptpl} to ${userstartup}" cp ${userstartuptpl} ${userstartup} else echo "Building ${userstartup}" fi fi if [ $(grep -ic "mister_sam" ${userstartup}) = "0" ]; then echo -e "Add mistersam to ${userstartup}\n" echo -e "\n# Startup Super Attract Mode" >> ${userstartup} echo -e "[[ -e ${mrsampath}/MiSTer_SAM_init ]] && ${mrsampath}/MiSTer_SAM_init \$1" >> ${userstartup} fi echo -n " SAM autoplay daemon starting..." there_can_be_only_one if [ "${1,,}" == "quickstart" ]; then ${mrsampath}/MiSTer_SAM_init quickstart else ${mrsampath}/MiSTer_SAM_init start fi echo " Done!" return } function sam_disable() { # Disable autoplay echo -n " Disabling SAM autoplay..." # Clean out existing processes to ensure we can update there_can_be_only_one sed -i '/MiSTer_SAM/d' ${userstartup} sync echo " Done!" } function sam_help() { # sam_help echo " start - start immediately" echo " skip - skip to the next game" echo " stop - stop immediately" echo "" echo " update - self-update" echo " monitor - monitor SAM output" echo "" echo " enable - enable autoplay" echo " disable - disable autoplay" echo "" echo " menu - load to menu" echo "" echo " arcade, genesis, gba..." echo " games from one system only" exit 2 } #======== UTILITY FUNCTIONS ======== function there_can_be_only_one() { # there_can_be_only_one # If another attract process is running kill it # This can happen if the script is started multiple times echo -n " Stopping other running instances of ${samprocess}..." # -- SAM's {soft,}start_real tmux instance kill -9 $(ps -o pid,args | grep '[M]iSTer_SAM_on.sh start_real' | awk '{print $1}') &> /dev/null kill -9 $(ps -o pid,args | grep '[M]iSTer_SAM_on.sh softstart_real' | awk '{print $1}') &> /dev/null # -- Everything executable in mrsampath kill -9 $(ps -o pid,args | grep ${mrsampath} | grep -v grep | awk '{print $1}') &> /dev/null # -- inotifywait but only if it involves SAM kill -9 $(ps -o pid,args | grep '[i]notifywait.*SAM' | awk '{print $1}') &> /dev/null # -- hexdump since that's launched, no better way to see which ones to kill killall -9 hexdump &> /dev/null #wait $(pidof -o ${sampid} ${samprocess}) &>/dev/null # -- can't wait PID-wise which is admittedly better, but we know the processes requested will close if running # -- instead we sleep one second which seems more than fair. Alternatives, while loop, grep against ps -o args for SAM? sleep 1 echo " Done!" } function env_check() { # Check if we've been installed if [ ! -f "${mrsampath}/partun" ] || [ ! -f "${mrsampath}/MiSTer_SAM_MCP" ]; then echo " SAM required files not found." echo " Surprised? Check your INI." sam_update ${1} echo " Setup complete." fi } function deleteall() { # In case of issues, reset SAM if [ -d "${mrsampath}" ]; then echo "Deleting MiSTer_SAM folder" rm -rf "${mrsampath}" fi if [ -f "/media/fat/Scripts/MiSTer_SAM.ini" ]; then echo "Deleting MiSTer_SAM.ini" rm /media/fat/Scripts/MiSTer_SAM.ini fi if [ -f "/media/fat/Scripts/MiSTer_SAM_off.sh" ]; then echo "Deleting MiSTer_SAM_off.sh" rm /media/fat/Scripts/MiSTer_SAM_off.sh fi if ls /media/fat/Config/inputs/*_input_1234_5678_v3.map 1> /dev/null 2>&1; then echo "Deleting Keyboard mapping files" rm /media/fat/Config/inputs/*_input_1234_5678_v3.map fi # Remount root as read-write if read-only so we can remove daemon mount | grep "on / .*[(,]ro[,$]" -q && RO_ROOT="true" [ "$RO_ROOT" == "true" ] && mount / -o remount,rw # Delete daemon echo "Deleting Auto boot Daemon..." if [ -f /etc/init.d/S93mistersam ] || [ -f /etc/init.d/_S93mistersam ]; then mount | grep "on / .*[(,]ro[,$]" -q && RO_ROOT="true" [ "$RO_ROOT" == "true" ] && mount / -o remount,rw sync rm /etc/init.d/S93mistersam &>/dev/null rm /etc/init.d/_S93mistersam &>/dev/null sync [ "$RO_ROOT" == "true" ] && mount / -o remount,ro fi echo "Done." sed -i '/MiSTer_SAM/d' ${userstartup} sed -i '/Super Attract/d' ${userstartup} printf "\n\n\n\n\n\nAll files deleted except for MiSTer_SAM_on.sh\n\n\n\n\n\n" for i in {5..1}; do echo -ne "Returning to menu in ${i}...\033[0K\r" sleep 1 done sam_resetmenu } function skipmessage() { #Skip past bios/safety warnings sleep 3 && "${mrsampath}"/mbc raw_seq {31!s}31 } function mglfavorite() { #Add current game to _Favorites folder if [ ! -d "${misterpath}"/_Favorites ]; then mkdir "${misterpath}"/_Favorites fi cp /tmp/SAM_game.mgl "${misterpath}"/_Favorites/"$(cat /tmp/SAM_Game.txt)".mgl } function tty_waitforack() { #echo -n "Waiting for tty2oled Acknowledge... " read -d ";" ttyresponse < ${ttydevice} # The "read" command at this position simulates an "do..while" loop while [ "${ttyresponse}" != "ttyack" ]; do read -d ";" ttyresponse < ${ttydevice} # Read Serial Line until delimiter ";" done #echo -e "${fgreen}${ttyresponse}${freset}" ttyresponse="" } # USB Send-Picture-Data function function tty_senddata() { newcore="${1}" unset picfnam if [ -e "${ttypicture_pri}/${newcore}.gsc" ]; then # Check for _pri pictures picfnam="${ttypicture_pri}/${newcore}.gsc" elif [ -e "${ttypicture_pri}/${newcore}.xbm" ]; then picfnam="${ttypicture_pri}/${newcore}.xbm" else picfolders="gsc_us xbm_us gsc xbm xbm_text" # If no _pri picture found, try all the others [ "${USE_US_PICTURE}" = "no" ] && picfolders="${picfolders//gsc_us xbm_us/}" [ "${USE_GSC_PICTURE}" = "no" ] && picfolders="${picfolders//gsc_us/}" && picfolders="${picfolders//gsc/}" [ "${USE_TEXT_PICTURE}" = "no" ] && picfolders="${picfolders//xbm_text/}" for picfolder in ${picfolders}; do for (( c="${#newcore}"; c>=1; c-- )); do # Manipulate string... picfnam="${ttypicture}/${picfolder^^}/${newcore:0:$c}.${picfolder:0:3}" # ...until it matches something [ -e "${picfnam}" ] && break done [ -e "${picfnam}" ] && break done fi if [ -e "${picfnam}" ]; then # Exist? # For testing... if [ "${samdebug,,}" == "yes" ]; then echo "-------------------------------------------" echo " tty2oled sending Corename: ${1} " echo " tty2oled found/send Picture : ${picfnam} " echo "-------------------------------------------" fi echo "CMDCOR,${1}" > ${ttydevice} # Send CORECHANGE" Command and Corename sleep 0.02 # sleep needed here ?! tail -n +4 "${picfnam}" | xxd -r -p > ${ttydevice} # The Magic, send the Picture-Data up from Line 4 and proces else # No Picture available! echo "${1}" > ${ttydevice} # Send just the CORENAME fi # End if Picture check } function tty_exit() { # tty_exit if [ "${ttyenable,,}" == "yes" ]; then # Clear Display with Random effect echo "CMDCLST,-1,0" > "${ttydevice}" tty_waitforack sleep 1 # Show GAME OVER! for 3 secs echo "CMDTXT,5,15,0,15,45,GAME OVER!" > "${ttydevice}" tty_waitforack sleep 3 # Set CORENAME for tty2oled Daemon start echo "MENU" > /tmp/CORENAME # Starting tty2oled daemon echo " Starting tty2oled daemon..." /media/fat/tty2oled/S60tty2oled start echo " Done!" #sleep 2 fi } function tty_init() { # tty_init # tty2oled initialization if [ "${ttyenable,,}" == "yes" ]; then echo " Init tty2oled, loading variables... " source ${ttysystemini} source ${ttyuserini} ttydevice=${TTYDEV} ttypicture=${picturefolder} ttypicture_pri=${picturefolder_pri} # Clear Serial input buffer first echo " Clear tty2oled Serial Input Buffer " while read -t 0 sdummy < ${ttydevice}; do continue; done echo " Done!" #sleep 2 # Stopping ScreenSaver echo " Stopping tty2oled ScreenSaver..." echo "CMDSAVER,0,0,0" > "${ttydevice}" tty_waitforack echo " Done!" #sleep 2 # Stopping tty2oled Daemon echo " Stopping tty2oled Daemon..." /media/fat/tty2oled/S60tty2oled stop echo " Done!" #sleep 2 # Small loop for Welcome... for l in {1..4}; do echo "CMDCLS" > "${ttydevice}" tty_waitforack sleep 0.2 echo "CMDTXT,1,15,0,0,9, Welcome to..." > "${ttydevice}" tty_waitforack sleep 0.2 done sleep 2 echo "CMDTXT,3,15,0,47,27, Super" > "${ttydevice}" tty_waitforack sleep 0.8 echo "CMDTXT,3,15,0,97,45, Attract" > "${ttydevice}" tty_waitforack sleep 0.8 echo "CMDTXT,3,15,0,153,63, Mode!" > "${ttydevice}" tty_waitforack sleep 1 fi } function tty_update() { # tty_update core game if [ "${ttyenable,,}" == "yes" ]; then # Wait for tty2oled daemon to show the core logo #inotifywait -e modify /tmp/CORENAME # Wait for tty2oled to show the core logo if [ "${samdebug,,}" == "yes" ]; then echo "-------------------------------------------" echo " tty_update got Corename: ${3} " fi tty_senddata "${3}" tty_waitforack # Show Core-Logo for 7 Secs sleep 7 # Clear Display with Random effect echo "CMDCLST,-1,0" > "${ttydevice}" tty_waitforack #sleep 0.5 # Split long lines - length is approximate since fonts are variable width! if [ ${#2} -gt 23 ]; then for l in {1..15}; do echo "CMDTXT,103,${l},0,0,20,${2:0:20}..." > "${ttydevice}" tty_waitforack echo "CMDTXT,103,${l},0,0,40, ${2:20}" > "${ttydevice}" tty_waitforack echo "CMDTXT,2,$(( ${l}/3 )),0,0,60,${1}" > "${ttydevice}" tty_waitforack sleep 0.1 done else for l in {1..15}; do echo "CMDTXT,103,${l},0,0,20,${2}" > "${ttydevice}" tty_waitforack echo "CMDTXT,2,$(( ${l}/3 )),0,0,60,${1}" > "${ttydevice}" tty_waitforack sleep 0.1 done fi fi } #======== DOWNLOAD FUNCTIONS ======== function curl_download() { # curl_download ${filepath} ${URL} curl \ --connect-timeout 15 --max-time 600 --retry 3 --retry-delay 5 --silent --show-error \ --insecure \ --fail \ --location \ -o "${1}" \ "${2}" } #======== UPDATER FUNCTIONS ======== function get_samstuff() { #get_samstuff file (path) if [ -z "${1}" ]; then return 1 fi filepath="${2}" if [ -z "${filepath}" ]; then filepath="${mrsampath}" fi echo -n " Downloading from ${repository_url}/blob/${branch}/${1} to ${filepath}/..." curl_download "/tmp/${1##*/}" "${repository_url}/blob/${branch}/${1}?raw=true" if [ ! "${filepath}" == "/tmp" ]; then mv --force "/tmp/${1##*/}" "${filepath}/${1##*/}" fi if [ "${1##*.}" == "sh" ]; then chmod +x "${filepath}/${1##*/}" fi echo " Done!" } function get_partun() { REPOSITORY_URL="https://github.com/woelper/partun" echo " Downloading partun - needed for unzipping roms from big archives..." echo " Created for MiSTer by woelper - Talk to him at this year's PartunCon" echo " ${REPOSITORY_URL}" latest=$(curl -s -L --insecure https://api.github.com/repos/woelper/partun/releases/latest | jq -r ".assets[] | select(.name | contains(\"armv7\")) | .browser_download_url") curl_download "/tmp/partun" "${latest}" mv --force "/tmp/partun" "${mrsampath}/partun" echo " Done!" } function get_mbc() { echo " Downloading mbc - Control MiSTer from cmd..." echo " Created for MiSTer by pocomane" get_samstuff .MiSTer_SAM/mbc } function get_inputmap() { #schlampig echo " Downloading input maps - needed to skip past BIOS for some systems..." for i in "${CORE_LAUNCH[@]}"; do if [ ! -f /media/fat/Config/inputs/"${CORE_LAUNCH[$i]}"_input_1234_5678_v3.map ]; then curl_download "/tmp/${CORE_LAUNCH[$i]^^}_input_1234_5678_v3.map" "${repository_url}/blob/${branch}/.MiSTer_SAM/inputs/${CORE_LAUNCH[$i]^^}_input_1234_5678_v3.map?raw=true" &>/dev/null mv --force "/tmp/${CORE_LAUNCH[$i]^^}_input_1234_5678_v3.map" "/media/fat/Config/inputs/${CORE_LAUNCH[$i]^^}_input_1234_5678_v3.map" &>/dev/null fi done echo " Done!" } #========= SAM MONITOR ========= function sam_monitor_new() { # We can omit -r here. Tradeoff; # window size size is correct, can disconnect with ctrl-C but ctrl-C kills MCP #tmux attach-session -t SAM # window size will be wrong/too small, but ctrl-c nonfunctional instead of killing/disconnecting tmux attach-session -r -t SAM } # ======== SAM OPERATIONAL FUNCTIONS ======== function loop_core() { # loop_core (core) echo -e " Starting Super Attract Mode...\n Let Mortal Kombat begin!\n" # Reset game log for this session echo "" |> /tmp/SAM_Games.log while :; do trap break INT #Break out of loop for skip & next command while [ ${counter} -gt 0 ]; do echo -ne " Next game in ${counter}...\033[0K\r" sleep 1 ((counter--)) if [ -s /tmp/.SAM_Mouse_Activity ]; then if [ "${listenmouse,,}" == "yes" ]; then echo " Mouse activity detected!" exit else echo " Mouse activity ignored!" echo "" |>/tmp/.SAM_Mouse_Activity fi fi if [ -s /tmp/.SAM_Keyboard_Activity ]; then if [ "${listenkeyboard,,}" == "yes" ]; then echo " Keyboard activity detected!" exit else echo " Keyboard activity ignored!" echo "" |>/tmp/.SAM_Keyboard_Activity fi fi if [ -s /tmp/.SAM_Joy_Activity ]; then if [ "${listenjoy,,}" == "yes" ]; then echo " Controller activity detected!" exit else echo " Controller activity ignored!" echo "" |>/tmp/.SAM_Joy_Activity fi fi done counter=${gametimer} next_core ${1} done trap - INT sleep 1 } function next_core() { # next_core (core) if [ -z "${corelist[@]//[[:blank:]]/}" ]; then echo " ERROR: FATAL - List of cores is empty. Nothing to do!" exit 1 fi if [ -z "${1}" ]; then nextcore="$(echo ${corelist}| xargs shuf --head-count=1 --random-source=/dev/urandom --echo)" elif [ "${1,,}" == "countdown" ] && [ "$2" ]; then countdown="countdown" nextcore="${2}" elif [ "${2,,}" == "countdown" ]; then nextcore="${1}" countdown="countdown" fi if [ "${nextcore,,}" == "arcade" ]; then # If this is an arcade core we go to special code load_core_arcade return fi # Mister SAM tries to determine how the user has set up their rom collection. There are 4 possible cases: # 1. Roms are all unzipped # 2. Roms are in one big zip archive - like Everdrive # 3. Roms are zipped individually # 4. There are some zipped roms and some unzipped roms in the same dir # Some cores don't use zips - get on with it #Setting up file lists mkdir -p /tmp/.SAMcount mkdir -p /tmp/.SAMlist function use_roms() { #Create list if [ ! -f /tmp/.SAMlist/${nextcore}_romlist ]; then romlist=$(find "${CORE_PATH[${nextcore,,}]}" -type d \( -iname *BIOS* ${fldrex} \) -not -path '*/.*' -prune -false -o -type f -iname "*.${CORE_EXT[${nextcore,,}]}" > /tmp/.SAMlist/${nextcore}_romlist) fi #Delete played game from list if [ -s /tmp/.SAMlist/${nextcore}_romlist ]; then rompath="$(cat /tmp/.SAMlist/${nextcore}_romlist | shuf --head-count=1 --random-source=/dev/urandom)" if [ "${loopall,,}" == "yes" ]; then sed -i "/${rompath//\//\\/}/d" /tmp/.SAMlist/${nextcore}_romlist fi else romlist=$(find "${CORE_PATH[${nextcore,,}]}" -type d \( -iname *BIOS* ${fldrex} \) -not -path '*/.*' -prune -false -o -type f -iname "*.${CORE_EXT[${nextcore,,}]}" > /tmp/.SAMlist/${nextcore}_romlist) fi romname=$(basename "${rompath}") } if [ "${CORE_ZIPPED[${nextcore,,}],,}" == "no" ]; then if [ "${samquiet,,}" == "no" ]; then echo " ${nextcore^^} does not use ZIPs."; fi use_roms # We might be using ZIPs else ########## Check how many ZIP and ROM files in core path (Case 4) if [ ! -f /tmp/.SAMcount/${nextcore}_zipcount ]; then zipcount=$(find "${CORE_PATH[${nextcore,,}]}" -type f -iname "*.zip" -print | wc -l) echo ${zipcount} > /tmp/.SAMcount/${nextcore}_zipcount else zipcount=$(cat /tmp/.SAMcount/${nextcore}_zipcount) fi if [ ! -f /tmp/.SAMcount/${nextcore}_romcount ]; then romcount=$(find "${CORE_PATH[${nextcore,,}]}" -type d \( -iname *BIOS* ${fldrex} \) -prune -false -o -type f -iname "*.${CORE_EXT[${nextcore,,}]}" -print | wc -l) echo ${romcount} > /tmp/.SAMcount/${nextcore}_romcount else romcount=$(cat /tmp/.SAMcount/${nextcore}_romcount) fi #How many roms and zips did we find if [ "${samquiet,,}" == "no" ]; then echo " Found ${zipcount} zip files in ${CORE_PATH[${nextcore,,}]}."; fi if [ "${samquiet,,}" == "no" ]; then echo " Found ${romcount} ${CORE_EXT[${nextcore,,}]} files in ${CORE_PATH[${nextcore,,}]}."; fi #Compare roms vs zips if [ "${zipcount}" -gt 0 ] && [ "${romcount}" -gt 0 ] && [ "${usezip,,}" == "yes" ]; then ############ Zip to Rom Compare completed ############# # We've found ZIPs AND ROMs AND we're using zips #if [ "${samquiet,,}" == "no" ]; then echo " Both ROMs and ZIPs found!"; fi # We found at least one large ZIP file - use it (Case 2) #if [ $(find "${CORE_PATH[${nextcore,,}]}" -xdev -type f -size +500M \( -iname "*.zip" \) -print | wc -l) -gt 0 ]; then # if [ "${samquiet,,}" == "no" ]; then echo " Using 500MB+ ZIP(s)."; fi # romfind=$(find "${CORE_PATH[${nextcore,,}]}" -xdev -size +500M -type f -iname "*.zip" | shuf --head-count=1 --random-source=/dev/urandom) # if [ ! -f /tmp/.SAMlist/${nextcore}_romlist ]; then # "${mrsampath}/partun" "${romfind}" -l -e ${fldrexzip::-1} -f ${CORE_EXT[${nextcore,,}]} > /tmp/.SAMlist/${nextcore}_romlist # fi # rompath="${romfind}/$(cat /tmp/.SAMlist/${nextcore}_romlist | shuf --head-count=1 --random-source=/dev/urandom)" # romname=$(basename "${rompath}") # We found at least one large ZIP file - use it (Case 2) if [ $(find "${CORE_PATH[${nextcore,,}]}" -maxdepth 1 -xdev -type f -size +500M \( -iname "*.zip" \) -print | wc -l) -gt 0 ]; then if [ "${samquiet,,}" == "no" ]; then echo " Using 500MB+ ZIP(s)."; fi romfind=$(find "${CORE_PATH[${nextcore,,}]}" -xdev -maxdepth 1 -size +500M -type f -iname "*.zip" | shuf --head-count=1 --random-source=/dev/urandom) rompath="${romfind}/$("${mrsampath}/partun" "${romfind}" -l -r -e ${fldrexzip::-1} -f ${CORE_EXT[${nextcore,,}]})" romname=$(basename "${rompath}") # We see more zip files than ROMs, we're probably dealing with individually zipped roms (Case 3) elif [ ${zipcount} -gt ${romcount} ]; then if [ "${samquiet,,}" == "no" ]; then echo " Fewer ROMs - using ZIPs."; fi romfind=$(find "${CORE_PATH[${nextcore,,}]}" -type f -iname "*.zip" | shuf --head-count=1 --random-source=/dev/urandom) rompath="${romfind}/$("${mrsampath}/partun" "${romfind}" -l -r -e ${fldrexzip::-1} -f ${CORE_EXT[${nextcore,,}]})" romname=$(basename "${rompath}") # I guess we use the ROMs! (Case 1) else if [ "${samquiet,,}" == "no" ]; then echo " Using ROMs."; fi use_roms fi # Found no ZIPs or we're ignoring them elif [ $zipcount = 0 ] || [ "${usezip,,}" == "no" ]; then if [ "${samquiet,,}" == "no" ]; then echo " Found no zips or ignoring them."; fi use_roms # Use the ZIP Luke! else if [ "${samquiet,,}" == "no" ]; then echo " Using zip"; fi romfind=$(find "${CORE_PATH[${nextcore,,}]}" -xdev -type f -iname "*.zip" | shuf --head-count=1 --random-source=/dev/urandom) rompath="${romfind}/$("${mrsampath}/partun" "${romfind}" -l -r -e ${fldrexzip::-1} -f ${CORE_EXT[${nextcore,,}]})" romname=$(basename "${rompath}") fi fi # Sanity check that we have a valid rom in var if [[ ${rompath} != *"${CORE_EXT[${nextcore,,}]}"* ]]; then next_core return fi # If there is a whitelist check it declare -n whitelist="${nextcore,,}list" # Possible exit statuses: # 0: found # 1: not found # 2: error (e.g. file not found) if [ $(grep -Fqsx "${romname}" "${whitelist}"; echo "$?") -eq 1 ]; then echo " ${romname} is not in ${whitelist} - SKIPPED" next_core return fi # If there is an exclude list check it declare -n excludelist="${nextcore,,}exclude" if [ ${#excludelist[@]} -gt 0 ]; then for excluded in "${excludelist[@]}"; do if [ "${romname}" == "${excluded}" ]; then echo " ${romname} is excluded - SKIPPED" next_core return fi done fi if [ -z "${rompath}" ]; then core_error "${nextcore}" "${rompath}" else if [ -f "${rompath}.sam" ]; then source "${rompath}.sam" fi declare -g romloadfails=0 load_core "${nextcore}" "${rompath}" "${romname%.*}" "${countdown}" fi } function load_core() { # load_core core /path/to/rom name_of_rom (countdown) echo -n " Starting now on the " echo -ne "\e[4m${CORE_PRETTY[${1,,}]}\e[0m: " echo -e "\e[1m${3}\e[0m" echo "$(date +%H:%M:%S) - ${1} - ${3}" >> /tmp/SAM_Games.log echo "${3} (${1})" > /tmp/SAM_Game.txt tty_update "${CORE_PRETTY[${1,,}]}" "${3}" "${CORE_LAUNCH[${1,,}]}" & # Non blocking Version #tty_update "${CORE_PRETTY[${1,,}]}" "${3}" "${CORE_LAUNCH[${1,,}]}" # Blocking Version if [ "${4}" == "countdown" ]; then for i in {5..1}; do echo -ne " Loading game in ${i}...\033[0K\r" sleep 1 done fi #Create mgl file and launch game echo "" > /tmp/SAM_game.mgl echo "_console/${MGL_CORE[${nextcore}]}" >> /tmp/SAM_game.mgl echo "" >> /tmp/SAM_game.mgl echo "" >> /tmp/SAM_game.mgl echo "load_core /tmp/SAM_game.mgl" > /dev/MiSTer_cmd sleep 1 echo "" |>/tmp/.SAM_Joy_Activity echo "" |>/tmp/.SAM_Mouse_Activity echo "" |>/tmp/.SAM_Keyboard_Activity if [ "${skipmessage,,}" == "yes" ] && [ "${CORE_SKIP[${nextcore,,}],,}" == "yes" ]; then skipmessage fi } function core_error() { # core_error core /path/to/ROM if [ ${romloadfails} -lt ${coreretries} ]; then declare -g romloadfails=$((romloadfails+1)) echo " ERROR: Failed ${romloadfails} times. No valid game found for core: ${1} rom: ${2}" echo " Trying to find another rom..." next_core ${1} else echo " ERROR: Failed ${romloadfails} times. No valid game found for core: ${1} rom: ${2}" echo " ERROR: Core ${1} is blacklisted!" declare -g corelist=("${corelist[@]/${1}}") echo " List of cores is now: ${corelist[@]}" declare -g romloadfails=0 next_core fi } function disable_bootrom() { if [ "${disablebootrom}" == "Yes" ]; then if [ -d "${misterpath}/Bootrom" ]; then mount --bind /mnt "${misterpath}/Bootrom" fi if [ -f "${misterpath}/Games/NES/boot0.rom" ]; then touch /tmp/brfake mount --bind /tmp/brfake ${misterpath}/Games/NES/boot0.rom fi if [ -f "${misterpath}/Games/NES/boot1.rom" ]; then touch /tmp/brfake mount --bind /tmp/brfake ${misterpath}/Games/NES/boot1.rom fi fi } # ======== ARCADE MODE ======== function build_mralist() { # If no MRAs found - suicide! find "${arcadepath}" -type f \( -iname "*.mra" \) &>/dev/null if [ ! ${?} == 0 ]; then echo " The path ${arcadepath} contains no MRA files!" loop_core fi # Check if the MRA list already exists - if so, leave it alone if [ -f ${mralist} ]; then return fi # This prints the list of MRA files in a path, # Cuts the string to just the file name, # Then saves it to the mralist file. # If there is an empty exclude list ignore it # Otherwise use it to filter the list if [ ${#arcadeexclude[@]} -eq 0 ]; then find "${arcadepath}" -type f \( -iname "*.mra" \) | cut -c $(( $(echo ${#arcadepath}) + 2 ))- >"${mralist}" else find "${arcadepath}" -type f \( -iname "*.mra" \) | cut -c $(( $(echo ${#arcadepath}) + 2 ))- | grep -vFf <(printf '%s\n' ${arcadeexclude[@]})>"${mralist}" fi } function load_core_arcade() { # Get a random game from the list mra="$(shuf --head-count=1 --random-source=/dev/urandom ${mralist})" # If the mra variable is valid this is skipped, but if not we try 10 times # Partially protects against typos from manual editing and strange character parsing problems for i in {1..10}; do if [ ! -f "${arcadepath}/${mra}" ]; then mra=$(shuf --head-count=1 --random-source=/dev/urandom ${mralist}) fi done # If the MRA is still not valid something is wrong - suicide if [ ! -f "${arcadepath}/${mra}" ]; then echo " There is no valid file at ${arcadepath}/${mra}!" return fi mraname="$(echo "$(basename "${mra}")" | sed -e 's/\.[^.]*$//')" echo -n " Starting now on the " echo -ne "\e[4m${CORE_PRETTY[${nextcore,,}]}\e[0m: " echo -e "\e[1m${mraname}\e[0m" echo "$(date +%H:%M:%S) - Arcade - ${mraname}" >> /tmp/SAM_Games.log echo "${mraname} (${nextcore})" > /tmp/SAM_Game.txt # Get Setname from MRA needed for tty2oled, thx to RealLarry mrasetname=$(grep "" "${arcadepath}/${mra}" | sed -e 's///' -e 's/<\/setname>//' | tr -cd '[:alnum:]') #tty_update "${CORE_PRETTY[${nextcore,,}]}" "${mraname}" "${mrasetname}" & # Non-Blocking tty_update "${CORE_PRETTY[${nextcore,,}]}" "${mraname}" "${mrasetname}" # Blocking if [ "${1}" == "countdown" ]; then for i in {5..1}; do echo " Loading game in ${i}...\033[0K\r" sleep 1 done fi # Tell MiSTer to load the next MRA echo "load_core ${arcadepath}/${mra}" > /dev/MiSTer_cmd sleep 1 echo "" |>/tmp/.SAM_Joy_Activity echo "" |>/tmp/.SAM_Mouse_Activity echo "" |>/tmp/.SAM_Keyboard_Activity } #========= MAIN ========= #======== DEBUG OUTPUT ========= if [ "${samtrace,,}" == "yes" ]; then echo " ********************************************************************************" #======== GLOBAL VARIABLES ========= echo " mrsampath: ${mrsampath}" echo " misterpath: ${misterpath}" echo " sampid: ${sampid}" echo " samprocess: ${samprocess}" echo "" #======== LOCAL VARIABLES ======== echo " commandline: ${@}" echo " repository_url: ${repository_url}" echo " branch: ${branch}" echo "" echo " gametimer: ${gametimer}" echo " corelist: ${corelist}" echo " usezip: ${usezip}" echo " mralist: ${mralist}" echo " listenmouse: ${listenmouse}" echo " listenkeyboard: ${listenkeyboard}" echo " listenjoy: ${listenjoy}" echo "" echo " arcadepath: ${arcadepath}" echo " gbapath: ${gbapath}" echo " genesispath: ${genesispath}" echo " megacdpath: ${megacdpath}" echo " neogeopath: ${neogeopath}" echo " nespath: ${nespath}" echo " snespath: ${snespath}" echo " tgfx16path: ${tgfx16path}" echo " tgfx16cdpath: ${tgfx16cdpath}" echo "" echo " gbalist: ${gbalist}" echo " genesislist: ${genesislist}" echo " megacdlist: ${megacdlist}" echo " neogeolist: ${neogeolist}" echo " neslist: ${neslist}" echo " sneslist: ${sneslist}" echo " tgfx16list: ${tgfx16list}" echo " tgfx16cdlist: ${tgfx16cdlist}" echo "" echo " arcadeexclude: ${arcadeexclude[@]}" echo " gbaexclude: ${gbaexclude[@]}" echo " genesisexclude: ${genesisexclude[@]}" echo " megacdexclude: ${megacdexclude[@]}" echo " neogeoexclude: ${neogeoexclude[@]}" echo " nesexclude: ${nesexclude[@]}" echo " snesexclude: ${snesexclude[@]}" echo " tgfx16exclude: ${tgfx16exclude[@]}" echo " tgfx16cdexclude: ${tgfx16cdexclude[@]}" echo " ********************************************************************************" read -p " Continuing in 5 seconds or press any key..." -n 1 -t 5 -r -s fi disable_bootrom # Disable Bootrom until Reboot build_mralist # Generate list of MRAs init_data # Setup data arrays parse_cmd ${@} # Parse command line parameters for input exit