AM2R-Autopatcher-Linux/patcher.sh
Ivv 3e2121c854
Patch out insecure dependencies and add NixOS instructions (#30)
* Patch out insecure dependencies and add NixOS instructions

This commit uses `patchelf` to remove the dependency on OpenSSL 1.0,
which is outdated and considered insecure. It replaces this dependency
with libcurl, of which the latest version works just fine.

This should make sure that any distrobution can run the generated binary
out of the box, without needing a dynamically linked placeholder.

I also noticed the game failed to start on my AMD machine, and after looking
through Mesa's Gitlab I found a workaround using an environment variable.
A wrapper bash script that sets this variable and executes the `runner` binary
is created to take care of this automatically.

This commit adds instructions on how to run the game on NixOS as well.

* readme: gramar fixes

* patcher: fix syntax error

* readme: add some codeblocks
2022-08-14 23:24:49 +02:00

365 lines
12 KiB
Bash
Executable file

#!/usr/bin/env bash
# exit on any error to avoid showing everything was successfull even though it wasnt
set -eou pipefail
# Patching user variables
OSCHOICE="linux"
AM2RZIP=""
HQMUSIC=false
SYSTEMWIDE=false
APPIMAGE=false
PREFIX=""
# Patching internal variables
# Since people are likely to double click on this, I need a way to get the script_dir
dirResult="$(dirname '${BASH_SOURCE[0]}')"
SCRIPT_DIR="$(realpath $dirResult)"
VERSION="1.5.5"
GAMEDIR="${SCRIPT_DIR}/am2r_${VERSION}"
RESOURCES="${GAMEDIR}/assets"
checkInstalled()
{
local command="$1"
# Check wether a command is installed
if [ ! -x "$(command -v "${command}")" ] ; then
>&2 echo "${command} is not installed! Please install '${command}' from your local package manager!"
exit 1
fi
}
patch_am2r ()
{
checkInstalled "unzip"
checkInstalled "sed"
checkInstalled "xdelta3"
# Set prefix to default value if empty
if [ -z "$PREFIX" ]; then
if [ "$SYSTEMWIDE" = true ] && [ ! "$OSCHOICE" = "android" ]; then
PREFIX="/usr/local"
else
PREFIX="$SCRIPT_DIR/am2r_${VERSION}"
fi
fi
GAMEDIR=$(mktemp -d -u)
trap "rm -rf $GAMEDIR" EXIT
RESOURCES="${GAMEDIR}/assets"
local output=""
# We need to do variable adjustments based on the prefix
if [ "$SYSTEMWIDE" = true ]; then
output="${PREFIX}/opt/am2r"
else
output="${PREFIX}"
fi
# Create necessary directories
mkdir -p "$GAMEDIR" "$RESOURCES"
# Check for AM2R_11
if [[ -f "$AM2RZIP" ]]; then
echo "AM2R_11.zip found!"
unzip -q "$AM2RZIP" -d "$GAMEDIR"
elif [[ -d "$AM2RZIP" ]]; then
echo "AM2R_11 folder found!"
cp -R "$AM2RZIP" "$GAMEDIR"
else
echo "AM2R_11 not found! Please place AM2R_11.zip (case sensitive) into \"$SCRIPT_DIR\" or provide it via command line arguments and try again."
exit 1
fi
# Check for which OS we patch
if [ "$OSCHOICE" = "linux" ]; then
checkInstalled "patchelf"
echo "Patching for Linux..."
echo "Applying AM2R xdelta patch..."
xdelta3 -dfs "$GAMEDIR/AM2R.exe" "$SCRIPT_DIR/data/AM2R.xdelta" "$GAMEDIR/runner"
echo "Applying asset xdelta patch..."
xdelta3 -dfs "$GAMEDIR/data.win" "$SCRIPT_DIR/data/game.xdelta" "$RESOURCES/game.unx"
echo "Cleaning up residual AM2R 1.1 files..."
rm "$GAMEDIR/AM2R.exe" "$GAMEDIR/data.win" "$GAMEDIR/D3DX9_43.dll"
echo "Formatting game directory..."
# This won't move the runner, because it doesn't have a dot
mv $GAMEDIR/*.* "$RESOURCES/"
echo "Installing new datafiles..."
cp -R "$SCRIPT_DIR"/data/files_to_copy/* "$RESOURCES/"
if [ "$HQMUSIC" = true ]; then
echo "Copying HQ music..."
cp "$SCRIPT_DIR"/data/HDR_HQ_in-game_music/*.ogg "$RESOURCES/"
fi
# On Unix the music filenames need to be converted to lowercase.
find "$RESOURCES" -type f -exec bash -c '
target="{}"
# Only files are meant to be modified, not the folders they are contained in.
cd "$(dirname "${target}")"
target="$(basename "${target}")"
# Convert the filename to lowercase, if required.
! [[ "${target}" = "${target,,}" ]] && mv "${target}" "${target,,}"
' \;
# GameMaker games (like AMR2) link to OpenSSL 1.0.0, which is outdated and insecure.
# When attempting to link to newer versions, an error is raised at runtime claiming it cannot find
# the outdated version of OpenSSL, even though it has been patched to link to the newer version.
# After replacing it with libcurl, versioning is ignored, and the binary starts just fine.
echo "Patching insecure OpenSSL dependency with libcurl..."
patchelf "$GAMEDIR/runner" \
--replace-needed "libcrypto.so.1.0.0" "libcurl.so" \
--replace-needed "libssl.so.1.0.0" "libcurl.so"
# An environment variable needs to be set on Mesa to avoid a race related to multithreaded shader compilation.
# To do this, we move the original executable to a hidden file, and create a bash script with the needed variable in place of the original.
echo "Creating wrapper script to fix Mesa support..."
mv "$GAMEDIR/runner" "$GAMEDIR/.runner-unwrapped"
echo '
#!/usr/bin/env bash
# This environment variable fixes Mesa support. If another driver is used this should not do anything.
# See https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/4181 for more information.
radeonsi_sync_compile="true" exec "$(dirname "${BASH_SOURCE[0]}")/.runner-unwrapped" "$@"
' > "$GAMEDIR/runner"
chmod +x "$GAMEDIR/runner" "$GAMEDIR/.runner-unwrapped"
# Remove old lang folder
rm -R "$GAMEDIR"/lang
chmod +x "$GAMEDIR/runner"
if [ "$SYSTEMWIDE" = true ]; then
mkdir -p "$PREFIX/bin"
if [ "$APPIMAGE" = true ]; then
ln -sf "$output/AM2R.AppImage" "$PREFIX/bin/am2r"
else
ln -sf "$output/runner" "$PREFIX/bin/am2r"
fi
fi
# Create .desktop file
echo "Creating desktop file..."
cp "$SCRIPT_DIR/data/files_to_copy/icon.png" "$GAMEDIR/icon.png"
local desktopPath="$GAMEDIR/AM2R.desktop"
# For systemwide we need to a) write to desktop file to a different directory
# and b) copy the icon to the proper XDG icon dir and c) use proper icon reference in desktop file
if [ "$SYSTEMWIDE" = true ]; then
mkdir -p "$PREFIX/share/applications"
desktopPath="$PREFIX/share/applications/AM2R.desktop"
mkdir -p "$PREFIX/share/icons/hicolor/72x72/apps"
mv "$GAMEDIR/icon.png" "$PREFIX/share/icons/hicolor/72x72/apps/am2r.icon"
fi
cp "$SCRIPT_DIR/DesktopTemplate" "$desktopPath"
# Replace with proper path
sed -i "s#\[REPLACE\]#$GAMEDIR#" "$desktopPath"
if [ "$SYSTEMWIDE" = true ]; then
sed -i "s#Icon=$GAMEDIR/icon.png#Icon=am2r#" "$desktopPath"
fi
if [ "$APPIMAGE" = false ]; then
# For non-appimage, the desktop file should point to runner
sed -i "s/AM2R.AppImage/runner/" "$desktopPath"
else
# Create AppImage
echo "Creating AppImage..."
# Dry/unsafe run with mktemp, as otherwise cp below will copy into the dir, rather than as the dir
local tempAppDir=$(mktemp -d -u)
trap "rm -rf $tempAppDir" EXIT
cp -R --preserve=links "$SCRIPT_DIR/data/AM2R.AppDir" $tempAppDir
mkdir -p "$tempAppDir/bin"
mv "$GAMEDIR"/* "$tempAppDir/bin"
ARCH=x86_64 "$SCRIPT_DIR/utilities/appimagetool-x86_64.AppImage" -n $tempAppDir "$GAMEDIR/AM2R.AppImage" 2> /dev/null
if [ "$SYSTEMWIDE" = false ] ; then
mv "$tempAppDir/bin/icon.png" "$GAMEDIR/icon.png"
fi
# For systemwide, we already moved the desktop file to prefix/share earlier above.
if [ "$SYSTEMWIDE" = false ]; then
mv "$tempAppDir/bin/AM2R.desktop" "$desktopPath"
fi
rm -R "$tempAppDir"
fi
elif [ "$OSCHOICE" = "android" ]; then
checkInstalled "java"
echo "Creating an APK for Android..."
local apktoolPath="$SCRIPT_DIR/utilities/android/apktool.jar"
local uberPath="$SCRIPT_DIR/utilities/android/uber-apk-signer.jar"
echo "Applying Android patch..."
xdelta3 -dfs "$GAMEDIR/data.win" "$SCRIPT_DIR/data/droid.xdelta" "$GAMEDIR/game.droid"
rm -rf "$GAMEDIR/D3DX9_43.dll" "$GAMEDIR/AM2R.exe" "$GAMEDIR/data.win" "$GAMEDIR/assets"
if [ -f "$SCRIPT_DIR/data/android/AM2R.ini" ]; then
cp -p "$SCRIPT_DIR/data/android/AM2R.ini" "$GAMEDIR/"
fi
echo "Installing new datafiles..."
cp -R "$SCRIPT_DIR"/data/files_to_copy/* "$GAMEDIR"
if [ "$HQMUSIC" = true ]; then
cp "$SCRIPT_DIR"/data/HDR_HQ_in-game_music/*.ogg "$GAMEDIR"
fi
echo "Packaging APK..."
# decompile the apk
# Dry/unsafe run with mktemp, as otherwise apktool below will GAMEDIR into the dir, rather than as the dir
local tempApkDir=$(mktemp -d -u)
trap "rm -rf $tempApkDir" EXIT
java -jar "$apktoolPath" -q d -f "$SCRIPT_DIR/data/android/AM2RWrapper.apk" -o "$tempApkDir"
mv "$GAMEDIR/"* "$tempApkDir/assets"
echo "Editing apktool.yml..."
sed -i "s/doNotCompress:/doNotCompress:\n- ogg/" "$tempApkDir/apktool.yml"
# build apk
echo "Building APK..."
java -jar "$apktoolPath" -q b "$tempApkDir" -o "$tempApkDir/AM2RWrapper.apk"
echo "Signing APK..."
java -jar "$uberPath" -a "$tempApkDir/AM2RWrapper.apk"
# Move APK
mv "$tempApkDir/AM2RWrapper-aligned-debugSigned.apk" "$PREFIX/AndroidM2R_"$VERSION"-signed.apk"
else
>&2 echo "Invalid OS \"$OSCHOICE\"! Cannot patch anything!"
exit 1
fi
# Put everything from temp directory into the proper output directory
# Moving does *not* work, as mv doesn't allow to overwrite existing directories
mkdir -p $output
cp -r -f $GAMEDIR/* $output
echo ""
echo "The operation was completed successfully. See you next mission!"
return 0
}
# ----
# Main function starts here
# ---
main ()
{
echo "-------------------------------------------"
echo ""
echo "AM2R ${VERSION} Shell Autopatching Utility"
echo ""
echo "-------------------------------------------"
if (( $# <= 0 )); then
APPIMAGE=true
AM2RZIP="$SCRIPT_DIR/AM2R_11.zip"
local input=""
echo "Running in interactive mode. For a full list of arguments, run the script with \"--help\""
echo "Select your patch type:"
echo ""
echo "1 - Linux"
echo "2 - Android"
echo ""
echo "Awaiting input:"
read -n1 input
echo ""
if [[ "${input}" = "1" ]]; then
OSCHOICE="linux"
elif [[ "${input}" = "2" ]]; then
OSCHOICE="android"
else
>&2 echo "Invalid OS!"
exit 1
fi
echo "Install high quality in-game music? Increases filesize by 194 MB!"
echo "[y/n]"
read -n1 input
echo ""
if [[ "${input,,}" = "y" ]]; then
HQMUSIC=true
fi
if [ $OSCHOICE = "linux" ]; then
echo "Do you want to install AM2R systemwide?"
echo "[y/n]"
read -n1 input
echo ""
if [[ "${input,,}" = "y" ]]; then
SYSTEMWIDE=true
fi
fi
patch_am2r
local result=$?
echo "Press any key to quit..."
read -s -n1 INPUT
exit $result
fi
while (( $# > 0 )); do
case $1 in
-s|--os)
OSCHOICE="$2"
shift 2 # past argument and value
;;
-m|--hqmusic)
HQMUSIC=true
shift # past argument
;;
-w|--systemwide)
SYSTEMWIDE=true
shift # past argument
;;
-a|--appimage)
APPIMAGE=true
shift # past argument
;;
-p|--prefix)
PREFIX=$(realpath "$2")
shift 2 # past argument and value
;;
-z|--am2rzip)
AM2RZIP="$2"
shift 2 # past argument and value
;;
-h|--help)
echo -e "-s, --os\t\t\tThe operating system to patch to. Valid are \"linux\" and \"android\". Default is \"linux\""
echo -e "-m, --hqmusic\t\t\tIf provided, high quality music will be used, otherwise low quality music will be used instead."
echo -e "-w, --systemwide\t\tIf provided, Linux will get installed systemwide, otherwise Linux will get installed portably. Has no effect on Android."
echo -e "-a, --appimage\t\t\tIf provided, an AppImage will get generated, otherwise the raw binary will get generated instead. Has no effect on Android."
echo -e "-p, --prefix\t\t\tThe prefix used for patching operations. Default for systemwide is \"/usr/local\" and for non-systemwide \"<directory where this script resides>/am2r_<VersionNumber>\". As systemwide is ignored on Android, for Android this will always default to the latter option."
echo -e "-z, --am2rzip\t\t\tThe path to the AM2R_11 zip or directory. Default is \"<directory where the script resides>/AM2R_11.zip\""
exit 0
;;
*)
>&2 echo "Unknown option $1"
exit 1
;;
esac
done
# restore positional parameters
set -- "${POSITIONAL_ARGS[@]}"
# check if necessary variables are provided
if [ -z "$OSCHOICE" ]; then
>&2 echo "Missing argument! The OS needs to be provided via the \"--os\" flag! For further info use --help."
exit 1
fi
if [ -z "$AM2RZIP" ]; then
AM2RZIP="$SCRIPT_DIR/AM2R_11.zip"
fi
patch_am2r
}
main "$@"