-
Notifications
You must be signed in to change notification settings - Fork 5
/
polyversal
executable file
·1137 lines (964 loc) · 38.2 KB
/
polyversal
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
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
### Constants ###
# Script version, used to check if out-of-date.
# Update this when creating a new release.
readonly VERSION=2.2.0
progname="$(basename "$0")"
readonly progname
DATADIR=$(dirname "$0")
readonly DATADIR
# Protontricks versions earlier than this minversion will cause issues that are hard to make sense of.
readonly ptx_flatpak='com.github.Matoking.protontricks'
readonly ptx_minversion=1.12.0
# For log files and the like, all hail ISO 8601.
# People likely won't be running this more than once in a second.
function iso_date() { date +%Y%m%dT%H%M%S; }
exectime=$(iso_date)
readonly exectime
# Repo info for version check
readonly REPO_ID="CommitteeOfZero/polyversal-coz-linux-patcher"
readonly URL_RELEASES="https://github.com/${REPO_ID}/releases"
readonly URL_API_LATEST="https://api.github.com/repos/${REPO_ID}/releases/latest"
# Custom Proton download
readonly AC_PROTON_URL='https://github.com/CommitteeOfZero/ProtonGE-AC/releases/download/1.0.0/protonge-anonymouscode.tar.gz'
readonly AC_PROTON_DIRNAME='protonge-anonymouscode'
### Functions ###
function print_usage() {
cat << EOF >&2
usage: polyversal [ -v | --verbose ] [ -h | --help ] [ --log ]
[ -r | --steamroot ] [ -F | --force-flatpak ] ...
Use a GUI for selecting everything graphically.
$ $progname
Patch GAME with the CoZ patch in PATCH_FOLDER.
$ $progname install GAME_ABBREV PATCH_FOLDER
Uninstall CoZ patch for GAME.
$ $progname uninstall GAME
Delete GAME's Proton prefix.
$ $progname nuke GAME
Install custom Proton-GE build for A;C video playback.
$ $progname ac-proton
Game abbreviations:
chn
sg
sge
rne
lbp
cc
sg0
rnd
ac
options:
-h, --help Display this help message and exit
-v, --verbose Enable debug logging
--log Copy terminal output to a new file log in
'logs'. Disables terminal colors. This is the
default when running from the desktop file
-r, --steamroot NEW_ROOT Use NEW_ROOT as Steam's root directory instead of
~/.local/share/Steam
-F, --force-flatpak Force the use of Flatpak Protontricks even if a
system install is detected.
EOF
}
# I like colors. Only use them if stderr outputs to a terminal which supports 8
# or more colors.
# Pulled into a function that we can call later since --log or similar
# options might change stderr.
txt_normal=''
txt_green=''
txt_yellow=''
txt_red=''
txt_purple=''
txt_blue=''
function set_logcolors() {
if ! test -t 2 || [[ ! $(tput colors) -ge 8 ]]; then return 0; fi
txt_normal="$(tput sgr0)"
if [[ $(tput colors) -ge 16 ]]; then
txt_green=$(tput setaf 2)
txt_yellow=$(tput setaf 11)
txt_red=$(tput setaf 9)
txt_purple=$(tput setaf 13)
txt_blue=$(tput setaf 12)
else
# at least 8 basic colors
txt_green="$(tput setaf 2)"
txt_yellow="$(tput setaf 3)"
txt_red="$(tput setaf 1)"
txt_purple="$(tput setaf 5)"
txt_blue="$(tput setaf 4)"
fi
}
function _log_msg() {
local sevpfx
case $(tolower "$1") in
'info' | 'i')
sevpfx="${txt_green} INFO" ;;
'warn' | 'w')
sevpfx="${txt_yellow} WARN" ;;
'error' | 'err' | 'e')
sevpfx="${txt_red}ERROR" ;;
'fatal' | 'fat' | 'f')
sevpfx="${txt_purple}FATAL" ;;
'debug' | 'd')
sevpfx="${txt_blue}DEBUG" ;;
*)
# Better than dying for no reason
sevpfx="$1" ;;
esac
sevpfx+=${txt_normal}
local header
header=$progname
$mode_debug && header+=:${BASH_LINENO[1]} # line number where log fn was called
local body=${*:2}
printf '%s %s - %s\n' "$header" "$sevpfx" "$body" >&2
}
function log_info() { _log_msg info "$*"; }
function log_warn() { _log_msg warn "$*"; }
function log_error() { _log_msg err "$*"; }
function log_fatal() { _log_msg fatal "$*"; }
function log_debug() {
! $mode_debug && return 0
_log_msg debug "$*"
}
# Want `./polyversal chn` and `./polyversal CHN` to work the same
function tolower() {
printf '%s' "$*" | tr '[:upper:]' '[:lower:]'
}
# `command -v COMMAND` prints information about COMMAND, but importantly has
# exit status 0 if the command exists and 1 if it does not. This true/false
# value is what we use in this script to determine whether a command is
# installed and available on the system.
function is_cmd() {
command -v "$1" > /dev/null
}
# Shadows any call to zenity so we don't accidentally forget an $is_gui
function zenity() {
! $is_gui && return 0
command zenity "$@"
}
# Happens often enough to warrant a function
function zenity_error() {
zenity --error --title "Polyversal Error" --text "$*"
}
function zenity_warn() {
zenity --warning --title "Polyversal Warning" --text "$*"
}
# Determines if Protontricks is up to date, i.e. $ptx_minversion or later.
# Single argument is which Protontricks to check:
# 'fp' = Flatpak, 'sys' = system install
# Other arguments don't do anything. Don't use them!
# is_ptxvalid <fp|sys>
function check_ptx() {
# usage: check_ptx <fp|sys>
local ptx_cmd
case $1 in
fp)
ptx_cmd="flatpak run $ptx_flatpak" ;;
sys)
ptx_cmd="protontricks" ;;
esac
# Decent way to check it's actually functional.
local cur_ver
! cur_ver=$($ptx_cmd --version) && return 1
log_info "protontricks version output: '$cur_ver'"
local older_ver
older_ver=$(printf '%s\n%s\n' "protontricks ($ptx_minversion)" "$cur_ver" | sort -V | head -n 1)
# really only useful for system protontricks bc we'll always update flatpak
[[ $older_ver == "protontricks ($ptx_minversion)" ]]
}
### Option Parsing ###
longopts='help,verbose,log,steamroot:,force-flatpak,i-really-want-to-run-this-as-root-dont-question-me-boy'
if ! parsed_args=$(getopt -n "$progname" -o 'hvr:F' --long "$longopts" -- "$@"); then
log_fatal "error parsing command line arguments"
print_usage
exit 1
fi
eval set -- "$parsed_args"
mode_debug=false
mode_filelog=false
STEAMROOT=$HOME/.steam/root
FORCE_FLATPAK=false
allow_root=false
while true; do
case "$1" in
-v | --verbose)
mode_debug=true
shift ;;
-h | --help)
print_usage
exit 0 ;;
--log)
mode_filelog=true
shift ;;
-r | --steamroot)
STEAMROOT=$2
shift 2 ;;
-F | --force-flatpak)
FORCE_FLATPAK=true
shift ;;
--i-really-want-to-run-this-as-root-dont-question-me-boy)
allow_root=true
shift ;;
--)
shift
break ;;
*)
log_fatal "Unexpected option '$1', this should never happen"
print_usage
exit 1 ;;
esac
done
if ! $allow_root && [[ $EUID == 0 ]]; then
printf '%s\n' "YOU ARE RUNNING THIS SCRIPT AS ROOT! This is VERY PROBABLY NOT what you want to do. Please run it without \`sudo'."
exit 1
fi
# Logging is output to a new .log file within 'logs/' under the same directory
# as this script.
logdir=${DATADIR}/logs
logname="${logdir}/polyversal-${exectime}" # minus the .log so we can add -wine
if $mode_filelog; then
# https://stackoverflow.com/questions/3173131/redirect-copy-of-stdout-to-log-file-from-within-bash-script-itself
mkdir -p "$logdir"
exec > >(tee -ia "${logname}.log")
exec 2> >(tee -ia "${logname}.log" >&2)
fi
set_logcolors # after all the output redirection
# GUI mode: 0 positional args
# CLI mode: >= 1 args
is_gui=false
if [[ $# -eq 0 ]]; then
if ! is_cmd zenity; then
log_fatal "Zenity is required to run this script in GUI mode. Please make sure you have it installed, or use the CLI."
print_usage
exit 1
fi
is_gui=true
fi
# Get some basic info on the user's machine (distro, kernel, CPU/GPU if it works)
os_pretty_name=$(grep -E ^PRETTY_NAME /etc/os-release)
mobo_vendor=$(cat /sys/devices/virtual/dmi/id/board_vendor)
mobo_name=$(cat /sys/devices/virtual/dmi/id/board_name)
cpu_name=$(lscpu | sed -nE 's/Model name:[[:space:]]+(.+)/\1/p')
vga_controller_name=$(lspci | sed -nE 's/^.*VGA compatible controller: (.+)/\1/p')
weird_3d_name=$(lspci | sed -nE 's/^.*3D controller: (.+)/\1/p') # some nvidia laptops do this for some reason
gpu_output=$vga_controller_name
[[ -n $weird_3d_name ]] && gpu_output="VGA compatible controller: $vga_controller_name ; 3D controller: $weird_3d_name"
log_info "Machine info: $(uname -srmo) ${os_pretty_name:-[no pretty name found]}"
log_info "Hardware: ${mobo_vendor:-[could not detect mobo vendor]} ${mobo_name:-[could not detect mobo name]} | ${cpu_name:-[could not detect CPU]} | ${gpu_output:-[could not detect GPU]}"
log_info "Starting Polyversal Patcher on $(date) ..."
# Detect whether the machine is a Steam Deck.
is_deck=false
if grep -qE '^VERSION_CODENAME=holo' /etc/os-release; then
is_deck=true
log_info "detected Steam Deck environment ..."
fi
### Functionality Functions ###
# We need Protontricks either through a system install or through Flatpak to
# work the magic. Prefer system install since it plays nice with relative dirs
# and doesn't need permissions to be setup, but if it's unavailable or outdated
# then use Flatpak.
ptx_cmd=
is_flatpak=false
ptx_pid= # this will be run in a subshell on script start so the actual GUI starts right away
function setup_protontricks() {
# usage: setup_protontricks
local ret_code=0
# check for valid system install
if ! $FORCE_FLATPAK; then
if is_cmd protontricks; then
if check_ptx sys; then
log_info "detected valid system install of protontricks"
# because i'm funny.
# (but more precisely because we only ever send this function to the background, and subshells can't modify
# global variables like $ptx_cmd belonging to the parent shell, so we set it based on the return code instead.)
# now LAUGH
return 69
else
log_warn "system install of protontricks failed basic version check. defaulting to flatpak"
fi
else
log_info "system install of protontricks not found"
fi
else
log_info "forcing Flatpak protontricks"
fi # ififif fififi, no bad code smell here for me
log_info "proceeding with flatpak ..."
# Nothing doing if no flatpak :(
if ! is_cmd flatpak; then
log_fatal "neither flatpak nor valid system install of protontricks was detected"
return 1
fi
# Install protontricks if it's not already
local just_installed=false # one would hope that a fresh install grabs the latest version
if ! flatpak list --system | grep -q "$ptx_flatpak" -; then
log_warn "protontricks is not installed on flatpak. attempting installation ..."
# one would think this should be --user, but no. --system is the default and --user straight up doesn't find the
# package on some systems (read: mine)
if ! flatpak install --system -y "$ptx_flatpak"; then
log_fatal "error occurred while installing flatpak protontricks"
return 2
fi
just_installed=true
log_info "flatpak protontricks installed successfully"
fi
# Just update no matter what, outdated versions beyond the minimum have alrady caused issues
if ! $just_installed && ! flatpak update --system -y "$ptx_flatpak"; then
log_warn "error occurred while updating flatpak protontricks"
ret_code=43 # not dire enough to kill the function, or by extension the script
else
log_info "flatpak protontricks either updated successfully or was already up-to-date"
fi
is_flatpak=true
[[ $ret_code != 43 ]] && ret_code=42 # return codes only go up to 255 and i'm devastated
return $ret_code
}
# Get the target game and initialize the relevant variables
appid=
patch_exe=
gamename=
gamecode=
gamedir=
function set_game() {
# usage: set_game <shortname?> <zenity msg>
# arg can be omitted if GUI mode is set (no params to script)
local arg_game
if $is_gui; then
arg_game=$(zenity --list --radiolist --title "$2" \
--height 400 --width 600 \
--column "Select" --column "Title" \
TRUE 'CHAOS;HEAD NOAH' \
FALSE 'STEINS;GATE' \
FALSE 'STEINS;GATE ELITE' \
FALSE 'ROBOTICS;NOTES ELITE' \
FALSE 'STEINS;GATE: Linear Bounded Phenogram' \
FALSE 'CHAOS;CHILD' \
FALSE 'STEINS;GATE 0' \
FALSE 'ROBOTICS;NOTES DaSH' \
FALSE 'ANONYMOUS;CODE')
local zen_ret=$?
log_debug "zen_ret for set_game = $zen_ret"
case $zen_ret in
1) start_screen ;;
-1)
log_fatal "An unexpeted Zenity error has occured in set_game()."
exit 42
;;
esac
else
arg_game=$1
fi
# Get the app ID and what the installer exe should be, based on the shortname.
# IDs are available in the README.
# CoZ's naming conventions are beautifully consistent, pls never change them
case $(tolower "$arg_game") in
'chn' | 'ch' | 'chaos'[\;\ ]'head noah')
appid=1961950
patch_exe='CHNSteamPatch-Installer.exe'
gamename="CHAOS;HEAD NOAH"
gamecode=chn
;;
'sg' | 'steins'[\;\ ]'gate')
appid=412830
patch_exe='SGPatch-Installer.exe'
gamename="STEINS;GATE"
gamecode=sg
;;
'sge' | 'steins'[\;\ ]'gate elite')
appid=819030
patch_exe='SGEPatch-Installer.exe'
gamename="STEINS;GATE ELITE"
gamecode=sge
;;
'rne' | 'rn' | 'robotics'[\;\ ]'notes elite')
appid=1111380
patch_exe='RNEPatch-Installer.exe'
gamename="ROBOTICS;NOTES ELITE"
gamecode=rne
;;
'lbp' | 'steins'[\;\ ]'gate: linear bounded phenogram')
appid=930910
patch_exe='SGLBPPatch-Installer.exe'
gamename="STEINS;GATE: Linear Bounded Phenogram"
gamecode=lbp
;;
'cc' | 'chaos'[\;\ ]'child')
appid=970570
patch_exe='CCSteamPatch-Installer.exe'
gamename="CHAOS;CHILD"
gamecode=cc
;;
'sg0' | '0' | 'steins'[\;\ ]'gate 0')
appid=825630
patch_exe='SG0Patch-Installer.exe'
gamename="STEINS;GATE 0"
gamecode=sg0
;;
'rnd' | 'dash' | 'robotics'[\;\ ]'notes dash')
appid=1111390
patch_exe='RNDPatch-Installer.exe'
gamename="ROBOTICS;NOTES DaSH"
gamecode=rnd
;;
'ac' | 'anonymous'[\;\ ]'code')
appid=2291020
patch_exe='ACSteamPatch-Installer.exe'
gamename="ANONYMOUS;CODE"
gamecode=ac
;;
*)
log_fatal "shortname '$arg_game' is invalid"
print_usage
exit 1
;;
esac
# for all script verbs that use protontricks, THIS is the first place an actual protontricks command is used.
# so we wait here for setup_protontricks to complete if it hasn't yet.
wait $ptx_pid
local setup_ret=$?
log_debug "ptx setup returned $setup_ret"
# return codes for setup_protontricks are:
# 0 - should be impossible
# 1 - neither `flatpak` nor system ptx was found on the system
# 2 - something went wrong installing flatpak ptx
# 42 - use flatpak protontricks
# 43 - use flatpak, but something went wrong while updating
# 69 - use system protontricks
case $setup_ret in
0)
zenity_error "This window shouldn't appear; something has gone horribly wrong. Please open a GitHub issue saying you saw this if you have a minute."
log_error "reached the impossible retcode 0 from setup_protontricks, AAAAAAAAAA"
;;
1)
zenity_error "Neither Flatpak nor system Protontricks >= $ptx_minversion was found. Please install one of the two and then try again."
exit 1 ;;
2)
zenity_error "An error occurred while installing Protontricks via Flatpak. Check the logs for details."
exit 2 ;;
42)
ptx_cmd="flatpak run $ptx_flatpak"
;;
43)
ptx_cmd="flatpak run $ptx_flatpak"
zenity_warn "An error occurred while updating Flatpak Protontricks. The script will continue, but this may cause issues."
;; # no exit, just a warning
69) # I SAID LAUGH
ptx_cmd='protontricks'
;;
esac
log_debug "ptx setup subshell finished, ptx_cmd = '$ptx_cmd'"
if ! gamedir=$($ptx_cmd -c 'printf "%s" "$STEAM_APP_PATH"' $appid); then
# protontricks outputs to stdout even in the case of an error, so $gamedir actually has the error message.
log_fatal "protontricks error while getting $gamecode game directory. likely is not installed or has not been run to generate prefix."
log_fatal "output of protontricks command: '$gamedir'"
zenity_error "Unable to locate $gamename's game directory. This likely means you have not yet run and quit the game once to generate its Proton prefix."
exit 99
fi
log_info "using game directory '$gamedir'"
}
# CHN CoZ patch includes custom SteamGrid images, but since the patch is built for
# Windows, the placement of those files ends up happening within the Wine prefix
# instead of the system-level Steam install. The following code will detect the
# STEAMGRID folder within the patch directory, and if it exists, copy any *.png
# files at its root to Steam userdata/<user_id>/config/grid within a default Steam
# path install ($HOME/.local/share/Steam)
#
# TODO: Add support for flatpak Steam installs.
function copy_steamgrid() {
# usage: copy_steamgrid <patch dir>
local patch_dir=$1
local has_users=false
local copies_fine=true
log_info "copying custom SteamGrid images ..."
# Don't iterate over userdata/*/config/grid because it might not exist.
for userdir in "$STEAMROOT"/userdata/*; do
has_users=true
local griddir="$userdir/config/grid"
log_debug "installing SteamGrid in $griddir ..."
if ! { mkdir -p "$griddir" && cp "$patch_dir"/STEAMGRID/* "$griddir"; }
then
copies_fine=false
log_error "error occured while installing SteamGrid files to $griddir"
fi
done
if ! $has_users; then
log_error "no users were found in $STEAMROOT/userdata"
zenity_error "No users were found in $STEAMROOT/userdata, unable to install custom SteamGrid images."
elif $copies_fine; then
log_info "SteamGrid images installed successfully"
fi
}
# S;G and C;C both launch the default launcher (Launcher.exe and launcher.exe,
# respectively) instead of the patched LauncherC0.exe.
# Fix by symlinking default launcher to patched.
# Jan 2024: S;G0 too
function apply_launcherfix() {
# usage: apply_launcherfix
log_info "fixing $gamecode launcher issue ..."
if ! pushd "$gamedir"; then
log_error "unable to cd into game directory '$gamedir'"
zenity_error "An error occurred while accessing the game directory '$gamedir', unable to apply launcher fix. See the logs for more info."
return 2
fi
local defaultlauncher
case $gamecode in
sg) defaultlauncher=Launcher.exe ;;
cc | sg0) defaultlauncher=launcher.exe ;;
*)
log_error "$gamecode does not have a launcherfix applied. how did you do this?"
popd
return 3 ;;
esac
if [[ ! ( -f $defaultlauncher && -f LauncherC0.exe ) ]]; then
log_error "the default launcher and CoZ launcher were not both present in gamedir '$gamedir', unable to proceed with fix"
ls -l "$defaultlauncher" LauncherC0.exe >&2
zenity_error "$gamename's game directory did not have the default and patched launcher both present, unable to apply launcher fix. Was the patch installed correctly?"
elif [[ $(readlink "$defaultlauncher") == LauncherC0.exe ]]; then
log_warn "$defaultlauncher was already symlinked to LauncherC0, has this script already been run?"
else
if mv "$defaultlauncher" "${defaultlauncher}_bkp" && ln -s LauncherC0.exe "$defaultlauncher"; then
log_info "symlinked $gamecode launcher"
else
log_error "unknown error occurred symlinking $gamecode launcher"
zenity_error "An unknown error occurred while applying launcher fix to $gamename. Execution will continue; consult the logs for details."
fi
fi
popd
}
# Undo the launcher fix for uninstallation
function undo_launcherfix() {
# usage: undo_launcherfix
log_info "undoing $gamecode launcher fix ..."
if ! pushd "$gamedir"; then
log_error "unable to cd into game directory '$gamedir'"
zenity_error "An error occurred accessing game directory '$gamedir' while undoing launcher fix, check the logs for details."
return 1
fi
local defaultlauncher
case $gamecode in
sg) defaultlauncher=Launcher.exe ;;
cc | sg0) defaultlauncher=launcher.exe ;;
*)
log_error "$gamecode does not have a launcherfix, nothing to undo. how?"
popd
return 3 ;;
esac
if [[ ! ( -L $defaultlauncher && -f ${defaultlauncher}_bkp && -f LauncherC0.exe ) ]]; then
log_warn "expected files/symlink in '$gamedir' were not present, unable to unapply launcher fix"
ls -l "$defaultlauncher"{,_bkp} LauncherC0.exe >&2
popd
return 2
else
if unlink "$defaultlauncher" && mv "${defaultlauncher}_bkp" "$defaultlauncher"; then
log_info "$gamecode launcher restored to original"
else
log_error "unknown error occurred undoing $gamecode launcher symlink"
zenity_error "An unexpected error occurred undoing launcher fix for $gamename while uninstalling. Check the logs for details."
fi
fi
popd
}
function install_patch() {
# usage: install_patch <patch dir>
## patch dir validation ##
local arg_patchdir
if $is_gui; then
arg_patchdir=$(zenity --file-selection --title "Choose $gamename CoZ Patch Directory" \
--directory --filename "$HOME/Downloads")
local zen_ret=$?
log_debug "zen_ret in install_patch = $zen_ret"
case $zen_ret in
1) start_screen ;;
-1)
log_fatal "An unexpected Zenity error has occurred in install_patch()."
exit 42
;;
esac
else
arg_patchdir=$1
fi
# Make sure the patch directory ($arg_patchdir) is valid.
# "Valid" here means:
# (1) it exists, and
# (2) it contains the expected patch EXE file to execute
if [[ ! -d $arg_patchdir ]]; then
log_fatal "directory '$arg_patchdir' does not exist"
zenity_error "Specified directory '$arg_patchdir' does not exist. Please try again."
exit 1
fi
if [[ ! -f "$arg_patchdir/$patch_exe" ]]; then
log_fatal "expected patch EXE '$patch_exe' does not exist within directory '$arg_patchdir'"
zenity_error "Directory '$arg_patchdir' does not contain expected EXE '$patch_exe', please try again. Make sure to select the extracted CoZ patch folder containing this file."
exit 1
fi
local has_steamgrid=false
if [[ -d "$arg_patchdir/STEAMGRID" ]]; then
has_steamgrid=true
log_info "using custom SteamGrid images ..."
fi
# Since we're running `cd` from within protontricks, we need to get the absolute
# path to the patch directory. Relative paths won't work for this since the
# shell invoked by `protontricks -c` sets its CWD to the game's directory.
# Prefer `realpath` to do the job, but if it's not available then get it by
# concatenating the user's CWD and the relative path. Simple testing shows that
# this hack does not work with the double-dot (..) on Flatpak Protontricks.
local patch_dir=$arg_patchdir
# only relative if it doesn't start with '/'
if [[ ! $arg_patchdir =~ ^/ ]]; then
log_warn "got relative path for patch directory"
# the '!' catches if realpath doesn't exist or some other permission error
if ! patch_dir=$(realpath "$arg_patchdir"); then
log_error "error using 'realpath' to set absolute path patch directory"
log_warn "attempting to set absolute path manually, this might cause issues ..."
patch_dir="$(pwd)/$arg_patchdir"
fi
fi
## game patching ##
log_info "patching $gamename ..."
zenity --timeout 10 --info --title 'Info' \
--text "$(printf 'Running patcher ...\n(This will disappear in 10 seconds)')" &
# Flatpak Protontricks has to be given access to the game's Steam folder to
# make changes. On Deck this is (hopefully) as easy as giving it access to all
# of its mounts and partitions; on PC, this could involve some tricky parsing
# of VDF files to give it access to different library folders.
# TODO: parse VDF files to give it access to different library folders. For
# now, FP Protontricks gives the user a prompt telling it which folder to give
# access to anyway, so it's not too big of an issue as long as the user can
# (a) read, and (b) copy and paste a single command.
compat_mts=
if $is_deck; then
flatpak override --user --filesystem=/run/media/ "$ptx_flatpak"
compat_mts="STEAM_COMPAT_MOUNTS=/run/media/"
fi
$is_flatpak && flatpak override --user --filesystem="$patch_dir" "$ptx_flatpak"
local ptx_winecmd="$ptx_cmd -c 'cd \"$patch_dir\" && $compat_mts wine $patch_exe' $appid"
if $mode_filelog; then
ptx_winecmd+=" > ${logname}-wine.log 2>&1"
fi
log_info "running command: \`$ptx_winecmd\`"
# this performs the magic
if ! eval "$ptx_winecmd"; then
log_error "patch installation exited with nonzero status"
zenity_error "Patch installation exited with a nonzero status. Script execution will continue; be wary of errors and check the output for information."
else
log_info "patch installation finished, no errors signaled."
fi
test -t 0 && stty sane # band-aid for newline wonkiness that wine sometimes creates
$has_steamgrid && copy_steamgrid "$patch_dir"
# Currently known launcher issues exist with s;g and c;c
[[ $gamecode == sg || $gamecode == cc || $gamecode == sg0 ]] && apply_launcherfix
log_info 'Success! Completed without any script-breaking issues. Enjoy the game.'
zenity --info --title 'Polyversal Success!' \
--text "Patch installation for $gamename finished. Please verify that the patch is working in case anything went wrong under the hood. Enjoy the game!"
}
function uninstall_patch() {
# usage: uninstall_patch
zenity --timeout 2 --info --title ':(' --text ':(' &
log_info "uninstalling $gamename patch ..."
zenity --timeout 10 --info --title 'Info' \
--text "$(printf 'Running patch uninstaller ...\n(This will disappear in 10 seconds)')" &
# make sure the uninstaller actually exists
local uninstall_exe='nguninstall.exe'
if [[ ! -f $gamedir/$uninstall_exe ]]; then
log_error "$uninstall_exe not present in game directory '$gamedir', can't run anything"
zenity_error "The CoZ uninstaller EXE was not present in the game directory '$gamedir'. Are you sure the patch been installed for this game?"
exit 1
fi
# $STEAM_APP_PATH is an env variable set by protontricks when it spawns its subshell, so we have to escape the $ lest
# it be interpreted by this script and always evaluate to empty
local ptx_winecmd="$ptx_cmd -c 'cd \"\$STEAM_APP_PATH\" && $compat_mts wine $uninstall_exe' $appid"
if $mode_filelog; then
ptx_winecmd+=" > ${logname}-wine.log 2>&1"
fi
log_info "running command: \`$ptx_winecmd\`"
[[ $gamecode == sg || $gamecode == cc || $gamecode == sg0 ]] && undo_launcherfix
if ! eval "$ptx_winecmd"; then
log_error "patch uninstallation exited with nonzero status"
zenity_error "Patch uninstallation exited with a nonzero status."
else
log_info "patch uninstallation complete, no errors signaled."
fi
test -t 0 && stty sane
log_info 'Success! Uninstalled without any script-breaking issues.'
zenity --info --title 'Polyversal Success!' \
--text "$gamename CoZ patch uninstalled. Please verify that the game works as you expect in case anything went wrong under the hood. We hope you re-install soon :D"
}
function check_update() {
# usage: check_update
local curl_out
if ! curl_out=$(
curl -SsL -m 10 \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"$URL_API_LATEST"
); then
log_error "cURL encountered an error retrieving the latest script version from GitHub"
return 1
fi
local latest_version
if ! latest_version=$(printf %s "$curl_out" | grep '"tag_name"' | grep -oE '[[:digit:]]+(\.[[:digit:]]+){2}'); then
log_error "GitHub version output differed from expected format"
log_debug "cURL output: $curl_out"
return 2
fi
local newer_version
newer_version=$(printf "%s\n%s" "$latest_version" "$VERSION" | sort -rV | head -n 1)
if [[ $newer_version == "$VERSION" ]]; then
log_info "polyversal is up-to-date"
return 0
fi
log_warn "Polyversal Patcher is out of date! using v$VERSION, you can download v$newer_version from $URL_RELEASES"
zenity --info --title 'New Update Available!' \
--text "A new version of the Polyversal Patcher (v$newer_version) is available! You can download it from <a href=\"$URL_RELEASES\">the releases page</a>."
}
function nuke_prefix() {
# usage: nuke_prefix
# expect $gamedir to be something like:
# /path/to/steamroot/steamapps/common/CHAOS;HEAD NOAH
# so the prefix dir would be $gamedir/../../compatdata/1961950.
local pfx_relpath=$gamedir/../../compatdata/$appid
local pfxdir
# realpath might not exist, or it might encounter a 'Permission denied', so check for failure
if ! pfxdir=$(realpath "$pfx_relpath"); then
log_error "error using 'realpath' to get canonical path to compatdata folder"
log_warn "attempting to set pfx path manually, this might cause issues ..."
pfxdir=$pfx_relpath
fi
if [[ ! -d $pfxdir ]]; then
log_error "prefix directory '$pfxdir' does not exist. this is strange..."
zenity_warn "Prefix directory '$pfxdir' does not exist, unable to delete it."
return 2
elif ! rm -rf "$pfxdir"; then
log_error "an error occurred while deleting '$pfxdir'"
zenity_error "Something went wrong removing prefix directory '$pfxdir'. Check the logs for more information."
return 3
fi
log_info "$gamename's Proton prefix successfully nuked"
zenity --info --title 'Polyversal Success!' \
--text "Proton prefix '$pfxdir' for $gamename successfully deleted."
}
# these are NOT local, since it has to be rm'd by a trap
curl_fifo=
proton_tar_name=
dl_proc=
function download_ac_proton() {
# usage: download_ac_proton
proton_tar_name="$DATADIR/acge-dl-TEMP-$(iso_date).tar.gz"
trap 'cleanup_proton_install' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # note: I don't know what I'm doing. these just seem like the right signals to trap
# CLI is so much easier jfc
if ! $is_gui; then
if ! curl -Lo "$proton_tar_name" "$AC_PROTON_URL"; then
log_error "cURL error occurred while downloading custom proton"
exit 1
fi
log_info "downloaded custom proton build to $proton_tar_name"
return 0
fi
curl_fifo="$DATADIR/curl-fifo-$(iso_date)"
if ! mkfifo "$curl_fifo"; then
log_fatal "error occurred creating named pipe for cURL call"
zenity_error "An unexpected error occurred creating a named pipe while downloading the custom Proton build. Check the logs for details."
exit 1
fi
if $mode_debug; then
# show cURL output for debugging
curl -Lo "$proton_tar_name" "$AC_PROTON_URL" 2>&1 | tee "$curl_fifo" 1>&2 &
else
curl -Lo "$proton_tar_name" "$AC_PROTON_URL" > "$curl_fifo" 2>&1 &
fi
dl_proc=$!
(
echo '# Downloading custom Proton tarball...'
# https://www.unix.com/shell-programming-and-scripting/280143-function-run-progress-bar-zenity.html, post #3
# the `system("")` call apparently flushes the buffer after each awk so that the progress bar can actually move as curl outputs more lines
<"$curl_fifo" awk -v RS="\r" '$1 ~ /[0-9]/ { print $1 ; system("") }'
echo "# Download finished! Click 'OK' to begin installation."
) | zenity --progress --cancel-label "Cancel download" --percentage 0 --title "Custom Proton Download" --width 400
local zen_ret="${PIPESTATUS[1]}"
log_debug "zenity return for the cURL monster = $zen_ret"
# I suppose theoretically we *should* care if something went horribly wrong with Zenity, but if the download completed
# then we'll just be pragmatic and not.
case $zen_ret in
-1)
kill $dl_proc
log_fatal "unexpected Zenity error occurred. proton download canceled as it's unclear if it succeeded"
# seems kinda weird to spit out a Zenity error using Zenity but idk
zenity_error "An unexpected error occurred in Zenity, and the Proton download could not be completed. Check the logs for more info."
exit 1
;;
1)
kill $dl_proc
log_info "stopped proton download due to pressing 'cancel' button"
zenity --info --title 'Proton Download Canceled' \
--text 'Custom A;C Proton-GE download has been canceled.'
exit 1
;;
esac
wait $dl_proc
local curl_ret=$?
dl_proc= # so that it doesn't get cleaned up by the trap function
if [[ $curl_ret != 0 ]]; then
log_error "cURL returned an error while downloading proton, exit status $curl_ret"
zenity_error "An error occured while downloading the custom Proton build. Check the logs for more info."
exit 1
fi
log_info "custom proton tarball successfully downloaded to $proton_tar_name. probably."
}
function install_ac_proton() {
# usage: install_ac_proton
local compatdir=$STEAMROOT/compatibilitytools.d
log_debug "compatibilitytools directory = '$compatdir'"
if [[ ! -d $compatdir ]]; then
log_warn "compatibilitytools.d doesn't exist... something might be going very wrong"
# if $STEAMROOT doesn't exist then we'll just let `tar` handle the failure
[[ -d $STEAMROOT ]] && mkdir -p "$STEAMROOT/compatibilitytools.d"
fi
# try and protect them from re-downloading and extracting it
local protondir=$compatdir/$AC_PROTON_DIRNAME
local prev_installed=false
if [[ -d $protondir ]]; then
log_warn "'$compatdir' already contains directory '$AC_PROTON_DIRNAME'"
local answer=
if $is_gui; then
if zenity --question --title 'Existing Custom Proton Detected' \
--text "It looks like you already have the custom A;C Proton-GE build installed in your Steam's compatibility tools. Do you want to download and re-install it anyway?"
then
answer='y'
log_info "selected re-installation via GUI"
else
log_info "selected not to re-install via GUI"
start_screen
fi
else
while [[ $(tolower "$answer") != 'y' && $(tolower "$answer") != 'n' ]]; do
read -rp "It looks like the custom Proton-GE build '$AC_PROTON_DIRNAME' already exists in your installed compatibility tools.
Download and install it anyway? (y/n): " answer
done
log_info "got answer '$answer' from CLI input"
fi
[[ $answer != 'y' ]] && exit 0
prev_installed=true
fi
download_ac_proton
local tar_opts='-xzf'
local tar_tail=
if $mode_filelog; then
tar_opts='-xvzf'
tar_tail='> "${logname}-untar.log" 2>&1'
fi
local tar_cmd="tar $tar_opts \"$proton_tar_name\" -C \"$DATADIR\" $tar_tail"
log_debug "running untar command: \`$tar_cmd\`"
eval "$tar_cmd" &
local tar_proc=$!
log_info "extracting A;C proton build $proton_tar_name to $protondir ..."
zenity --info --timeout 20 --title "Polyversal Extraction" \
--text "Extracting A;C Proton build ...\n(This might take a short while. A new window will appear upon completion.)" &
local zen_proc=$!