#!/bin/bash
# GLOBAL VARS
debug=1
testing=0
action='DUMP'
mode='RUNCMD'
retro_running=0
title_found=0
game_ready=0
game_listed=0
identified=0
credits=0
last_credits=0
coins_count=0
start_count=0
scanning=0
scan_level=1
coin_key="KEY_C"
start_key="KEY_S"
system='mame-libretro'
game=''
game_title=''
coin_addr=''
credits_str=''
title_str='GAMEINFO:'
loaded_arr=('SET_GEOMETRY' 'hiscore memory file')
declare -A data=()
declare -a regions=()
#___________________________________________________
#
# MAIN
Main() {
debug "Main():\n"
debug "MODE:\t$mode\n\tACTION:\t$action\n\n"
if [[ "$action" == DUMP ]]; then
if [[ "$mode" == RUNCMD ]]; then
cafca_dump
elif [[ "$mode" == LAUNCH ]]; then
# launch retroarch
sleep 1
fi
elif [[ "$action" == SCAN ]]; then
if [[ "$mode" == RUNCMD ]]; then
cafca_scan
elif [[ "$mode" == LAUNCH ]]; then
if [ "${#args[@]}" -gt 2 ]; then
game=$(echo "$4" | tr '[:upper:]' '[:lower:]')
elif [ "${#args[@]}" -gt 3 ]; then
system=$(echo "$3" | tr '[:upper:]' '[:lower:]')
game=$(echo "$4" | tr '[:upper:]' '[:lower:]')
fi
#cafca_scan
fi
fi
}
#___________________________________________________
#
# FUNCTIONS
cafca_scan() {
retro_running=1
loaded_str="${loaded_arr[1]}"
serialSend "GAME_LOADED 1"
sleep 1
while (( ! $game_ready )); do
game_ready=$(grep -Ec -m1 "$loaded_str" $ra_log)
done
serialSend "GAME_READY 1"
watch_credits
# EXPECT:
/usr/bin/expect -c '
log_user 1
exp_internal 0
set timeout -1
set log_path "/home/pi/CAFCA/log/expect.log"
set busy_flag "/home/pi/CAFCA/data/scan_flag"
set coin_file "/home/pi/CAFCA/data/tmp/cafca_coins_cnt"
set pid [exec pidof retroarch]
set ready 0
set scanned 0
set matched 0
set matches 999
set match_limit 5
set credits 0
set last_credits 0
exec echo -e "\n" > "$log_path"
log_file "$log_path"
spawn scanmem -p $pid
proc startup {} {
exec echo 0 > "$::busy_flag"
expect {
"Please" {
exec sleep 1
send "option region_scan_level 3\r"
exec sleep 1
send "reset\r"
exec sleep 1
exp_continue
}
"*suitable*" {
set ::ready 1
exec sleep 1
}
}
while {$::credits == $::last_credits} {
set ::credits [exec cat "$::coin_file"]
}
}
proc put_coin {} {
set ::credits [exec cat "$::coin_file"]
if {$::credits != $::last_credits} {
set ::last_credits $::credits
exec echo 1 > "$::busy_flag"
exec sleep 1
send "$::credits\r"
exec sleep 1
expect {
"*currently*" {
exec echo 0 > "$::busy_flag"
#set ::matches [lindex [split [lindex [split $expect_out(0,string) "\n"] end ] ">" ] 0]
set ::matches [lindex [split [lindex [split $expect_out(buffer) "\n"] end-1 ] " " ] end-1 ]
send_user "\n\nmatches: $::matches\n"
if {$::matches == "other"} {
send_user "MATCH IS 1!\n"
set ::matches 1
}
#if {$::matches <= $::match_limit} {
# send "list\r"
#}
#exp_continue
}
#"*identified*" {
# exec sleep 1
# send "list\r"
# # exp_continue
#}
#"*matches*" {
# exec echo 0 > "$::busy_flag"
# exec sleep 1
#send "\r"
#exec sleep 1
#set ::matches [lindex [split [lindex [split $expect_out(0,string) "\n"] end ] ">" ] 0]
#set ::matches [lindex [split $expect_out(0,string) "\n"] end ]
#send_user "\n\nmatches: $::matches\n\n"
#if {$::matches < $::match_limit} {
# extract_match
#}
#}
}
}
}
proc extract_match {} {
send_user "\n\nMATCH LIMIT REACHED\n\n"
send "list\r"
}
startup
while {$matches > $match_limit} {
put_coin
}
exec sleep 1
send_user "\nBREAK!\n"
send "list\r"
expect {
"*0x*" {
send_user "LISTING!\n"
exp_continue
}
}
#send "exit\r"
#close
#expect eof
'
#___________________________________________________
while (( $retro_running )); do
if [[ $credits != $last_credits ]]; then
calc_credits
last_credits=$credits
debug "credits: $credits"
#serialSend "CREDITS $credits"
fi
done
}
cafca_dump() {
retro_running=1
serialSend "GAME_LOADED 1"
if [[ "$mode" == RUNCMD ]]; then
system=$(awk 'NR==1' $rc_info); debug "SYSTEM: $system"
game=$(awk -F'/' 'NR==3 {print $NF}' $rc_info | cut -d. -f1); debug "GAME: $game"
fi
game_name=$(echo "$game" | tr '[:lower:]' '[:upper:]')
serialSend "GAME_NAME $game_name"
#---------------------------------------------------
game_table="$data_dir/$system/game_table"
game_listed=$(cat "$game_table" | grep -wc "$game")
[[ $game_listed -gt 0 ]] && get_gamedata
#------------------------------------------------
limit=20
repeats=0
last_len=0
while (( ! $game_ready )); do
log_len=$(wc -l $ra_log | cut -d' ' -f1)
if [[ $repeats -lt $limit ]]; then
[[ $log_len == $last_len ]] && repeats=$(( repeats + 1 ))
last_len=$log_len
else
game_ready=1
break
fi
(( ! $title_found )) && check_title
sleep 0.5
done
serialSend "GAME_READY 1"
debug "\n\nGame Ready!"
debug "\n\nlast log: $(awk 'END{print}' $ra_log)"
#------------------------------------------------
watch_credits
while (( ! $credits )); do calc_credits; done
debug "\n\ncoin inserted!\n"
serialSend "CREDITS $credits"
serialSend "VFD 6 COIN INSERTED!"
[[ $game_listed -gt 0 ]] && getAddr
#------------------------------------------------
if [[ $identified -gt 0 ]]; then
serialSend "LCD 0 GAME DATA FOUND:"
serialSend "LCD 1 $coin_addr: $credits_str"
fi
while (( $retro_running )); do
retro_running=$(ps -ef | grep -v grep | grep -c -m1 retroarch)
if [[ $identified -gt 0 ]]; then
credits=$(sudo scanmem -p `pidof retroarch` -c"dump $coin_addr 1;exit" 2>&1 | awk 'NR==15' | awk -F' ' '{print $2}')
else
calc_credits 1
fi
if [[ $credits != $last_credits ]]; then
last_credits=$credits
debug "credits: $credits"
serialSend "CREDITS $credits"
fi
sleep 1
done
}
getAddr() {
local scan_args='option region_scan_level 3;reset;lregions;exit'
sudo scanmem -p `pidof retroarch` -c"$scan_args" 2>&1 |& grep --line-buffered -P -A1000 '\[ 0\]' |& sed s'/[][,]//g' | sudo tee "$file_regionlist" &>/dev/null && sudo sed -i '/> exit/d' "$file_regionlist"
if [[ $scan_level > 1 ]]; then
for i in {1..3}; do
get_regions "${data[NAME]}" $i
[[ $identified -gt 0 ]] && break
done
elif [[ $scan_level == 1 ]]; then
get_regions "${data[NAME]}" $scan_level
fi
if [[ $identified -gt 0 ]]; then
last_credits="$credits_str"
debug "IDENTIFIED! $coin_addr: $credits_str"
fi
}
get_regions() {
local region_list=$(cat "$file_regionlist")
local region_count=$(cat "$file_regionlist" | wc -l)
local reg_num=${data[NUM]}
local range=5
[[ ${#} -lt 2 ]] && iter=1 || iter=${2}
[[ ${1} == code ]] && iter=$(( iter - 1 ))
debug "get_regions()\n\targs (${#}): ${@}\n"
debug "regions: $region_count\n"
debug "iter: $iter\n\n"
if [[ $iter == 0 ]]; then
reg_start=$reg_num
reg_end=$(( reg_start + 1 ))
elif [[ $iter == 1 ]]; then
reg_range=$(calc_range $reg_num $range)
reg_start=$(echo "$reg_range" | cut -d' ' -f1)
reg_end=$(echo "$reg_range" | cut -d' ' -f2); reg_end=$(( reg_end+1 ))
elif [[ $iter -gt 1 ]]; then
reg_start=1
reg_end=$region_count
fi
for (( i=$reg_start; i<$reg_end; i++)); do
line=$(awk -v ln="$(( i + 1 ))" 'NR==ln' "$file_regionlist" | awk '{$1=$1; print}')
name=$(echo "$line" | awk '{print $5}')
[[ $name == ${data[NAME]} ]] && regions+=("0x$(echo $line | awk '{print $2}')")
done
(( ! $testing )) && dump_addr
}
dump_addr() {
for (( i=0; i<${#regions[@]}; i++)); do
BASE="${regions[$i]}"
OFFS="${data[OFFS]}"
ADDR="";printf -v ADDR "0x%X\n" $(( BASE + OFFS)) &>/dev/null; ADDR=$(echo "$ADDR" | tr '[A-Z]' '[a-z]')
VAL=$(sudo scanmem -p `pidof retroarch` -c"dump $ADDR 1;exit" 2>&1 | awk 'NR==15' | awk -F' ' '{print $2}')
calc_credits 1
if [[ "$VAL" == "$credits_str" ]]; then
LASTVAL="$credits_str"
printf -v credits_str '%02d' "$(( credits+1 ))" &>/dev/null
sudo scanmem -p `pidof retroarch` -c"write i16 $ADDR $credits_str;exit" &>/dev/null
sleep 0.5
VAL=$(sudo scanmem -p `pidof retroarch` -c"dump $ADDR 1;exit" 2>&1 | awk 'NR==15' | awk -F' ' '{print $2}')
calc_credits 1
sleep 0.5
sudo scanmem -p `pidof retroarch` -c"write i16 $ADDR $credits_str;exit" &>/dev/null
if [[ ! $VAL == $LASTVAL ]]; then
debug "NEW WAL: $VAL"
coin_addr="$ADDR"
identified=1 && break
fi
fi
done
}
get_gamedata() {
local table_data=$(cat "$game_table" | grep -w "$game" | awk '{$1=$1;print}')
data[GAME]=$(echo "$table_data" | awk '{print $1}')
data[OFFS]=$(echo "$table_data" | awk '{print $2}')
data[NUM]=$(echo "$table_data" | awk '{print $3}')
data[NAME]=$(echo "$table_data" | awk '{print $4}')
data[TYPE]=$(echo "$table_data" | awk -F'\[' '{print substr($2,0,length($2))}')
debug "\n\nget_gamedata():\n"
debug "DATA:\n"
for i in "${!data[@]}"; do
debug "$i:\t\t${data[$i]}"
done
}
calc_range() {
base=$1
int=$2
sum=$(( base - int ))
if [[ $sum -le 0 ]]; then
start=1; end=$(( int+int ))
else
start=$sum; end=$(( base+int ))
fi
echo "$start $end"
}
calc_credits() {
coins_count=$(cat "$file_coins_count")
start_count=$(cat "$file_start_count")
credits=$(( coins_count - start_count ))
[[ $# > 0 ]] && printf -v credits_str '%02d' "$credits" &>/dev/null
}
watch_credits() {
read_inputs &
}
read_inputs() {
local coins_cnt=0
local start_cnt=0
local limit_start=20
local t_start=$limit_start
local last_start=$(date +%s)
while read -r line; do
pressed=$(echo "$line" | grep -E "$coin_key|$start_key" | \
awk -v C="$coin_key" -v S="$start_key" '{
if ($2 == C) print "COIN"
if ($2 == S) print "START"
}')
coins_cnt=$(cat "$file_coins_count")
start_cnt=$(cat "$file_start_count")
if [[ $pressed == COIN ]]; then
echo "$(( coins_cnt + 1 ))" | sudo tee $file_coins_count &>/dev/null
elif [[ $pressed == START ]]; then
if [[ $coins_cnt -gt 0 ]]; then
if [[ $start_cnt -gt 0 ]]; then
t_start=$(( `date +%s` - $last_start ))
if [[ $t_start -ge $limit_start ]]; then
echo "$(( start_cnt + 1 ))" | sudo tee $file_start_count &>/dev/null
t_start=0
last_start=$(date +%s)
fi
else
echo "$(( start_cnt + 1 ))" | sudo tee $file_start_count &>/dev/null
t_start=0
last_start=$(date +%s)
fi
fi
fi
if [[ "$action" == SCAN ]]; then
scanning=$(cat "$data_dir/scan_flag")
while (( $scanning )); do
scanning=$(cat "$data_dir/scan_flag")
done
fi
done< <(thd --dump "$teensy_kbd" 2>&1 |& grep --line-buffered -E "$coin_key|$start_key" | awk -W interactive '{$1=$1;print}' | grep --line-buffered -Ew "EV_KEY $coin_key 1|EV_KEY $start_key 1" 2>&1)
}
check_title() {
game_title=$(grep -wc "$title_str" $ra_log)
if [[ $game_title -gt 0 ]]; then
#game_title=$(grep -w "$title_str" $ra_log | cut -d' ' -f2-)
#game_title=$(grep -w -A1 "$title_str" $ra_log | awk -F':' '{printf "%s ", $0} END {print ""}')
game_title=$(grep -w -A1 "$title_str" $ra_log | awk -F':' '{if(NR==1){str=$2}else{str="("$0")"}; printf "%s ", str} END {print ""}')
debug "GAME TITLE: $game_title"
serialSend "GAME_TITLE $game_title"
title_found=1
fi
}
wipe() {
file="$@"
[[ ! -f "$file" ]] && sudo touch "$file" &>/dev/null \
|| echo -e "\n" | sudo tee "$file" &>/dev/null
sudo chmod 775 "$file" &>/dev/null
}
serialSend() {
[ -c /dev/ttyACM0 ] && printf "${@}" > /tmp/pyserial.fifo
sleep 0.5
}
press_key() {
printf $@ > /tmp/vkbdd.fifo
}
debug() {
(( $debug )) && echo -e "[DEBUG] $@"
}
cleanup() {
debug "Cleaning up..."
serialSend "GAME_STOPPED 1"
(( $retro_running )) && printf '\033' >/tmp/vkbdd.fifo #sudo pkill retroarch
}
#_____________________________________________________
#
# SETUP
#
#-----------------------------------------------------
#CONSTS:
SCRIPT=$(readlink -f $0)
HOMEDIR=$(getent passwd pi | cut -d: -f6)
DIR=$(cd $(dirname $SCRIPT) && pwd)
args=("$@")
root_user=
log_dir="$DIR/log"
log_file="$log_dir/CAFCA.log"
rc_log='/dev/shm/runcommand.log'
rc_info='/dev/shm/runcommand.info'
ra_log="$log_dir/retroarch/retroarch.log"
teensy_kbd='/dev/input/by-id/usb-Teensyduino_Serial_Keyboard_Mouse_Joystick_1008140-if02-event-kbd'
data_dir="$DIR/data"
game_table="$data_dir/$system/game_table"
file_regionlist="$data_dir/tmp/regionlist"
file_coins_count="$data_dir/tmp/cafca_coins_cnt"
file_start_count="$data_dir/tmp/cafca_start_cnt"
bypass=$(grep bypass "$data_dir/.settings" | cut -d= -f2)
#---------------------------------------------------
(( ! $(id -u) )) && root_user=1 || root_user=0
trap cleanup EXIT
wipe "$log_file"
wipe "$ra_log"
ra_log_owner=$(ls -l "$log_dir/retroarch" | grep "$(echo $ra_log | cut -d/ -f7)" | awk '{print $3" "$4}')
[[ $ra_log_owner != "pi pi" ]] && sudo chown pi:pi "$ra_log"
exec > >(sudo tee $log_file 2>&1)
debug "\n\nC A F C A\n\n`date '+%d/%m-%Y %T'`\n\nHOMEDIR:\t$HOMEDIR\nDIR:\t\t$DIR\nSCRIPT:\t\t$SCRIPT\nROOT USER:\t$root_user\n"
if [ "${#args[@]}" -gt 0 ]; then
testing=0
action=$(echo "$1" | tr '[:lower:]' '[:upper:]')
[ "${#args[@]}" -gt 1 ] && mode=$(echo "$2" | tr '[:lower:]' '[:upper:]')
debug "ARGS: \x27${args[@]}\x27 (${#args[@]})\n\n"
else
debug "NO ARGS..\n"
fi
#debug "_____________________________________________\n"
wipe "$file_regionlist"
wipe "$file_coins_count" && echo 0 | sudo tee "$file_coins_count" &>/dev/null
wipe "$file_start_count" && echo 0 | sudo tee "$file_start_count" &>/dev/null
[[ $bypass -ne 1 ]] && Main
exit 0