/
savestate_selector.sh
676 lines (547 loc) · 17.6 KB
/
savestate_selector.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
#!/bin/bash
# define colors for output
NORMAL="\Zn"
BLACK="\Z0"
RED="\Z1"
GREEN="\Z2"
YELLOW="\Z3\Zb"
BLUE="\Z4"
MAGENTA="\Z5"
CYAN="\Z6"
WHITE="\Z7"
BOLD="\Zb"
REVERSE="\Zr"
UNDERLINE="\Zu"
# parameters
system="$1"
emulator="$2"
rom="$3"
command="$4"
from="$5"
# load INI variables
source ~/scripts/savestate_selector/savestate_selector.cfg
# global variables
backtitle="Savestate Selector (https://github.com/Jandalf81/savestate_selector)"
configDir=/opt/retropie/configs
declare -a menuItems
# import HELPERS.SH
# this script uses the following functions from there
# joy2keyStart()
# joy2keyStop()
# needs the variable $scriptdor to be set
source ~/RetroPie-Setup/scriptmodules/helpers.sh
scriptdir=~/RetroPie-Setup
function log ()
# Prints messages of different severeties to a logfile
# Each message will look something like this:
# <TIMESTAMP> <SEVERITY> <CALLING_FUNCTION> <MESSAGE>
# needs a set variable $logLevel
# -1 > No logging at all
# 0 > prints ERRORS only
# 1 > prints ERRORS and WARNINGS
# 2 > prints ERRORS, WARNINGS and INFO
# 3 > prints ERRORS, WARNINGS, INFO and DEBUGGING
# needs a set variable $log pointing to a file
# Usage
# log 0 "This is an ERROR Message"
# log 1 "This is a WARNING"
# log 2 "This is just an INFO"
# log 3 "This is a DEBUG message"
{
severity=$1
message=$2
if (( ${severity} <= ${logLevel} ))
then
case ${severity} in
0) level="ERROR" ;;
1) level="WARNING" ;;
2) level="INFO" ;;
3) level="DEBUG" ;;
esac
printf "$(date +%FT%T%:z):\t${level}\t${0##*/}\t${FUNCNAME[1]}\t${message}\n" >> ${log}
fi
}
function getROMFileName ()
{
log 3 "()"
rompath="${rom%/*}" # directory containing $rom
romfilename="${rom##*/}" # filename of $rom, including extension
romfilebase="${romfilename%.*}" # filename of $rom, excluding extension
romfileext="${romfilename#*.}" # extension of $rom
log 3 "ROMPATH:\t${rompath}"
log 3 "ROMFILENAME:\t${romfilename}"
log 3 "ROMFILEBASE:\t${romfilebase}"
log 3 "ROMFILEEXT:\t${romfileext}"
}
function getConfigValueToKey ()
{
key="$1"
log 3 "() \$key=${key}"
log 3 "LOOKING FOR VALUE \"${key}\" IN ${configDir}/${system}/retroarch.cfg"
local file=""
value="$(grep --only-matching "^${key} = .*" "${configDir}/${system}/retroarch.cfg")"
if [ "${value}" == "" ]
then
log 3 "LOOKING FOR VALUE \"${key}\" IN ${configDir}/all/retroarch.cfg"
value="$(grep --only-matching "^${key} = .*" "${configDir}/all/retroarch.cfg")"
if [ "${value}" == "" ]
then
log 1 "NO VALUE TO KEY \"${key}\" FOUND"
return 1
else
file="${configDir}/all/retroarch.cfg"
fi
else
file="${configDir}/${system}/retroarch.cfg"
fi
# remove key and " from result
value=${value/$key = /}
value=${value//\"/}
eval value="${value}"
log 3 "FOUND VALUE \"${value}\" TO KEY \"${key}\" IN ${file}"
return 0
}
function setConfigValue ()
{
key="$1"
value="$2"
log 3 "() \$key=${key} \$value=${value}"
if [[ $(grep -c --only-matching "^${key} = .*" "${configDir}/${system}/retroarch.cfg") -eq 1 ]]
then
log 2 "UPDATING KEY ${key} TO VALUE ${value} IN ${configDir}/${system}/retroarch.cfg"
sudo sed -i "/^${key} = /c\\${key} = \"${value}\"" ${configDir}/${system}/retroarch.cfg
else
if [[ $(grep -c --only-matching "^${key} = .*" "${configDir}/all/retroarch.cfg") -eq 1 ]]
then
log 2 "UPDATING KEY ${key} TO VALUE ${value} IN ${configDir}/all/retroarch.cfg"
sudo sed -i "/^${key} = /c\\${key} = \"${value}\"" ${configDir}/all/retroarch.cfg
else
log 2 "ADDING KEY ${key} TO ${configDir}/${system}/retroarch.cfg"
# add new parameter above "#include..."
sudo sed -i "/^#include \"\/opt\/retropie\/configs\/all\/retroarch.cfg\"/c\\${key} = /c\\${key} = \"${value}\"\n#include \"\/opt\/retropie\/configs\/all\/retroarch.cfg\"" ${configDir}/${system}/retroarch.cfg
fi
fi
}
function getSaveAndStatePath ()
{
log 3 "()"
getConfigValueToKey "savefile_directory"
if [[ $? -eq 0 ]] && [ "${value}" != "default" ]
then
log 3 "FOUND VALUE \"${value}\" TO KEY \"savefile_directory\" IN CONFIGURATION FILE"
savePath="${value}"
else
log 3 "FOUND NO VALUE to TO KEY \"savefile_directory\" IN CONFIGURATION FILE, USING \"${rompath}\" INSTEAD"
savePath="${rompath}"
fi
getConfigValueToKey "savestate_directory"
if [[ $? -eq 0 ]] && [ "${value}" != "default" ]
then
log 3 "FOUND VALUE \"${value}\" TO KEY \"savestate_directory\" IN CONFIGURATION FILE"
statePath="${value}"
else
log 3 "FOUND NO VALUE to TO KEY \"savestate_directory\" IN CONFIGURATION FILE, USING \"${rompath}\" INSTEAD"
statePath="${rompath}"
fi
log 2 "SAVEPATH: \"${savePath}\""
log 2 "STATEPATH: \"${statePath}\""
}
function buildMenuItems ()
{
log 3 "()"
declare -a stateFiles
# build list of SAVESTATEs
while read stateFile
do
# skip empty lines
if [ "${stateFile}" == "" ]; then continue; fi
# prepare item
slot="${stateFile##*.}"
slot=${slot/state/}
if [ "${slot}" == "" ]; then slot="0"; fi
file="${stateFile##*/}"
lastModified=$(stat --format=%y "${stateFile}")
lastModified="${lastModified%%.*}"
text="Slot ${slot}, last modified ${lastModified}"
# add item to list
stateFiles+=("${slot}|${file}|${lastModified}|${text}")
done <<< $(find $statePath -type f -iname "${romfilebase}.state*" ! -iname "*.png" ! -iname "*.auto") # get all STATE files, exclude *.PNG and *.AUTO
log 2 "FOUND ${#stateFiles[@]} SAVESTATES"
# define sortString
case ${sortOrder} in
0) sortString="rk 3,3" ;; # LASTMODIFIED (text) DESC
1) sortString="k 3,3" ;; # LASTMODIFIED (text)
2) sortString="nrk 1,1" ;; # SLOT (numeric) DESC
3) sortString="nk 1,1" ;; # SLOT (numeric)
*) sortString="rk 3,3" ;; # LASTMODIFIED (text) DESC
esac
# check for SRM save
if [ -f "${savePath}/${romfilebase}.srm" ]
then
srmStatus="${GREEN}"
else
srmStatus="${RED}No "
fi
# add default items to list of menu items
menuItems+=("L" "Launch ROM without Savestate (${srmStatus}Battery Save found${NORMAL})")
menuItems+=("D" "Delete Savestates")
# add more items if the script has been started via RUNCOMMAND-MENU / USER SCRIPTS
if [ "${from}" == "runcommand-menu" ]
then
log 3 "STARTED FROM RUNCOMMAND-MENU, ADDING MORE MENU ITEMS"
menuItems+=("B" "Back to RUNCOMMAND")
menuItems+=("X" "Exit to EmulationStation")
fi
menuItemsDefault=${#menuItems[@]}
# add menu items for each SAVESTATE to list of menu items
while read stateFile
do
# skip empty lines
if [ "${stateFile}" == "" ]; then continue; fi
# split each line
IFS="|"
read -ra line <<< ${stateFile}
menuItems+=("${line[0]}" "${line[3]}") # SLOT ITEM
done <<< $(printf "%s\n" "${stateFiles[@]}" | sort -${sortString} -t"|")
}
function showSavestateSelector ()
{
log 3 "()"
local choice
local i
log 3 "FOUND ${#menuItems[@]} MENU ITEMS"
# only show dialog if items have been added, e. g. savestates have been found
if [[ ${#menuItems[@]} -gt ${menuItemsDefault} ]]
then
log 3 "AT LEAST 1 SAVESTATE HAS BEEN FOUND, SHOWING DIALOG"
# start JOY2KEY from the sourced file with cursor key for axis/DPAD, ENTER for A, TAB for B
joy2keyStart kcub1 kcuf1 kcuu1 kcud1 0x0a 0x09
log 2 "STARTED joy2key WITH RETURN CODE $?, PID ${__joy2key_pid}"
if [ "${showThumbnails}" == "TRUE" ]; then refreshThumbnailMontage; fi
log 3 "WAITING FOR USER INPUT"
choice=$(dialog \
--colors \
--backtitle "${backtitle}" \
--title "Starting ${romfilename}..." \
--no-cancel \
--menu "\nPlease select which SAVESTATE to start" 20 75 12 \
"${menuItems[@]}" \
2>&1 >/dev/tty)
log 2 "SELECTED OPTION \"$choice\""
# react to choice
if [ "${choice}" == "" ]; then startROM
elif [ "${choice}" == "L" ]; then startROM
elif [ "${choice}" == "D" ]; then showSavestateDeleter
elif [ "${choice}" == "B" ]; then backToRUNCOMMAND
elif [ "${choice}" == "X" ]; then exitToEmulationStation
elif (( ${choice} >= 0 && ${choice} <= 999 )); then startSavestate "${choice}"
else startROM
fi
else
log 2 "NO SAVESTATE HAS BEEN FOUND, SKIPPING DIALOG"
startROM
fi
}
function startROM ()
{
log 3 "()"
log 3 "SETTING DEFAULT CONFIG PARAMETERS"
setConfigValue "state_slot" "0"
setConfigValue "savestate_auto_load" "false"
if [ "${showThumbnails}" == "TRUE" ]; then setConfigValue "savestate_thumbnail_enable" "true"; fi # create THUMBNAILS on SAVESTATE creation
pkill pngview
joy2keyStop
exit 2
}
function showSavestateDeleter ()
{
log 3 "()"
local choice
log 3 "SHOW MENU DIALOG"
if [ "${showThumbnails}" == "TRUE" ]; then refreshThumbnailMontage; fi
log 3 "WAITING FOR USER INPUT"
# this menu shows anything but the default items, e. g. only the statefiles
choice=$(dialog \
--colors \
--backtitle "${backtitle}" \
--title "Delete savestates" \
--ok-label "Delete" \
--cancel-label "Back" \
--menu "\nWhich savestate is to be ${RED}deleted${NORMAL}?" 20 75 12 \
"${menuItems[@]:${menuItemsDefault}}" \
2>&1 >/dev/tty)
log 2 "SELECTED OPTION \"$choice\""
if [ "${choice}" == "" ]; then showSavestateSelector
elif (( ${choice} >= 0 && ${choice} <= 999 )); then deleteSavestate "$choice"
fi
}
function deleteSavestate ()
{
slot="$1"
log 3 "() \$slot=${slot}"
# show DELETE MARKER overlay
if [ "${showThumbnails}" == "TRUE" ]; then showDeleteOverlay "${choice}"; fi
# ask for confirmation
local choice
log 3 "AWAITING CONFIRMATION"
dialog \
--colors \
--backtitle "${backtitle}" \
--title "Confirm deletion" \
--defaultno \
--yesno "\nDo you really want to ${RED}delete${NORMAL}\n\n${YELLOW}${statePath}/${romfilebase}.state${slot}${NORMAL}\n\nThis can ${RED}not be undone${NORMAL}!" 20 75 \
2>&1 >/dev/tty
choice=$?
log 3 "SELECTED OPTION ${choice}"
if [[ ${choice} -eq 0 ]]
then
# check if RCLONE_SCRIPT is also installed, then offer to delete from remote
if [ -f ~/scripts/rclone_script/rclone_script.sh ]
then
# confirm again to also delete files from remote
log 2 "RCLONE_SCRIPT detected"
dialog \
--colors \
--backtitle "${backtitle}" \
--title "Confirm deletion from remote" \
--defaultno \
--yesno "\nIt seems you also use RCLONE_SCRIPT. Do you want to ${RED}delete${NORMAL} the savestate from your remote? If you don't, the savestate will be ${YELLOW}re-downloaded${NORMAL} on the next start." 20 75 \
2>&1 > /dev/tty
choice=$?
# answer was YES so delete file from remote
if [ ${choice} -eq 0 ]
then
log 2 "Deleting files from remote..."
# special case for slot 0
if [ "${slot}" == "0" ]
then
slotRemote=""
else
slotRemote="${slot}"
fi
~/scripts/rclone_script/rclone_script.sh "delete" "${system}/${romfilebase}.state${slotRemote}.png"
~/scripts/rclone_script/rclone_script.sh "delete" "${system}/${romfilebase}.state${slotRemote}"
case $? in
0) log 2 "File(s) deleted successfully" ;;
1) log 0 "File(s) not deleted, connection not available" ;;
2) log 0 "File(s) not deleted, unknown error" ;;
esac
fi
fi
# remove DELETE MARKER overlay
if [ "${pidDeleteOverlay}" != "" ]; then kill -INT ${pidDeleteOverlay}; fi
# remove menu item from array (2 entries)
for i in "${!menuItems[@]}"
do
if [ "${menuItems[i]}" == "${slot}" ]
then
unset -v menuItems[i]
unset -v menuItems[++i]
break # leave loop
fi
done
if [ "${slot}" == "0" ]; then slot=""; fi # special case for slot 0
# remove Savestate file
log 2 "REMOVED STATEFILE \"${statePath}/${romfilebase}.state${slot}\""
rm "${statePath}/${romfilebase}.state${slot}"
if [ -f "${statePath}/${romfilebase}.state${slot}.png" ]; then rm "${statePath}/${romfilebase}.state${slot}.png"; fi
if [ ${#menuItems[@]} -gt ${menuItemsDefault} ]
then
# return to showSavestateDeleter
showSavestateDeleter
else
# there is no more savestate to delete or chose from
log 2 "NO MORE SAVESTATE TO DELETE OR START, STARTING ROM NOW"
startROM
fi
else
# remove DELETE MARKER overlay
if [ "${pidDeleteOverlay}" != "" ]; then kill -INT ${pidDeleteOverlay}; fi
# return to dialog
showSavestateDeleter
fi
}
function showDeleteOverlay ()
{
slot="$1"
log 3 "() \$slot=${slot}"
local position
local offset
local posx
local posy
local j=0
# get index of $menuItem to $slot
for i in ${!menuItems[@]}
do
if [ "${slot}" == "${menuItems[$i]}" ]
then
offset=$(( $i - $j )) # compute the offset: index of item minus real position (deleting items creates gaps, this compensates these gaps)
position=$(( ((${i} - 4 - ${offset}) / 2) + 1 )) # compute the position of the thumbnail to delete
log 3 "FOUND SLOT ${slot} AT POSTION ${position}"
break
fi
let j++
done
# set coords according to $position
case ${position} in
1) posx=75; posy=40 ;;
2) posx=555; posy=40 ;;
3) posx=1035; posy=40 ;;
4) posx=1515; posy=40 ;;
5) posx=75; posy=390 ;;
6) posx=1515; posy=390 ;;
7) posx=75; posy=740 ;;
8) posx=555; posy=740 ;;
9) posx=1035; posy=740 ;;
10) posx=1515; posy=740 ;;
*) posx=0; posy=0 ;;
esac
if [[ ${posx} -ne 0 ]]
then
log 3 "SHOWING DELETE MARKER AT ${posx}, ${posy}"
nohup pngview -b 0 -l 10001 "/home/pi/scripts/savestate_selector/delete.png" -x ${posx} -y ${posy} &> /dev/null &
# save pid to kill the process later
pidDeleteOverlay=$!
fi
}
function startSavestate ()
{
slot="$1"
log 3 "() \$slot=${slot}"
log 2 "START SAVESTATE FROM SLOT ${slot}"
# set parameters in CFG
setConfigValue "savestate_auto_load" "true" # load SAVESTATE named AUTO
setConfigValue "state_slot" "${slot}" # set SLOT to slot of selected SAVESTATE
if [ "${showThumbnails}" == "TRUE" ]; then setConfigValue "savestate_thumbnail_enable" "true"; fi # create THUMBNAILS
if [[ $slot -eq 0 ]]; then slot=""; fi # special case for slot 0
# copy selected slot to AUTO
log 2 "COPIED ${statePath}/${romfilebase}.state${slot} TO ${statePath}/${romfilebase}.state.auto"
cp -f "${statePath}/${romfilebase}.state${slot}" "${statePath}/${romfilebase}.state.auto"
# copy tumbnail to LAUNCHING image
#cp -f "${statePath}/${romfilebase}.state${slot}.png" "${rompath}/images/${romfilebase}-launching.png"
log 2 "WILL REMOVE ${statePath}/${romfilebase}.state.auto IN ${deleteDelay} SECONDS..."
pkill pngview
joy2keyStop
# remove AUTO after 10 seconds in background task (so the ROM is already started)
(
sleep ${deleteDelay}
rm "${statePath}/${romfilebase}.state.auto"
#rm "${rompath}/images/${romfilebase}-launching.png"
log 2 "REMOVED ${statePath}/${romfilebase}.state.auto"
) &
exit 2
}
function refreshThumbnailMontage ()
{
log 3 "()"
# only create a new montage if the number of menu items has been changed since the last run
if [[ ${oldNumberOfMenuItems} -ne ${#menuItems[@]} ]]
then
log 2 "PREPARING NEW THUMBNAIL MONTAGE"
pkill pngview
# msgbox CREATING PREVIEW
dialog \
--colors \
--backtitle "${backtitle}" \
--title "Generating Previews..." \
--infobox "\nThis may take some seconds...\n\n(The Previews can\nbe ${YELLOW}disabled${NORMAL} in the configuration)" 10 40 \
2>&1 >/dev/tty
declare -a thumbnailFile
# initialize array
for i in 0 1 2 3 4 5 6 7 8 9 # $(seq 0 9)
do
thumbnailFile[i]=null:
done
local tnF=0
# save current number of menu items for future runs
oldNumberOfMenuItems=${#menuItems[@]}
# fill array with references to thumbnails
for mi in ${!menuItems[@]}
do
# skip the default items, skip every odd item
if [[ $mi -lt ${menuItemsDefault} ]] || [[ $(( mi % 2 )) -eq 1 ]]; then continue; fi
# stop this after 10 thumbnails
if [[ $tnF -ge 10 ]]; then break; fi
# get SLOT from menuItem
slot=${menuItems[mi]}
log 3 "GOT SLOT ${slot} FROM \$menuItems"
if [[ ${slot} -eq 0 ]]; then slot=""; fi # special case for slot 0
log 3 "CHECKING FILE ${statePath}/${romfilebase}.state${slot}.png"
# check if there's a thumbnail for the currenct thumbnailFile
if [ -f "${statePath}/${romfilebase}.state${slot}.png" ]
then
log 3 "FOUND THUMBNAIL ${statePath}/${romfilebase}.state${slot}.png"
thumbnailFile[tnF]="${statePath}/${romfilebase}.state${slot}.png"
let tnF++
else
log 3 "NO THUMBNAIL FOUND, USING FALLBACK"
cp "/home/pi/scripts/savestate_selector/no_thumbnail.png" "/dev/shm/${romfilebase}.state${slot}.png"
thumbnailFile[tnF]="/dev/shm/${romfilebase}.state${slot}.png"
let tnF++
fi
done
# create montage based on &thumbnailFile
log 2 "CREATING NEW THUMBNAIL MONTAGE"
montage \
-label '%[basename]' \
"${thumbnailFile[0]}" \
"${thumbnailFile[1]}" \
"${thumbnailFile[2]}" \
"${thumbnailFile[3]}" \
"${thumbnailFile[4]}" \
"null:" \
"null:" \
"${thumbnailFile[5]}" \
"${thumbnailFile[6]}" \
"${thumbnailFile[7]}" \
"${thumbnailFile[8]}" \
"${thumbnailFile[9]}" \
-tile 4x3 \
-geometry 320x240+75+40 \
-background transparent \
-shadow \
-frame 5 \
"/dev/shm/savestate_selector.png" >> ${log}
log 2 "SHOWING THUMBNAIL MONTAGE"
nohup pngview -b 0 -l 10000 "/dev/shm/savestate_selector.png" -x 0 -y 0 &>/dev/null &
else
log 2 "NO CHANGE, RE-USING THUMBNAIL MONTAGE"
fi
}
function debugPrintArray ()
{
for i in ${!menuItems[@]}
do
log 3 "INDEX = ${i}, CONTENT = ${menuItems[i]}"
done
}
function backToRUNCOMMAND ()
{
log 2 "BACK TO RUNCOMMAND-MENU"
pkill pngview
joy2keyStop
exit 0
}
function exitToEmulationStation ()
{
log 2 "EXIT TO EMULATIONSTATION"
pkill pngview
joy2keyStop
exit 1
}
log 3 "()"
log 3 "\$1 SYSTEM:\t${system}"
log 3 "\$2 EMULATOR:\t${emulator}"
log 3 "\$3 ROM:\t${rom}"
log 3 "\$4 COMMAND:\t${command}"
if [ "${enabled}" != "TRUE" ] && [ "${from}" != "runcommand-menu" ]
then
log 2 "SAVESTATE_SELECTOR IS CURRENTLY DISABLED"
exit
fi
getROMFileName
getSaveAndStatePath
buildMenuItems
showSavestateSelector
pkill pngview
joy2keyStop