[BACK]Return to fnaify CVS log [TXT][DIR] Up to [local] / projects / fnaify

File: [local] / projects / fnaify / fnaify (download)

Revision 1.209, Sat Apr 30 22:57:10 2022 UTC (8 weeks, 2 days ago) by thfr
Branch: MAIN
CVS Tags: HEAD
Changes since 1.208: +1 -1 lines

Terraria doesn't need MONO_FORCE_COMPAT=1 anymore

#!/usr/bin/env ksh

set -o errexit
set -o pipefail

FNAIFY_VERSION=3.0

#########################################################################
# fnaify 								#
# ======								#
#									#
# Run FNA/XNA games on OpenBSD						#
#									#
# Copyright (c) 2017-2020 Thomas Frohwein				#
# portability fixes by Mariusz Zaborski (oshogbo)			#
#									#
# FNA is a reimplementation of the Microsoft XNA Game Studio 4.0	#
# Refresh libraries.							#
# Thanks to the great work by Ethan Lee (flibitijibibo) games using FNA	#
# are highly portable and can even run on OpenBSD. Please refer to	#
# https://fna-xna.github.io/ for more information about FNA.		#
#									#
# License: ISC								#
#########################################################################

# TODO:
# - fix TODOs
# - fails to detect XNA in Streets of Fury EX because of whitespace; resolved after renaming .exe
# - update the creator for dllmap file; based on fnaify.dllmap.config
# - mention package fnaify-extralibs in documentation
# - add option to add runtime exe_flags to run mode
# - AVNT: find a way to have Steam deactivated for this one in libSteamworksNative.so
# - add flag to create log file (for fnaify, as well as the output from the game)
# - document '--' to end getopts parsing; can make it clear when to pass remaining args to game
# - add check for ffmpeg if needed

USAGE="
Usage:

$(basename "$0") [-i|-y] [-hsv] [-c configfile] [-D depdir] [-d gamedir] [-f frameworkfile | -F frameworkversion] [-m monopath] [userflags]

-c: specify config file for dllmap
-D: add directory to native library loader
-d: game directory
-F: framework version
-f: framework file
-h: print usage information
-i: interactive mode
-m: add directories to MONO_PATH
-n: skip the library check
-s: force (re-)running setup
-y: non-interactive mode; automatically replies 'yes' to all recommended choices
-V: display version
-v: verbose output

<userflags> are optional. These arguments are passed to the game runtime.
"

depdir="/usr/local/lib:/usr/X11R6/lib"	# /usr/X11R6/lib is location of libfreetype.so on OpenBSD
fnadir="/usr/local/share/FNA"
gamedir="$PWD"
interaction=n	# y|n|i: yes to all, no to all, interactive
debug=
licenses=
gameconfig=
force_setup=0
nolibcheck=0
frameworkfile=
setup_frameworkfile=
frameworkversion=
setup_frameworkversion=
frameworkmajor=
frameworkminor=
userflags=
monopath="${MONO_PATH}"
netstub_commit=e7d890e0ede0caa9e76ef37af6070344e3ab0abf
newline='
'
SAVEIFS=$IFS
exefile=
nexefile=0	# TODO: is nexefile really needed?
my_exe=""
exe_flags=""
exe_env=""
fna_warning=0
nlog_warning=0
set -A needlibarray	# array that will hold the names of needed libraries
FNAIFY_DEBUG=false

if [ -d "/usr/local/lib/steamworks-nosteam" ] ; then
	monopath="${monopath}:/usr/local/lib/steamworks-nosteam"
	depdir="/usr/local/lib/steamworks-nosteam:$depdir"
fi
if [ -d "$fnadir" ] ; then
	monopath="${monopath}:${fnadir}"
fi
if [ -d "/usr/local/share/steamstubs" ]; then
	monopath="${monopath}:/usr/local/share/steamstubs"
fi
if [ -d "/usr/local/share/fnaify-libs" ]; then
	depdir="/usr/local/share/fnaify-libs:$depdir"
fi

# array of lib names to ignore for the configuration checking
ignoredarray="
FarseerPhysics.Portable.xml
libCommunityExpressSW.so
libCSteamworks.so
libGalaxy64.so
libGalaxyCSharpGlue.so
libGalaxy.so
libParisSteam.so
libSkiaSharp.so
libSteamWrapper.so
libXNAFileDialog.so
libXNAWebRenderer.so
libsteam_api.so
libcef.so
libfmod.so
libfmodex.so
libfmodstudio.so
liblua53.so
libtiny_jpeg.so
steamwrapper.so
steam_appid.txt
"

# array of mono files that need to be removed from the game folder
monofilearray="
CSteamworks.dll
I18N.CJK.dll
I18N.MidEast.dll
I18N.Other.dll
I18N.Rare.dll
I18N.West.dll
I18N.dll
Microsoft.CSharp.dll
Mono.CSharp.dll
Mono.Posix.dll
Mono.Security.dll
Steam.dll
System.Configuration.dll
System.Configuration.Install.dll
System.Core.dll
System.Data.dll
System.Design.dll
System.Drawing.dll
System.IO.Compression.FileSystem.dll
System.IO.Compression.dll
System.Management.dll
System.Net.dll
System.Numerics.dll
System.Runtime.Serialization.dll
System.Security.dll
System.ServiceModel.dll
System.Transactions.dll
System.Web.Extensions.dll
System.Web.Http.dll
System.Web.Services.dll
System.Web.dll
System.Windows.Forms.dll
System.Xml.Linq.dll
System.Xml.dll
System.dll
WindowsBase.dll
libMonoPosixHelper.so
libMonoPosixHelper.so.x86
libMonoPosixHelper.so.x86_64
libSteamworksNative.so
monoconfig
monomachineconfig
mscorlib.dll
"

configbasearray="
BlitNet.dll
CDGEngine.dll
CommunityExpress.dll
FMOD.dll
FNA.dll
GameClasses.dll
MonoGame.Framework.dll
Nuclex.Input.dll
OpenAL-CS.dll
SDL2#.dll
SDL2-CS.dll
"

debug_echo()
{
	[ "$FNAIFY_DEBUG" = "false" ] && return
	if [ "${1}" = '-n' ]; then
		printf '%b' "$2"
	else
		printf '%b\n' "$1"
	fi
}

printdash()
{
	string=
	string="$*"
	if [ -z "$string" ]; then
		return
	fi
	printf '%b\n' "$string"
	c=1
	dashes=
	while [ $c -lt ${#string} ]
	do
		dashes="${dashes}-"
		# $((...)) for arithmetic substitution
		c=$((c+1))
	done
	echo "$dashes"
}

inarray() # check if $1 is in array $2
{
	firstarg="$1"
	shift 1
	if echo "$*" | grep -q "$firstarg"
	then
		echo "true"
	else
		echo "false"
	fi
}

# function to find the latest present library. Return -1 if none found.
# $1 is the basename of the library without the version suffix (e.g. /usr/lib/libc.so)
# it will return the filename of the latest library version (e.g. /usr/lib/libc.so.96.0)
latest_syslib()
{
	if [ -z "$1" ] ; then
		return 1
	fi
	find "$(dirname "$1")" -maxdepth 1 -name "$(basename "$1")*" | tail -1
}

trunklibnam() # truncate library name
{
	libnam="$1"
	libnam="$(echo "$libnam" | sed -n -E "s/[\.0-9]*$//p")"
	echo "$libnam" | sed -E "s/(libSDL2[^-]*)-2\.0(\.so.*)/\1\2/"
}

validlib() # returns "false" if $1 is in ignoredarray, otherwise "true"
{
	libnam="$(trunklibnam "$1")"
	if echo \""$ignoredarray"\" | grep -q "$libnam"; then
		echo "false"
	elif echo \""$libnam"\" | grep -q '\.dll[ \t]*'; then
		echo "false"
	else
		echo "true"
	fi
}

symlink_mg_libs()
{
	set -A mg_subdirs x64 x86
	debug_echo "\nChecking for MonoGame x64/x86 dir to create symlinks"
	
	if [ ! -e "$gamedir/x64" ] && [ ! -e "$gamedir/x86" ] ; then
		debug_echo "...Couldn't find directories x64 or x86 in $gamedir\n"
	else
		for d in "${mg_subdirs[@]}" ; do
			debug_echo "\nProcessing library directory $gamedir/$d"
			for file in $gamedir/$d; do
				# sort out libs that need to be ignored
				if [ "$(validlib "$file")" = "true" ]
				then
					debug_echo "\tignored file: $file"
					continue
				fi
				debug_echo "\tfound library file: $file. Replacing with symlink..."
				trunk=$(trunklibnam "$file")
				rm "$gamedir/$d/$file"
				debug_echo -n "\t\tchecking for location of $trunk... "
				if [ -n "$(latest_syslib "/usr/local/lib/$trunk")" ] ; then
					debug_echo "found in /usr/local/lib!"
					trunkpath="/usr/local/lib/$trunk"
				elif [ -n "$(latest_syslib "/usr/X11R6/lib/$trunk")" ] ; then
					debug_echo "found in /usr/X11R6/lib!"
					trunkpath="/usr/X11R6/lib/$trunk"
				elif [ -n "$(latest_syslib "/usr/lib/$trunk")" ] ; then
					debug_echo "found in /usr/lib!"
					trunkpath="/usr/lib/$trunk"
				else
					debug_echo "NOT FOUND!!"
					trunkpath=
					printf "\n\t - ERROR: couldn't find system lib for %s\n" "$file"
				fi
				ln -s "$(latest_syslib "$trunkpath")" "$gamedir/$d/$file"
			done
			debug_echo "Done with library directory $gamedir/$d"
		done
		debug_echo "\nCreating libdl.so.2 symlink\n"
		[ ! -e "$gamedir/libdl.so.2" ] && \
			ln -s "$(latest_syslib /usr/lib/libc.so)" "$gamedir/libdl.so.2"
	fi
}

selectexe()
{
	debug_echo "\nexecutable assembly selection routine"
	IFS="
"
	for xfile in $(cd "$gamedir" && find . -maxdepth 1 -type f -iname "*.exe" -exec basename {} \; | sort)
	do
		if 	echo "$xfile" | ! grep -q vshost && \
			echo "$xfile" | ! grep -iq config && \
			echo "$xfile" | ! grep -q dotNetFx4 && \
			echo "$xfile" | ! grep -q Updater
		then
			exefile="$exefile$xfile:"
			nexefile=$((nexefile + 1))
		fi
	done
	IFS=$SAVEIFS
	if [ $nexefile -gt 1 ]; then
		if [ "$interaction" = "y" ] ; then
			# TODO: for now just pick the first .exe. Come up with
			#        better heuristic
			my_exe="$(echo "$exefile" | cut -f 1 -d ':')"
		else
			i=1
			while [ $i -le $nexefile ]; do
				echo "$i: $(echo "$exefile" | cut -f $i -d ':')"
				i=$((i + 1))
			done
			input_exe=0
			while	[ ${#input_exe} -gt 2 ] \
				|| ! echo "$input_exe" | grep -q '[1-9]' \
				|| [ $input_exe -gt $((i - 1)) ]
			do
				echo -n "Enter number of .exe file to run: "
				read -r input_exe
			done
			# NOTE: this only works for up to 9 candidate files
			my_exe="$(echo "$exefile" | cut -f "$input_exe" -d ':')"
		fi
	elif [ $nexefile -eq 1 ]; then
		my_exe="$exefile"
	else
		# TODO: there appear to be games without .exe, like Streets of Rage 4
		printf "ERROR: no .exe file found\n\n"
		exit 1
	fi
	my_exe="$(echo "$my_exe" | sed -E "s/[ \t]$//")" # trim trailing whitespace
}

check_gameconfig()
{
	if [ -z "$gameconfig" ] ; then
		if [ -f "$HOME/.config/fnaify/fnaify.dllmap.config" ] ; then
			gameconfig="$HOME/.config/fnaify/fnaify.dllmap.config"
			# check for outdated config file
			if [ "$(grep -m 1 "fnaify version $FNAIFY_VERSION" "$gameconfig")" = "" ] ; then
				echo "WARNING: $gameconfig appears to be out of date. It is strongly recommended to remove it and re-run $(basename "$0")."
			fi
		else
			if [ -f "$fnadir/FNA.dll.config" ] ; then
				gameconfig="$fnadir/FNA.dll.config"
			elif [ -f "/usr/local/share/fnaify/fnaify.dllmap.config" ] ; then
				gameconfig="/usr/local/share/fnaify/fnaify.dllmap.config"
			else	# in this case create ~/.config/fnaify/fnaify.dllmap.config
				gameconfig="$HOME/.config/fnaify/fnaify.dllmap.config"
				debug_echo "creating $gameconfig"
				mkdir -p ~/.config/fnaify
				cat <<EOF > "$gameconfig"
<!-- mono config file for fnaify -->
<!-- fnaify version 3.0 -->
<configuration>
	<dllmap dll="FAudio" target="libFAudio.so"/>
	<dllmap dll="MojoShader.dll" target="libmojoshader.so"/>
	<dllmap dll="SDL2.dll" target="libSDL2.so"/>
	<dllmap dll="SDL2_image.dll" target="libSDL2_image.so"/>
	<dllmap dll="SDL2_mixer.dll" target="libSDL2_mixer.so"/>
	<dllmap dll="SDL2_ttf.dll" target="libSDL2_ttf.so"/>
	<dllmap dll="freetype6" target="libfreetype.so" />
	<dllmap dll="freetype6.dll" target="libfreetype.so" />
	<dllmap dll="libtheorafile.dll" target="libtheorafile.so"/>
	<dllmap dll="libtheoraplay.dll" target="libtheoraplay.so"/>
	<dllmap dll="libvorbisfile.dll" target="libvorbisfile.so"/>
	<dllmap dll="libvorbisfile-3.dll" target="libvorbisfile.so"/>
	<dllmap dll="openal32.dll" target="libopenal.so"/>
	<dllmap dll="soft_oal.dll" target="libopenal.so"/>
	<dllmap dll="System.Native" target="libmono-native.so"/>
	<dllmap dll="System.Net.Security.Native" target="libmono-native.so"/>
	<dllmap dll="i:msvcrt" target="libc.so" os="!windows"/>
	<dllmap dll="i:msvcrt.dll" target="libc.so" os="!windows"/>
	<dllmap dll="msvcr100.dll" target="libc.so"/>

	<dllmap dll="i:CommunityExpressSW" target="libcestub.so"/>
	<dllmap dll="i:CommunityExpressSW.dll" target="libcestub.so"/>

	<dllmap dll="user32.dll">
		<dllentry dll="libc.so" name="GetWindowThreadProcessId" target="getthrid"/>
		<dllentry dll="libstubborn.so" name="SetWindowsHookEx" target="int_0"/>
		<dllentry dll="libstubborn.so" name="GetClipCursor" target="int_0"/>
		<dllentry dll="libstubborn.so" name="DestroyIcon" target="int_0"/>
	</dllmap>

	<dllmap dll="ntdll.dll">
		<dllentry dll="libstubborn.so" name="NtQueryInformationProcess" target="int_0"/>
	</dllmap>

	<dllmap dll="ArkSteamWrapper.dll">
		<dllentry dll="libstubborn.so" name="ArkSteamInit" target="int_0"/>
		<dllentry dll="libstubborn.so" name="ArkGetPlayerId" target="int_0"/>
	</dllmap>

	<dllmap dll="CSteamworks">
		<dllentry dll="libstubborn.so" name="InitSafe" target="int_0"/>
		<dllentry dll="libstubborn.so" name="RestartAppIfNecessary" target="int_0"/>
	</dllmap>

	<dllmap dll="discord-rpc">
		<dllentry dll="libstubborn.so" name="Initialize" target="int_0"/>
		<dllentry dll="libstubborn.so" name="Discord_Initialize" target="int_0"/>
		<dllentry dll="libstubborn.so" name="Discord_UpdatePresence" target="int_0"/>
		<dllentry dll="libstubborn.so" name="Discord_RunCallbacks" target="int_0"/>
	</dllmap>

	<dllmap dll="BrutallyUnfairDll.dll">
		<dllentry dll="libstubborn.so" name="loadSteamDll" target="int_0"/>
		<dllentry dll="libstubborn.so" name="initSteamAPI" target="int_0"/>
		<dllentry dll="libstubborn.so" name="GetModuleHandle" target="int_0"/>
	</dllmap>

	<dllmap dll="SteamworksNative">
		<dllentry dll="libstubborn.so" name="GetStat" target="int_0"/>
		<dllentry dll="libstubborn.so" name="Initialize" target="int_0"/>
		<dllentry dll="libstubborn.so" name="RunCallbacks" target="ptr_null"/>
		<dllentry dll="libstubborn.so" name="Services_RegisterManagedCallbacks" target="int_0"/>
	</dllmap>

	<dllmap dll="steamwrapper.dll">
		<dllentry dll="libstubborn.so" name="API_RunCallbacks" target="int_0"/>
		<dllentry dll="libstubborn.so" name="API_Init" target="int_0"/>
		<dllentry dll="libstubborn.so" name="API_Shutdown" target="int_0"/>
		<dllentry dll="libstubborn.so" name="Stats_getStat" target="int_0"/>
		<dllentry dll="libstubborn.so" name="RestartViaSteamIfNecessary" target="int_0"/>
	</dllmap>

	<dllmap dll="fmod_event.dll">
		<dllentry dll="libstubborn.so" name="FMOD_EventSystem_Create" target="int_0"/>
	</dllmap>

	<dllmap dll="kernel32">
		<dllentry dll="ld.so" name="LoadLibrary" target="dlopen"/>
	</dllmap>

	<!--<dllmap dll="SteamworksNative.dll" target="libSteamworksNative.so"/>-->

	<dllmap dll="SteamWrapper.dll">
		<dllentry dll="libstubborn.so" name="SteamWrapper_GetCurrentGameLanguage" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamWrapper_Init" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamWrapper_GetUserDataFolder" target="string_empty"/>
		<dllentry dll="libstubborn.so" name="SteamWrapper_RunCallbacks"	 target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamWrapper_LbUploadScore" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamWrapper_Shutdown" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamWrapper_SetAchievement" target="int_0"/>
	</dllmap>

	<dllmap dll="steam_api">
		<dllentry dll="libstubborn.so" name="SteamAPI_Init" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_RegisterCallback" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_GetHSteamUser" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_GetHSteamPipe" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamInternal_CreateInterface" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamUser" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamFriends" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamUtils" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamMatchmaking" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamMatchmakingServers" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamUserStats" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamApps" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamNetworking" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamRemoteStorage" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamScreenshots" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamGameSearch" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamHTTP" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamController" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamUGC" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamAppList" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamMusic" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamMusicRemote" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamHTMLSurface" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamInventory" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamVideo" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamParentalSettings" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamInput" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamClient_GetISteamParties" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamUser_GetSteamID" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamFriends_GetFriendPersonaName" target="string_empty"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamUserStats_RequestCurrentStats" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamApps_GetCurrentBetaName" target="string_empty"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamApps_BIsDlcInstalled" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamApps_GetLaunchCommandLine" target="string_empty"/>
		<dllentry dll="libstubborn.so" name="SteamUserStats" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamRemoteStorage" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamRemoteStorage_FileExists" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_RunCallbacks" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamRemoteStorage_FileWrite" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_Shutdown" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamUserStats_GetAchievement" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamUserStats_SetAchievement" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamUserStats_StoreStats" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_ISteamInput_Init" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamAPI_RestartAppIfNecessary" target="int_0"/>
	</dllmap>

	<dllmap dll="steam_api64">
		<dllentry dll="libstubborn.so" name="SteamAPI_Init" target="int_0"/>
	</dllmap>

	<dllmap dll="fmodstudio">
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Create" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Initialize" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_SetListenerAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Update" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_LoadBankFile" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetVCA" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_VCA_SetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_VCA_GetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetEvent" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_LoadSampleData" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_CreateInstance" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_Is3D" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Start" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetBus" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_SetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_GetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetDescription" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_GetPath" target="int_celeste_event"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bank_LoadSampleData" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetListenerAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Set3DAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Release" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Stop" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Get3DAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Release" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetParameterValue" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_IsOneshot" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_TriggerCue" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_StopAllEvents" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetPlaybackState" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetLowLevelSystem" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_GetInstanceCount" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_UnloadSampleData" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_FlushCommands" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_GetChannelGroup" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetCallback" target="int_0"/>
	</dllmap>

	<dllmap dll="fmodstudio.dll">
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Create"	 target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Initialize" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_SetListenerAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Update" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_LoadBankFile" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetVCA" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_VCA_SetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_VCA_GetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetEvent" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_LoadSampleData" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_CreateInstance" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_Is3D" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Start" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetBus" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_SetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_GetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetDescription" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_GetPath" target="int_celeste_event"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bank_LoadSampleData" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetListenerAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Set3DAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Release" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Stop" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_Get3DAttributes" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_Release" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetParameterValue" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_IsOneshot" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_TriggerCue" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_StopAllEvents" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetPaused" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_GetPlaybackState" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_GetLowLevelSystem" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_GetInstanceCount" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventDescription_UnloadSampleData" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_LockChannelGroup" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_System_FlushCommands" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_Bus_GetChannelGroup" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_Studio_EventInstance_SetCallback" target="int_0"/>
	</dllmap>

	<dllmap dll="fmodex">
		<dllentry dll="libstubborn.so" name="FMOD_System_Create" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_GetVersion" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_Init" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_SetReverbProperties" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_Update" target="int_0"/>
	</dllmap>

	<dllmap dll="uP2P.dll">
		<dllentry dll="libstubborn.so" name="libuP2P_liaison_init" target="int_1"/>
		<dllentry dll="libstubborn.so" name="libuP2P_hook" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_part_read" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_part" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_sync_zero" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_persona_rich" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_liaison_poll" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_fake" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_take" target="int_0"/>
		<dllentry dll="libstubborn.so" name="libuP2P_liaison_exit" target="int_0"/>
	</dllmap>

	<dllmap dll="fmod">
		<dllentry dll="libstubborn.so" name="FMOD_System_GetVersion" target="int_fmf_getversion"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_SetDSPBufferSize" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_SetAdvancedSettings" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_System_SetSoftwareChannels" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_ChannelGroup_SetVolume" target="int_0"/>
		<dllentry dll="libstubborn.so" name="FMOD_ChannelGroup_SetPitch" target="int_0"/>
	</dllmap>

	<!-- PhotonBridge: Unrailed! -->
	<dllmap dll="PhotonBridge">
		<dllentry dll="libstubborn.so" name="init" target="int_1"/>
		<dllentry dll="libstubborn.so" name="Init" target="int_1"/>
	</dllmap>

	<dllmap dll="SteamLink.dll">
		<dllentry dll="libstubborn.so" name="SteamLink_Init" target="int_1"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetMessageCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetDataReceivedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetPersonaStateChangeCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_Shutdown" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetLobbyChatUpdateCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetLobbyDataUpdatedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetLobbyCreatedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetLobbyEnteredCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetLobbyGameCreatedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetBeginAuthResponseCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetP2PSessionRequestCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetP2PSessionConnectFailCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetAvatarImageLoadedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetSteamServersConnectedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetSteamServersDisconnectedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetSteamServerConnectFailureCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetServerListRefreshCompleteCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetIPCFailureCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetSteamShutdownCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetUserStatsReceivedCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetUserStatsStoredCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetAchievementStoredCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetPolicyResponseCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetGSClientApproveCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetGSClientDenyCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_SetGSClientKickCallbackPtr" target="int_0"/>
		<dllentry dll="libstubborn.so" name="SteamLink_GetAchievementUnlockStatus" target="int_0"/>
	</dllmap>
</configuration>
EOF
			fi
		fi
	fi
}

libraryname()
{
        version="$1"
	if [ ! -e "$gamedir/$version" ]; then
		debug_echo "Couldn't find library directory $gamedir/$version"
	else
		debug_echo "\nEntering library directory $gamedir/$version"
		for file in "$gamedir"/"$version"/*; do
			file="$(basename "$file")"
			# sort out libs that need to be ignored
			if [ "$(validlib "$file")" = "true" ]
			then
				debug_echo "\tignored file: $file"
				continue
			fi
			debug_echo -n "	found library file: $file"
			file=$(trunklibnam "$file")
			debug_echo -n " -> $file"
			case "$(inarray "$file" "${needlibarray}")" in
				"true")
					debug_echo " - already in array"
					;;
				"false")
					needlibarray[$((${#needlibarray[*]} + 1))]="$file"
					debug_echo ""
					;;
				*)
					printf "\n\tERROR: inarray returned with unexpected error\n\n"
					exit 1
					;;
			esac
		done
		debug_echo "Done with library directory $gamedir/$version"
	fi
}

# install FNA into gamedir; specify version (e.g. 17.12) as $1
# if XNA bridge should be installed, add "xna_bridge" as $2
install_fna()
{
	if [ $# -lt 1 ]; then
		echo "ERROR: install_fna called with insufficient arguments"
		exit 1
	fi
	fna_version=
	if [ "${1}" = 'latest' ]; then
		# get version number of latest release from GitHub API
		fna_version=$(ftp -Vo - https://api.github.com/repos/FNA-XNA/FNA/releases/latest \
			| grep -Eo "\"tag_name\"\:\"[0-9\.]*\"" \
			| cut -d\" -f4)
	else
		fna_version="$1"
	fi
	printf '\nInstalling FNA %s ...' "${fna_version}\n"
	licenses="${licenses}FNA:\t\tMs-PL (https://github.com/FNA-XNA/FNA/blob/master/licenses/LICENSE)${newline}"
	lastdir="$PWD"
	cd /tmp || exit 1
	ftp -V "https://github.com/FNA-XNA/FNA/releases/download/${fna_version}/FNA-$(echo "$fna_version" | tr -d '.').zip"
	debug_echo "extracting FNA ${fna_version}"
	unzip "FNA-$(echo "$fna_version" | tr -d '.').zip" > /dev/null
	if	[ $# -gt 1 ] \
		&& [ "$2" = "xna_bridge" ]
	then
		printf '\nInstalling FNA.NetStub for XNA ...\n'
		licenses="${licenses}FNA.NetStub:\tMs-PL (https://github.com/FNA-XNA/FNA.NetStub/blob/master/LICENSE)${newline}"
		ftp -V https://github.com/FNA-XNA/FNA.NetStub/archive/$netstub_commit.tar.gz
		tar zxf $netstub_commit.tar.gz
		mv FNA.NetStub-$netstub_commit FNA.NetStub
		echo -n "compiling XNA ABI/bridge files. This may take a moment... "
		xbuild /p:Configuration=Release FNA/abi/Microsoft.Xna.Framework.sln >> /tmp/fnaify-xbuild.log
		echo "done."
		cp /tmp/FNA/abi/bin/Release/* "$gamedir/"
	else
		# Download Vorbisfile-CS to be included in the build
		ftp https://raw.githubusercontent.com/flibitijibibo/Vorbisfile-CS/master/Vorbisfile.cs
		# add in FNA.Settings.props
		cat <<EOF > FNA/FNA.Settings.props
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<ItemGroup>
		<Compile Include="lib/SDL2-CS/src/SDL2_image.cs" />
		<Compile Include="../Vorbisfile.cs" />
	</ItemGroup>
</Project>
EOF
		echo -n "compiling FNA ${fna_version} with xbuild... "
		xbuild /p:Configuration=Release FNA/FNA.sln >> /tmp/fnaify-xbuild.log
		echo "done."
		echo "Moving FNA.dll to fnaify-backup subdirectory"
		mkdir -p "$gamedir/fnaify-backup"
		mv "$gamedir/FNA.dll" "$gamedir/fnaify-backup/"
		cp /tmp/FNA/bin/Release/FNA.dll "$gamedir"
	fi
	debug_echo "removing the temporary build directories and returning to previous directory"
	rm -rf /tmp/FNA /tmp/FNA.NetStub /tmp/Vorbisfile.cs
	cd "$lastdir" || exit 1
}

check_nlog()
{
	if [ -f "$gamedir/NLog.dll" ] ; then
		nlogversion=$(monodis --assembly "$gamedir/NLog.dll" | grep "Version" | tr -d '[:alpha:]' | tr -d " " | tr -d :)
		nlogmajor=$(echo "$nlogversion" | sed -n -E "s/\..*//p")
		nlogminor=$(echo "$nlogversion" | sed -n -E "s/[0-9]+\.([0-9]+)\.[0-9]+\.[0-9]+/\1/p")
		debug_echo -n "\tNLog.dll version $nlogversion, "
		debug_echo -n "major: $nlogmajor, "
		debug_echo "minor: $nlogminor"
		if	[ "$nlogmajor" -lt 4 ] \
			&& [ "$interaction" = "i" ]
		then
			printf '\nInstall NLog 4.6.5 from www.nuget.org? [y/n] '
			response=
			until [ "$response" = "y" ] || [ "$response" = "n" ]
			do
				read -r response
			done
		elif [ "$nlogmajor" -lt 4 ] && [ "$interaction" = "y" ] ; then
			response="y"
		else
			response="n"
		fi
		if [ "$response" = "y" ] ; then
			debug_echo "Moving old NLog.dll out of the way"
			mkdir -p "$gamedir/fnaify-backup"
			mv "$gamedir/NLog.dll" "$gamedir/fnaify-backup/"
			lastdir="$PWD"
			mkdir /tmp/nlog
			cd /tmp/nlog || exit 1
			printf '\nInstalling NLog 4.6.5 ...\n'
			licenses="${licenses}NLog:\tBSD 3-clause (https://github.com/NLog/NLog/blob/master/LICENSE.txt)${newline}"
			ftp -Vo nlog.4.6.5.nupkg https://www.nuget.org/api/v2/package/NLog/4.6.5
			unzip nlog.4.6.5.nupkg > /dev/null
			cp lib/net45/NLog.dll "$gamedir/"
			cd "$lastdir" || exit 1
			rm -rf /tmp/nlog
		elif [ "$nlogmajor" -lt 4 ] ; then
			nlog_warning=1
		fi
	fi
}

process_options()
{
	while getopts c:Dd:F:f:hil:m:nsVvy o; do
		case "$o" in
			c) gameconfig="$OPTARG" ;;
			D) debug="--debug" ;;
			d) gamedir=$(readlink -f "$OPTARG") ;;
			f) frameworkfile="$OPTARG" ;;
			F) frameworkversion="$OPTARG" ;;
			h) echo "$USAGE"; exit 0;;
			i) interaction=i ;;
			l) depdir="$OPTARG" ;;
			m) monopath="$OPTARG" ;;
			n) nolibcheck=1 ;;
			s) force_setup=1 ;;
			V) echo "$FNAIFY_VERSION"; exit 0;;
			v) FNAIFY_DEBUG=true ;;
			y) interaction=y ;;
			--) break ;;
			*) ;;
		esac
	done
	debug_echo "options:	$*"
	debug_echo "OPTIND:	$OPTIND"
	shift $((OPTIND-1))
	debug_echo "options after shift:	$*\n"
	userflags=$*
}

check_mono_binaries()
{
	debug_echo "Checking mono and monodis"
	if [ -z "$FNAIFY_MONO" ]; then
		FNAIFY_MONO="$(which mono 2>/dev/null)"
	fi
	[ -z "$FNAIFY_MONO" ] && { \
		echo "error finding mono"
		exit 1
	}
	if ! "$FNAIFY_MONO" --version 2>/dev/null >/dev/null
	then
		echo "error calling mono - aborting... Please make sure that mono is in path or set it in FNAIFY_MONO environment variable!"
		exit 1
	fi
	# check if monodis can be called
	if monodis 2>/dev/null
	then
		echo "error calling monodis - aborting... Please make sure that monodis is in the PATH!"
		exit 1
	fi
}

iomap()
{
	debug_echo "iomap: checking if symlinks for filename cases need to be created"
	for f in $(find "$gamedir" -maxdepth 1 -name "*.exe" -exec basename {} \;); do
		case $f in
			"Dead Pixels.exe" )
				ln -sf Sprites Content/sprites
				ln -sf Effects Content/Sprites/effects
				ln -sf Splash Content/Sprites/splash
				ln -sf Items Content/Sprites/InGame/items
				ln -sf Grenades Content/Sprites/InGame/grenades
				ln -sf Hud Content/Sprites/InGame/hud
				ln -sf insideBuildings Content/Sprites/InGame/InsideBuildings
				ln -sf Character Content/Sprites/InGame/character
				ln -sf City Content/Sprites/InGame/city
				ln -sf Traders Content/Sprites/InGame/traders
				ln -sf Zombies Content/Sprites/InGame/zombies
				ln -sf Objects Content/Sprites/InGame/objects
				ln -sf Other Content/Sprites/InGame/other
				ln -sf GunShots Content/Sprites/InGame/gunShots
				ln -sf Buttons Content/Sprites/buttons
				ln -sf Menu Content/Sprites/menu
				ln -sf Cursor Content/Sprites/cursor
				ln -sf Achievements Content/Sprites/achievements
				ln -sf Credits Content/Sprites/credits
				ln -sf Font Content/Sprites/font
				ln -sf preview Content/Sprites/Preview
				ln -sf preview Content/Sprites/Menu/Preview
				ln -sf PsxButtons Content/Sprites/buttons/psxButtons
				ln -sf Cutscene Content/Sprites/CutScene
				ln -sf buildings Content/Sprites/InGame/City/Buildings
				ln -sf mainbackground.xnb Content/ConfigSprites/mainBackground.xnb
				ln -sf largeCuts.xnb Content/Sprites/effects/largecuts.xnb
				ln -sf smallCuts.xnb Content/Sprites/effects/smallcuts.xnb
				ln -sf effects Content/Sprites/Effects
				ln -sf cuemark.xnb Content/Sprites/effects/cueMark.xnb
				ln -sf Static.xnb Content/Sprites/Menu/Preview/static.xnb
				;;
			AJ1.exe )
				ln -sf j_rip.xnb Content/AJ1/j_Rip.xnb
				ln -sf j_rip.xnb Content/AJ2/j_Rip.xnb
				ln -sf Owlturnneo2.xnb Content/AJ1/owlturnneo2.xnb
				ln -sf Owlturnneo2.xnb Content/AJ2/owlturnneo2.xnb
				;;
			CSTW.exe )
				ln -sf paws_Happy.xnb Content/Portrait/Paws/Paws_Happy.xnb
				;;
			ThePit.exe )
				ln -sf UI Content/ui
				;;
			"Grand Class Melee.exe" )
				ln -sf water.xnb Content/Sounds/Water.xnb
				ln -sf grass.xnb Content/Sounds/Grass.xnb
				ln -sf move.xnb Content/Sounds/Move.xnb
				ln -sf select.xnb Content/Sounds/Select.xnb
				ln -sf back.xnb Content/Sounds/Back.xnb
				ln -sf squire_base.xnb Content/Textures/Players/Squire_base.xnb
				ln -sf squire_greyscale.xnb Content/Textures/Players/Squire_greyscale.xnb
				ln -sf militia_base.xnb Content/Textures/Players/Militia_base.xnb
				ln -sf militia_greyscale.xnb Content/Textures/Players/Militia_greyscale.xnb
				ln -sf apprentice_base.xnb Content/Textures/Players/Apprentice_base.xnb
				ln -sf apprentice_greyscale.xnb Content/Textures/Players/Apprentice_greyscale.xnb
				ln -sf savant_base.xnb Content/Textures/Players/Savant_base.xnb
				ln -sf savant_greyscale.xnb Content/Textures/Players/Savant_greyscale.xnb
				ln -sf sword.xnb Content/Textures/Weapons/Sword.xnb
				ln -sf arrow.xnb Content/Sounds/Arrow.xnb
				ln -sf scorch.xnb Content/Sounds/Scorch.xnb
				ln -sf bigspeed.xnb Content/Sounds/Bigspeed.xnb
				ln -sf frame_ingame_left_ruin.xnb Content/Textures/Menu/frame_ingame_left_Ruin.xnb
				ln -sf frame_ingame_left_mire.xnb Content/Textures/Menu/frame_ingame_left_Mire.xnb
				ln -sf frame_ingame_right_ruin.xnb Content/Textures/Menu/frame_ingame_right_Ruin.xnb
				ln -sf frame_ingame_left_wood.xnb Content/Textures/Menu/frame_ingame_left_Wood.xnb
				ln -sf ruin_leaf.xnb Content/Textures/Terrain/Ruin_leaf.xnb
				ln -sf frame_ingame_right_wood.xnb Content/Textures/Menu/frame_ingame_right_Wood.xnb
				ln -sf bigblow1.xnb Content/Sounds/Bigblow1.xnb
				ln -sf ruin_grassmove.xnb Content/Textures/Terrain/Ruin_grassmove.xnb
				ln -sf wood_leaf.xnb Content/Textures/Terrain/Wood_leaf.xnb
				ln -sf ruin_watermove.xnb Content/Textures/Terrain/Ruin_watermove.xnb
				ln -sf castshort.xnb Content/Sounds/Castshort.xnb
				ln -sf frame_ingame_right_mire.xnb Content/Textures/Menu/frame_ingame_right_Mire.xnb
				ln -sf frame_ingame_left_dune.xnb Content/Textures/Menu/frame_ingame_left_Dune.xnb
				ln -sf sword1.xnb Content/Sounds/Sword1.xnb
				ln -sf frame_ingame_right_dune.xnb Content/Textures/Menu/frame_ingame_right_Dune.xnb
				ln -sf staff1.xnb Content/Sounds/Staff1.xnb
				;;
			"LaserCat.exe" )
				ln -sf audio Content/Audio
				;;
			"MountYourFriends.exe" )
				ln -sf menuBg.xnb Content/images/menubg.xnb
				ln -sf humanClean.xnb Content/images/humanclean.xnb
				ln -sf humanCleanNorm.xnb Content/images/humancleannorm.xnb
				ln -sf menuMarker.xnb Content/images/menumarker.xnb
				ln -sf stegersaurusLogo.xnb Content/images/backdrops/stegersauruslogo.xnb
				ln -sf UIComponents.xnb Content/images/uicomponents.xnb
				ln -sf restrictedArea.xnb Content/images/restrictedarea.xnb
				ln -sf goatSheet.xnb Content/images/goatsheet.xnb
				ln -sf BP3_SSTRIP_64.xnb Content/images/bp3_sstrip_64.xnb
				ln -sf BP3_SSTRIP_32.xnb Content/images/bp3_sstrip_32.xnb
				ln -sf keySheet.xnb Content/images/keysheet.xnb
				;;
			"One Finger Death Punch.exe" )
				ln -sf font2.xnb Content/Font2.xnb
				ln -sf font5.xnb Content/Font5.xnb
				ln -sf font6.xnb Content/Font6.xnb
				;;
			"PhoenixForce.exe" )
				ln -sf LIfeBar.xnb Content/1.4/Boss/lifeBar.xnb
				ln -sf firewavexml.xml Content/1.4/Player/fireWavexml.xml
				ln -sf firewave.xnb Content/1.4/Player/fireWave.xnb
				;;
			"Streets of Fury EX.exe" )
				ln -sf ShockWave.xnb Content/Texture2D/Shockwave.xnb
				;;
			"Aces Wild.exe" )
				ln -sf HitSparks Content/Sprites/Hitsparks
				ln -sf preFabs.awx Content/Data/prefabs.awx
				;;
			"CameraObscura.exe" )
				ln -sf enemies Content/Enemies
				ln -sf buttons Content/Buttons
				ln -sf ui Content/UI
				ln -sf particle Content/Particle
				;;
			"DLC.exe" )
				ln -sf ../../campaigns/dlcquest/texture/awardmentSpriteSheet.xnb Content/base/texture/awardmentSpriteSheet.xnb
				ln -sf ../../campaigns/dlcquest/texture/dlcSpriteSheet.xnb Content/base/texture/dlcSpriteSheet.xnb
				ln -sf ../../campaigns/dlcquest/data/map Content/base/data/map
				ln -sf ../../campaigns/dlcquest/texture/tiles_16x16.xnb Content/base/texture/tiles_16x16.xnb
				ln -sf ../../campaigns/dlcquest/texture/skyNightSpriteSheet.xnb Content/base/texture/skyNightSpriteSheet.xnb
				ln -sf ../../campaigns/dlcquest/texture/backgroundSpriteSheet.xnb Content/base/texture/backgroundSpriteSheet.xnb
				ln -sf ../../campaigns/dlcquest/texture/background2SpriteSheet.xnb Content/base/texture/background2SpriteSheet.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/shopkeep.xnb Content/base/data/npc/shopkeep.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/shopkeep2.xnb Content/base/data/npc/shopkeep2.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/shepherd.xnb Content/base/data/npc/shepherd.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/random.xnb Content/base/data/npc/random.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/filler.xnb Content/base/data/npc/filler.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/blacksmith.xnb Content/base/data/npc/blacksmith.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/sidequest.xnb Content/base/data/npc/sidequest.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/troll.xnb Content/base/data/npc/troll.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/gunsmith.xnb Content/base/data/npc/gunsmith.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/princess.xnb Content/base/data/npc/princess.xnb
				ln -sf ../../../campaigns/dlcquest/data/npc/horse.xnb Content/base/data/npc/horse.xnb
				;;
			"EvilQuest.exe" )
				ln -sf Weapons Content/weapons
				ln -sf DialogWindow.xnb Content/HUD/dialogWindow.xnb
				ln -sf SpellSprites Content/spellSprites
				ln -sf PromptMessageWindow.xnb Content/HUD/promptMessageWindow.xnb
				ln -sf PromptWindow.xnb Content/HUD/promptWindow.xnb
				ln -sf Menu Content/menu
				ln -sf smallCursor.xnb Content/Menu/smallcursor.xnb
				ln -sf ItemIcons.xnb Content/Menu/itemIcons.xnb
				ln -sf SpellIcons.xnb Content/Menu/spellIcons.xnb
				ln -sf SplashSymbol.xnb Content/Menu/splashSymbol.xnb
				ln -sf SplashChaosoftLogo.xnb Content/Menu/splashChaosoftLogo.xnb
				ln -sf SplashGamesText.xnb Content/Menu/splashGamesText.xnb
				ln -sf PrisonGalvis_NoShadow.xnb Content/prisonGalvis_NoShadow.xnb
				ln -sf EnemySprites Content/enemysprites
				ln -sf Galvis_NoShadow.xnb Content/EnemySprites/galvis_noShadow.xnb
				ln -sf DemonGalvis.xnb Content/demonGalvis.xnb
				ln -sf StunnedEffect.xnb Content/SpellSprites/stunnedEffect.xnb
				ln -sf Colorize.xnb Content/colorize.xnb
				ln -sf bossDialogMusic.xnb Content/BossDialogMusic.xnb
				ln -sf Equip.xnb Content/Menu/equip.xnb
				ln -sf MessageWindow.xnb Content/Menu/messageWindow.xnb
				ln -sf Shop.xnb Content/shop.xnb
				ln -sf Title.xnb Content/Menu/title.xnb
				ln -sf TitleNewGame.xnb Content/Menu/titleNewGame.xnb
				ln -sf TitleNewGameActive.xnb Content/Menu/titleNewGameActive.xnb
				ln -sf GalvisTheme.xnb Content/galvisTheme.xnb
				ln -sf FlamesBG.xnb Content/Intro/Screen1/flamesBG.xnb
				ln -sf FlamesFG.xnb Content/Intro/Screen1/flamesFG.xnb
				ln -sf ForegroundMask.xnb Content/Intro/Screen1/foregroundMask.xnb
				ln -sf VillageBGFire.xnb Content/Intro/Screen3/villageBGFire.xnb
				ln -sf VillageFG.xnb Content/Intro/Screen3/villageFG.xnb
				ln -sf VillageFire.xnb Content/Intro/Screen3/villageFire.xnb
				ln -sf Galvis1.xnb Content/Intro/Screen3/galvis1.xnb
				ln -sf Galvis2.xnb Content/Intro/Screen3/galvis2.xnb
				ln -sf Silhouettes.xnb Content/Intro/Screen4/silhouettes.xnb
				ln -sf FullColor.xnb Content/Intro/Screen4/fullcolor.xnb
				ln -sf FlamesBG.xnb Content/Intro/Screen4/flamesBG.xnb
				ln -sf FlamesFG.xnb Content/Intro/Screen4/flamesFG.xnb
				ln -sf ForegroundMask.xnb Content/Intro/Screen4/foregroundMask.xnb
				ln -sf ArrestFG1.xnb Content/Intro/Screen5/arrestFG1.xnb
				ln -sf ArrestFG2.xnb Content/Intro/Screen5/arrestFG2.xnb
				ln -sf ArrestFG3.xnb Content/Intro/Screen5/arrestFG3.xnb
				ln -sf GalvisEndingCloseUpBG1.xnb Content/Intro/Screen9/GalvisEndingCloseupBG1.xnb
				ln -sf ControlsPC.xnb Content/Menu/controlsPC.xnb
				ln -sf amethyst.xnb Content/Amethyst.xnb
				ln -sf BattlefieldIntro.xnb Content/BATTLEFIELDINTRO.xnb
				ln -sf Prison2.xnb Content/PRISON2.xnb
				ln -sf Prison1.xnb Content/PRISON1.xnb
				ln -sf Items.xml Data/items.dat
				ln -sf NPCs.xml Data/NPCS.dat
				;;
			"HELLYEAH.exe" )
				ln -sf QuadNoir.xnb Content/QUADNOIR.xnb
				ln -sf QuadBlanc.xnb Content/QUADBLANC.xnb
				ln -sf TRANS_Mask.xnb Content/TRANS_MASK.xnb
				ln -sf Popup Content/GAME/HUD/POPUP
				ln -sf pop_u_trung.xnb Content/GAME/HUD/Popup/POP_U_TRUNG.xnb
				ln -sf popup_cartouche_noir.xnb Content/GAME/HUD/Popup/POPUP_CARTOUCHE_NOIR.xnb
				ln -sf popup_barre_rouges.xnb Content/GAME/HUD/Popup/POPUP_BARRE_ROUGES.xnb
				ln -sf Menu_\(arial\).xnb Content/TITLE/FONTS/MENU_\(ARIAL\).xnb
				ln -sf cursor.xnb Content/PCONLY/CURSORS/CURSOR.xnb
				ln -sf viseur.xnb Content/PCONLY/CURSORS/VISEUR.xnb
				ln -sf Shaders Content/SHADERS
				ln -sf WhiteFlash.xnb Content/Shaders/WHITEFLASH.xnb
				ln -sf VaguePoison.xnb Content/Shaders/VAGUEPOISON.xnb
				ln -sf Black.xnb Content/Shaders/BLACK.xnb
				ln -sf White.xnb Content/Shaders/WHITE.xnb
				ln -sf BloomExtract.xnb Content/Shaders/BLOOMEXTRACT.xnb
				ln -sf BloomCombine.xnb Content/Shaders/BLOOMCOMBINE.xnb
				ln -sf GaussianBlur.xnb Content/Shaders/GAUSSIANBLUR.xnb
				ln -sf Sobel.xnb Content/Shaders/SOBEL.xnb
				ln -sf RadialBlur.xnb Content/Shaders/RADIALBLUR.xnb
				ln -sf VagueFeu.xnb Content/Shaders/VAGUEFEU.xnb
				ln -sf ColorEffects.xnb Content/Shaders/COLOREFFECTS.xnb
				ln -sf Explosion.xnb Content/Shaders/EXPLOSION.xnb
				ln -sf Ripple.xnb Content/Shaders/RIPPLE.xnb
				ln -sf RedFilter.xnb Content/Shaders/REDFILTER.xnb
				ln -sf Distortion.xnb Content/Shaders/DISTORTION.xnb
				ln -sf Lightning.xnb Content/Shaders/LIGHTNING.xnb
				ln -sf LoadingWheel.xnb Content/GAME/LOADING/LOADINGWHEEL.xnb
				ln -sf LOGO_Sega.xnb Content/LOGO/LOGO_SEGA.xnb
				ln -sf LOGO_Arkedo.xnb Content/LOGO/LOGO_ARKEDO.xnb
				ln -sf Sounds Content/GAME/WORLD/SOUNDS
				ln -sf HY_Sounds.xnb Content/GAME/WORLD/Sounds/HY_SOUNDS.xnb
				ln -sf picto_A.xnb Content/GAME/FONTS/PICTO/PICTO_A.xnb
				ln -sf picto_B.xnb Content/GAME/FONTS/PICTO/PICTO_B.xnb
				ln -sf picto_BK.xnb Content/GAME/FONTS/PICTO/PICTO_BK.xnb
				ln -sf picto_LB.xnb Content/GAME/FONTS/PICTO/PICTO_LB.xnb
				ln -sf picto_LS.xnb Content/GAME/FONTS/PICTO/PICTO_LS.xnb
				ln -sf picto_LT.xnb Content/GAME/FONTS/PICTO/PICTO_LT.xnb
				ln -sf picto_PAD.xnb Content/GAME/FONTS/PICTO/PICTO_PAD.xnb
				ln -sf picto_RB.xnb Content/GAME/FONTS/PICTO/PICTO_RB.xnb
				ln -sf picto_RS.xnb Content/GAME/FONTS/PICTO/PICTO_RS.xnb
				ln -sf picto_RT.xnb Content/GAME/FONTS/PICTO/PICTO_RT.xnb
				ln -sf picto_ST.xnb Content/GAME/FONTS/PICTO/PICTO_ST.xnb
				ln -sf picto_X.xnb Content/GAME/FONTS/PICTO/PICTO_X.xnb
				ln -sf picto_Y.xnb Content/GAME/FONTS/PICTO/PICTO_Y.xnb
				ln -sf picto_RS1.xnb Content/GAME/FONTS/PICTO/PICTO_RS1.xnb
				ln -sf picto_RS2.xnb Content/GAME/FONTS/PICTO/PICTO_RS2.xnb
				ln -sf picto_RS3.xnb Content/GAME/FONTS/PICTO/PICTO_RS3.xnb
				ln -sf picto_RS4.xnb Content/GAME/FONTS/PICTO/PICTO_RS4.xnb
				ln -sf picto_360.xnb Content/GAME/FONTS/PICTO/PICTO_360.xnb
				ln -sf saving.xnb Content/LOGO/SAVING.xnb
				ln -sf branchetonpad.xnb Content/PCONLY/MENUS/BRANCHETONPAD.xnb
				ln -sf pckey.xnb Content/PCONLY/MENUS/PCKEY.xnb
				ln -sf mousecenter.xnb Content/PCONLY/MENUS/MOUSECENTER.xnb
				ln -sf mousedefault.xnb Content/PCONLY/MENUS/MOUSEDEFAULT.xnb
				ln -sf mousedirections.xnb Content/PCONLY/MENUS/MOUSEDIRECTIONS.xnb
				ln -sf mouseleftbt.xnb Content/PCONLY/MENUS/MOUSELEFTBT.xnb
				ln -sf mouserightbt.xnb Content/PCONLY/MENUS/MOUSERIGHTBT.xnb
				ln -sf mousewheelup.xnb Content/PCONLY/MENUS/MOUSEWHEELUP.xnb
				ln -sf mousewheeldown.xnb Content/PCONLY/MENUS/MOUSEWHEELDOWN.xnb
				ln -sf ls.xnb Content/PCONLY/MENUS/LS.xnb
				ln -sf ls1.xnb Content/PCONLY/MENUS/LS1.xnb
				ln -sf ls2.xnb Content/PCONLY/MENUS/LS2.xnb
				ln -sf ls360.xnb Content/PCONLY/MENUS/LS360.xnb
				ln -sf rs1.xnb Content/PCONLY/MENUS/RS1.xnb
				ln -sf rs2.xnb Content/PCONLY/MENUS/RS2.xnb
				ln -sf rs3.xnb Content/PCONLY/MENUS/RS3.xnb
				ln -sf rs4.xnb Content/PCONLY/MENUS/RS4.xnb
				ln -sf "INT_Radar_(good_girl).xnb" "Content/GAME/FONTS/INT_RADAR_(GOOD_GIRL).xnb"
				ln -sf "DLG_Name_(trashand).xnb" "Content/GAME/FONTS/DLG_NAME_(TRASHAND).xnb"
				ln -sf Particules Content/TITLE/PARTICULES
				ln -sf boum.xnb Content/TITLE/Particules/BOUM.xnb
				ln -sf flameches.xnb Content/TITLE/Particules/FLAMECHES.xnb
				ln -sf fumee_background_flou.xnb Content/TITLE/Particules/FUMEE_BACKGROUND_FLOU.xnb
				ln -sf fumee_noire.xnb Content/TITLE/Particules/FUMEE_NOIRE.xnb
				ln -sf gaz_multicolor.xnb Content/TITLE/Particules/GAZ_MULTICOLOR.xnb
				ln -sf TITLE_Cartouche.xnb Content/TITLE/TITLE_CARTOUCHE.xnb
				ln -sf TITLE_Subtitle-EN.xnb Content/TITLE/TITLE_SUBTITLE-EN.xnb
				ln -sf TITLE_Logo.xnb Content/TITLE/TITLE_LOGO.xnb
				ln -sf TITLE_FlameScroll.xnb Content/TITLE/TITLE_FLAMESCROLL.xnb
				ln -sf TITLE_LogoMask.xnb Content/TITLE/TITLE_LOGOMASK.xnb
				ln -sf TITLE_Background.xnb Content/TITLE/TITLE_BACKGROUND.xnb
				ln -sf TITLE_Select.xnb Content/TITLE/TITLE_SELECT.xnb
				;;
			"HonourRuns.exe" )
				ln -sf Sprites Content/sprites
				ln -sf Textures Content/textures
				ln -sf Levels Content/levels
				;;
			"POOF.exe" )
				ln -sf QuadNoir.xnb Content/QUADNOIR.xnb
				ln -sf QuadBlanc.xnb Content/QUADBLANC.xnb
				ln -sf TRANS_Mask.xnb Content/TRANS_MASK.xnb
				;;
			# beware, SSDD and SSDDXXL have the same named SSGame.exe
			"SSGame.exe" )
				# SSDDXXL
				ln -s HUD_ShopBackground.xnb Content/textures/menus/HUD_Shopbackground.xnb
				ln -s GLOBAL.xnb Content/levels/global.xnb
				ln -s HUD_challenge_skull.xnb Content/textures/menus/hud_challenge_skull.xnb
				ln -s LEVEL1.xnb Content/levels/level1.xnb
				# SSDD
				ln -s FRONT.xnb Content/levels/front.xnb
				;;
			"TheFallOfGods2.exe" )
				ln -sf Data Content/data
				;;
			"Snails.exe" )
				ln -s ScreensData.xnb Content/screens/screensdata.xnb
				ln -s footerMessage.xnb Content/fonts/footermessage.xnb
				ln -s MainMenu.xnb Content/screens/mainmenu.xnb
				ln -s UISnailsMenu.xnb Content/screens/controls/uisnailsmenu.xnb
				ln -s UIMainMenuBodyPanel.xnb Content/screens/controls/uimainmenubodypanel.xnb
		esac
	done
	IFS=$SAVEIFS
}

xnasetup()
{
	if [ ! -d "$fnadir" ] ; then
		if [ "$interaction" = "i" ] ; then
			printf '\nInstall FNA files from GitHub to for this XNA application? [y/n] '
			response=
			until [ "$response" = "y" ] || [ "$response" = "n" ]
			do
				read -r response
			done
		elif [ "$interaction" = "y" ] ; then
			response="y"
		else
			response="n"
		fi

		if [ "$response" = "y" ]; then
			install_fna latest xna_bridge
		else
			printf 'Failed to install FNA/XNA files. Try running with -i or -y flag\n'
			exit 1
		fi
	fi

	# convert .wma to .ogg and .wmv to .ogv
	# https://gist.github.com/flibitijibibo/c97bc14aab04b1277d8ef5e97fc9aeff
	IFS="
"
	# TODO: check for errors on return of these commands
	echo "checking for WMA/WMV files and converting. This may take several minutes..."
	for afile in $(find "$gamedir" -name "*.wma"); do
		echo "converting $afile to OGG"
		ffmpeg -loglevel fatal -i "$afile" -c:a libvorbis -q:a 10 \
			"$(echo "$afile" | rev | cut -d. -f2-$(($(echo "$afile" \
			| tr -dc '.' | wc -c) + 1)) | rev).ogg" \
			|| echo "...skipping..."
	done
	for vfile in $(find . -name "*.wmv"); do
		echo "converting $vfile to OGV"
		ffmpeg -loglevel fatal -i "$vfile" -c:v libtheora -q:v 10 -c:a libvorbis \
			-q:a 10 "$(echo "$vfile" | rev | cut -d. -f2-$(($(echo "$vfile" \
			| tr -dc '.' | wc -c) + 1)) | rev).ogv" \
			|| echo "...skipping..."
	done
	IFS=$SAVEIFS
	echo " done."
}

get_setup_frameworkfile()
{
	if [ -e "$gamedir/FNA.dll" ] ; then
		setup_frameworkfile="$gamedir/FNA.dll"
	elif [ -e "$gamedir/MonoGame.Framework.dll" ] ; then
		setup_frameworkfile="$gamedir/MonoGame.Framework.dll"
	fi
}

get_frameworkversion()
{
	if [ -f "$1" ] ; then
		monodis --assembly "$1" | grep -F Version | sed -E 's/Version:[[:blank:]]*//'
	fi
}

get_frameworkmajor()
{
	echo "$1" | sed -n -E "s/([0-9]+)\.[0-9\.]+/\1/p"
}

get_frameworkminor()
{
	# strip leading zeros
	echo "$1" | sed -n -E "s/[0-9]+\.([0-9]+).*/\1/p" | sed "s/^0*//"
}

check_eagle_island()
{
	{ [ -f "$gamedir/EagleIsland.exe" ] && \
	[ "$(basename "$gamedir")" != "Linux" ]; } && \
	{ \
		debug_echo "correct gamedir for Eagle Island"
		gamedir="${gamedir}/Linux"
	}
}

run()
{
	check_eagle_island
	[ -e "$gamedir/.fnaify_ready" ] || { echo "ERROR: setup not completed."; exit 1; }
	check_mono_binaries
	cd "$gamedir" || exit 1

	selectexe
	check_gameconfig
	[ -e "$gameconfig" ] || { echo "ERROR: provided gameconfig file not existent: $gameconfig"; exit 1; }
	debug_echo "gameconfig: $gameconfig"

	# Quirks
	my_exe="$(echo "$my_exe" | tr -d '\:')"
	case "$my_exe" in
		Blueberry.exe|Shenzhen.exe|ThePit.exe )
			exe_env="${exe_env}MONO_FORCE_COMPAT=1 "
			;;
		Hacknet.exe )
			exe_flags="${exe_flags}-disableweb "
			;;
		ScourgeBringer.exe )
			exe_flags="${exe_flags}-noSound "
			;;
		MidBoss.exe )
			if 	[ -e fnaify.dllmap.MidBoss.config ] &&
				[ "$(diff -U 0 "$gameconfig" fnaify.dllmap.MidBoss.config | \
					wc -l)" -eq 5 ]
			then
				debug_echo "fnaify.dllmap.MidBoss.config up to date."
			else
				debug_echo "fnaify.dllmap.MidBoss.config is missing or out of date."
				debug_echo "creating fnaify.dllmap.MidBoss.config from $gameconfig for MidBoss"
				# copy $gameconfig to fnaify.dllmap.MidBoss.config
				cp "$gameconfig" fnaify.dllmap.MidBoss.config
				# edit fnaify.dllmap.MidBoss.config to use libSDL2_image_compact.so
				sed -Ei 's/libSDL2_image\.so/libSDL2_image_compact.so/g' \
					fnaify.dllmap.MidBoss.config
				# redefine gameconfig to fnaify.dllmap.MidBoss.config
				gameconfig=fnaify.dllmap.MidBoss.config
			fi
			;;
		SSGame.exe )
			mkdir -p ~/.local/share/SSDD
			;;
	esac

	for f in $configbasearray; do
		[ -e "$f" ] && { \
			debug_echo "existing symlink target: ${f}.config -> $(readlink "${f}.config")"
			gameconfigsymlink="${gameconfigsymlink}${f}.config "
			[ "$gameconfig" = "$(readlink "${f}.config")" ] \
				|| ln -sf "$gameconfig" "${f}.config"
		}
	done
	gameconfigsymlink="${gameconfigsymlink}${my_exe}.config "
	[ "$gameconfig" = "$(readlink "${my_exe}.config")" ] || \
		ln -sf "$gameconfig" "${my_exe}.config"

	# format all relevant command variables
	depdir="$(echo "$depdir" | sed -E 's/:$//')"
	monopath="$(echo "$monopath" | sed -E 's/:$//' | sed -E 's/^://')"

	#########################################
	# Heuristic for frameworkfile selection #
	#########################################

	# Scenario: user-specified framework file (-f)
	if [ -n "$frameworkfile" ] ; then
		debug_echo "using user-supplied frameworkfile: $frameworkfile"

	# Scenario: MonoGame.Framework.dll
	elif [ -f "$gamedir/MonoGame.Framework.dll" ] ; then
		frameworkfile="$gamedir/MonoGame.Framework.dll"

	# Scenario: user-specified framework version (-F)
	elif [ -n "$frameworkversion" ] ; then
		# check fnadir and gamedir for fitting version
		if [ -f "$fnadir/FNA.dll" ] ; then
			file_fwv="$(get_frameworkversion "$fnadir/FNA.dll")"
			if [ "$(get_frameworkmajor "$file_fwv").$(get_frameworkminor "$file_fwv")" = "$(get_frameworkmajor "$frameworkversion").$(get_frameworkminor "$frameworkversion")" ] ; then
				frameworkfile="$fnadir/FNA.dll"
			fi
		fi
		if [ -z "$frameworkfile" ] && [ -f "$gamedir/FNA.dll" ] ; then
			file_fwv="$(get_frameworkversion "$gamedir/FNA.dll")"
			if [ "$(get_frameworkmajor "$file_fwv").$(get_frameworkminor "$file_fwv")" = "$(get_frameworkmajor "$frameworkversion").$(get_frameworkminor "$frameworkversion")" ] ; then
				frameworkfile="$gamedir/FNA.dll"
			fi
		fi
		if [ -z "$frameworkfile" ] ; then
			install_fna "$frameworkversion"
			frameworkfile="$gamedir/FNA.dll"
		fi

	# Scenario: no frameworkfile in gamedir
	elif [ ! -f "$gamedir/FNA.dll" ] ; then
		if [ -e "$gamedir/.fnaify_needfna" ] ; then
			# this occurs if fnaify uses system framework file
			if [ -f "$fnadir/FNA.dll" ] ; then
				frameworkfile="$fnadir/FNA.dll"
			else
				echo "ERROR: frameworkfile needed, but not found. Try re-installing?"
				exit
			fi
		else
			debug_echo "game without framework file"
		fi

	# Scenario: FNA.dll in fnadir
	elif [ -f "$fnadir/FNA.dll" ] ; then
		# existence of $gamedir/FNA.dll is implied by above elif [ ! -f $gamedir/FNA.dll ]
		debug_echo "by default, move FNA.dll out of gamedir if present in fnadir"
		mkdir -p "$gamedir/fnaify-backup"
		mv "$gamedir/FNA.dll" "$gamedir/fnaify-backup/"
		debug_echo "touch .fnaify_needfna to make it clear framework file is needed"
		touch "$gamedir/.fnaify_needfna"
		frameworkfile="$fnadir/FNA.dll"

	# Scenario: FNA.dll in gamedir - suboptimal for compatibility since mojoshader updates
	else
		# existence of $gamedir/FNA.dll implied by above
		frameworkfile="$gamedir/FNA.dll"
	fi

	if [ -z "$frameworkversion" ] ; then
		frameworkversion="$(get_frameworkversion "$frameworkfile")"
	fi

	datasize=$(( $(ulimit -d) / 1024 ))

	echo
	printdash "fnaify runtime configuration"
	echo "fnaify Version:			$FNAIFY_VERSION"
	echo "Game Directory:			$PWD"		# PWD as cd'd into $gamedir happened above
	echo "Mono Binary:			$FNAIFY_MONO"
	echo "Mono Path:			$monopath"
	echo "Main Assembly:			$my_exe"
	echo "Native Library Directories:	$depdir"
	echo "Framework File:			$frameworkfile"
	echo "Framework File Version:		$frameworkversion"
	echo "Symlinks to Config:		$gameconfigsymlink"
	echo "Config File:			$gameconfig"
	echo "Other Runtime Environment:	$exe_env"
	echo "Runtime Flags:			$exe_flags $userflags"
	echo "Datasize Limit:			${datasize} M"
	echo

	[ $datasize -lt 2048 ] && { \
		echo "AT LEAST 2G are recommended for most games. See ksh(1) for ulimit command documentation"
		echo
	}

	# check if broken symlinks exist in the game directory, e.g. libdl.so.2
	IFS='
'
	for l in $(find . -type l); do
		IFS=$SAVEIFS
		if [ ! -e "$l" ] ; then
			newl="$(latest_syslib "$(trunklibnam "$(readlink "$l")")")"
			if [ -z "$newl" ] ; then
				echo "ERROR - broken symlink: $l. Expect trouble."
			else
				ln -fs "$newl" "$l"
			fi
		fi
	done

	debug_echo "env LD_LIBRARY_PATH=\"$depdir\" MONO_PATH=\"$monopath\" $exe_env \"$FNAIFY_MONO\" $debug \"$my_exe\" $exe_flags $userflags"
	debug_echo ""
	env LD_LIBRARY_PATH="$depdir" MONO_PATH="$monopath" $exe_env "$FNAIFY_MONO" $debug "$my_exe" $exe_flags $userflags

	exit 0
}

setup()
{
	echo "Performing setup (mode: $interaction) ..."
	check_mono_binaries
	check_eagle_island

	cd "$gamedir" || exit 1
	debug_echo "gamedir: $PWD"	# cd'd into direct above

	debug_echo "Setting up the framework files"
	get_setup_frameworkfile
	if [ -z "$setup_frameworkfile" ]; then
		debug_echo "... no framework file found. Checking other options..."
	else
		debug_echo "	framework file:			$setup_frameworkfile"
		setup_frameworkversion="$(get_frameworkversion "$setup_frameworkfile")"
		debug_echo "	framework version:	$setup_frameworkversion"
	fi

	if [ -z "$setup_frameworkfile" ] ; then
		# games without framework files:
		# Atom Zombie Smasher, Eliza, Molek-Syntez, Exapunks, Opus Magnum (Lightning.exe),
		# Shenzhen I/O, SpaceChem (64bit and fullscreen update from 2020-07-15), Streets of Fury EX
		if [ -f "$gamedir/AtomZombieSmasher.exe" ] ; then
			if ls $(echo "$depdir" | tr -s ':' ' ') | grep -q "libatomstb\.so"
			then
				debug_echo "\tfound libatomstb library in a library directory"
			else
				# add libatomstb to needlibarray
				if [ "$(inarray libatomstb.so "$needlibarray")" = "false" ]
				then
					needlibarray[$((${#needlibarray[*]} + 1))]="$libatomstb.so"
					debug_echo "\tlibatomstb.so added to array"
				fi
			fi
		elif	[ -f "$gamedir/Eliza.exe" ] \
			|| [ -f "$gamedir/MOLEK-SYNTEZ.exe" ] \
			|| [ -f "$gamedir/EXAPUNKS.exe" ] \
			|| [ -f "$gamedir/Lightning.exe" ] \
			|| [ -f "$gamedir/Shenzhen.exe" ] \
			|| [ -f "$gamedir/SpaceChem.exe" ] \
			|| [ -f "$gamedir/Hammerwatch.exe" ] \
			|| [ -f "$gamedir/SOR4.exe" ] \
			|| [ -f "$gamedir/MobiusFront83.exe" ] \
			|| [ -f "$gamedir/Proteus.exe" ]
		then
			debug_echo "\nCompatible game without XNA/FNA framework file found."
			debug_echo "Creating \$HOME/Desktop directory which is needed for Zachtronics games"
			mkdir -p "$HOME/Desktop"
		else
			found_xna=0
			IFS='
		'
			for f in $(find . -maxdepth 1 \( -iname "*.exe" -o -iname "*.dll" \) -exec file {} \; \
				| grep -F "Mono/.Net assembly"); do
				f="$(echo "$f" | cut -d : -f 1)"
				debug_echo "Checking $f for XNA"
				if ! $(monodis --assemblyref "$f" 2>&1 | grep -Fqim1 "Microsoft.Xna.Framework")
				then
					found_xna=1
					break
				fi
			done
			IFS=$SAVEIFS
			if [ $found_xna -gt 0 ]; then
				debug_echo "Found XNA reference"
				xnasetup
			else
				echo "No FNA, MonoGame or XNA reference found"
				exit 1
			fi
		fi
	fi

	if [ "$setup_frameworkfile" = "FNA.dll" ] && { [ "$frameworkmajor" -lt 16 ] || { [ "$frameworkmajor" -eq 16 ] && [ "$frameworkminor" -lt 5 ];};}
	then
		fna_warning=1
	fi

	check_nlog

	set -A configfilesarray		# empty the array
	nconfigfilesarray=0		# TODO: is this really needed?
	debug_echo "Identifying config files..."
	IFS="
"
	if [ -f "$gamedir"/*.config ]; then
		for cfile in "$gamedir"/*.config; do
			cfile="$(basename "$cfile")"
			debug_echo "	found config file: $cfile"
			configfilesarray[$((${#configfilesarray[*]} + 1))]="$cfile"
			nconfigfilesarray=$((nconfigfilesarray + 1))
		done
	fi
	IFS=$SAVEIFS
	debug_echo "Done identifying config files.\n"

	if [ $nolibcheck -eq 1 ] ; then
		debug_echo "skipping library checks"
	else
		# identify required libraries in .config files and lib{,64} directories
		# TODO: filenames not whitespace-safe (but should not be used in such files anyway)
		printdash "$(debug_echo "Identifying libraries required by the game...")"

		#for MidBoss, add SDL2_image_compact to needlib
		[ -f "$gamedir/MidBoss*" ] && \
			needlibarray[$((${#needlibarray[*]} + 1))]="libSDL2_image_compact.so"

		libraryname "lib64"
		libraryname "lib"
		libraryname "x64"
		libraryname "x86"

		debug_echo "\nChecking for library references in config files..."
		if [ $nconfigfilesarray -lt 1 ]; then
			debug_echo "...No config files found."
		else
			cfile=""
			IFS="
"
			for cfile in "${configfilesarray[@]}"; do
				IFS=$SAVEIFS
				debug_echo "\t$cfile"
				linuxlines=$(grep "os\=\"linux" "$gamedir/$cfile") \
					|| debug_echo "no os=linux entries in $cfile"
				for libstring in $(echo "$linuxlines" | sed -n -E "s/.*target=\"([^\"]+).*/\1/p"); do
					# Fix where library name includes directory information
					libstring=$(echo "$libstring" | sed -E 's/^.\///')
					# remove directories at the start of lib name
					libstring=$(echo "$libstring" | sed -E 's/^.*\///')
					debug_echo -n "\t\tFound library string: $libstring"
					# sort out libs that need to be ignored
					if [ "$(validlib "$libstring")" = "false" ]
					then
						debug_echo " - ignored"
						continue
					fi
					libstring=$(trunklibnam "$libstring")
					debug_echo -n " -> $libstring"
					# add to libstring to needlibarray if not in there yet
					case "$(inarray "$libstring" "$needlibarray")" in
						"true")
							debug_echo " - already in array"
							;;
						"false")
							needlibarray[$((${#needlibarray[*]} + 1))]="$libstring"
							debug_echo " - added to array"
							;;
						*)
							printf '\n\t - ERROR: inarray returned with unexpected error\n\n'
							exit 1
						;;
					esac
				done
			done
			debug_echo "Done with identifying libraries in config files"
		fi
		debug_echo "Done with identification of needed libraries."
	fi

	debug_echo -n "\nChecking if libpng filename needs fixing..."
	i=1
	while [ "$i" -le "${#needlibarray[@]}" ]; do
		needlibarray[i]="$(echo "${needlibarray[i]}" | sed -E "s/(libpng)..(\.so.*)/\1\2/")"
		i=$((i + 1))
	done
	debug_echo " done.\n"

	# Check if the libraries are available on the system (/usr/local/lib).
	# If not, break and inform user which libraries need to be installed.

	debug_echo "Checking installed libraries... "
	# missinglibs[*] accumulates missing library names to inform user
	missinglibs=""
	for needlib in ${needlibarray}; do
		if ls $(echo "$depdir" | tr -s ':' ' ') | grep -q "$needlib"
		then
			IFS=$SAVEIFS
			debug_echo "\tfound library for: $needlib"
		else
			IFS=$SAVEIFS
			debug_echo "\tNot found: $needlib"
			missinglibs="$missinglibs$needlib "
		fi
	done
	debug_echo "done.\n"

	if [ -n "${missinglibs}" ]; then
		printf '\nCould not find the following libraries:\n\n%b\n' "${missinglibs}"
		exit 1
	fi

	# identify all .config files that do dllmap and move out of the way
	debug_echo 'moving .config files with dllmap out of the way'
	if [ ! -d "$gamedir/fnaify-backup" ] ; then
		mkdir -p "$gamedir/fnaify-backup"
	fi
	IFS="
"
	for config_file in "${configfilesarray[@]}"; do
		IFS=$SAVEIFS
		if [ ! -h "$config_file" ] && grep -q "dllmap" "$config_file"; then
			mv "$config_file" "$gamedir/fnaify-backup/"
		fi
	done

	debug_echo "Moving some bundled dll files into fnaify-backup subfolder... "
	for file in $monofilearray; do
		if [ -e "$gamedir/$file" ]; then
			debug_echo "\tFound bundled mono file: $file"
			mkdir -p "$gamedir/fnaify-backup"
			mv "$gamedir/$file" "$gamedir/fnaify-backup/"
		fi
	done
	debug_echo " done."

	# if necessary, replace FNA.dll
	fna_replace=""
	lastdir=""
	if [ $fna_warning -eq 1 ]; then
		if [ "$interaction" = "i" ] ; then
			printf '\nWARNING: version of FNA.dll potentially incompatble!\n'
			echo "Fetch FNA 17.12 from GitHub, compile, and replace bundled FNA.dll?"
			echo "FNA is distributed under Microsoft Public License. The license"
			echo "can be viewed here:"
			echo "https://github.com/FNA-XNA/FNA/blob/master/licenses/LICENSE"
			echo -n "Proceed with replacing FNA with version 17.12 (recommended)? [y/n] "
			until [ "$fna_replace" = "y" ] || [ "$fna_replace" = "n" ]; do
				read -r fna_replace
			done
		elif [ "$interaction" = "y" ] ; then
			fna_replace="y"
		else
			fna_replace="n"
		fi

		if [ "$fna_replace" = "y" ]; then
			install_fna 17.12
			fna_warning=0
		fi
	fi

	symlink_mg_libs
	iomap

	if [ -n "${licenses}" ]; then
		printf 'Installed modules are under the following license(s):\n\n'
		echo "${licenses}"
	fi
	if [ $fna_warning -eq 1 ]; then
		printf '\nWARNING: version of FNA.dll potentially incompatible!\n'
		printf "If the game doesn't run, try running 'fnaify -i' (or 'fnaify -y').\n\n"
	fi
	if [ $nlog_warning -eq 1 ]; then
		printf '\nWARNING: version of NLog.dll potentially incompatible!\n'
		printf "If the game doesn't run, try running 'fnaify -i' (or 'fnaify -y').\n\n"
	fi

	touch .fnaify_ready
}

###	END OF VARIABLES AND FUNCTIONS	###

###################
###	MAIN	###
###################

process_options "$@"
if [ $force_setup -gt 0 ]
then
	setup
	run
else
	[ ! -e "$gamedir/.fnaify_ready" ] && setup
	run
fi