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

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

Revision 1.192, Wed Feb 10 17:05:08 2021 UTC (8 months, 1 week ago) by thfr
Branch: MAIN
Changes since 1.191: +59 -0 lines

more iomap; Diehard Dungeon support

#!/usr/bin/env ksh

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)

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=
setup_frameworkmajor=
frameworkminor=
setup_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
needlibarray="" # array that will hold the names of needed libraries

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()
{
	if [ -z "$FNAIFY_DEBUG" ]; then
		return
	fi
	if [ "${1}" = '-n' ]; then
		printf "$2"
	else
		printf "$1\n"
	fi
}

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

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

# 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 1 if $1 is in ignoredarray, otherwise 0
{
	libnam="$(trunklibnam $1)"
	if [ -n "$(echo \"$ignoredarray\" | grep $libnam)" ]; then
		return 1
	elif [ -n "$(echo \"$libnam\" | grep '\.dll[ \t]*')" ]; then
		return 1
	else
		return 0
	fi
}

symlink_mg_libs()
{
	mg_subdirs="x64
x86"
	debug_echo "\nChecking for MonoGame x64/x86 dir to create symlinks"

	
	if [ \( ! -e "$gamedir/x64" \) -a \( ! -e "$gamedir/x86" \) ]; then
		debug_echo "\nCouldn't find directories x64 or x86 in $gamedir"
	else
		for d in ${mg_subdirs[@]} ; do
			debug_echo "\nProcessing library directory $gamedir/$d"
			for file in $(ls "$gamedir/$d"); do
				# sort out libs that need to be ignored
				validlib $file
				if [ $? -eq 1 ]; 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=
					echo "\n\t - ERROR: couldn't find system lib for $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 "executable assembly selection routine"
	IFS="
"
	for xfile in $(cd "$gamedir" && find . -maxdepth 1 -type f -iname "*.exe" | cut -f 2 -d "/" | sort)
	do
		if (	[ -z "$(echo "$xfile" | grep vshost)" ] && \
			[ -z "$(echo "$xfile" | grep -i config)" ] && \
			[ -z "$(echo "$xfile" | grep dotNetFx4)" ] && \
			[ -z "$(echo "$xfile" | grep Updater)" ]
		) ; then
			exefile="$exefile$xfile:"
			nexefile=$((nexefile + 1))
		fi
	done
	IFS=$SAVEIFS
	if [ $nexefile -gt 1 ]; then
		if [ "x$interaction" = "xy" ] ; 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 [ \( $(echo "$input_exe" | wc -c) -gt 2 \) -o \( -z "$(echo "$input_exe" | grep [1-9])" \) -o \( $input_exe -gt $((i - 1)) \) ]; do
				echo -n "Enter number of .exe file to run: "
				read 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
		echo "ERROR: no .exe file found\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 [ "x$(cat "$gameconfig" | grep -m 1 "fnaify version $FNAIFY_VERSION")" = "x" ] ; 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()
{
	debug_echo ""
        version="$1"
	if [ ! -e "$gamedir/$version" ]; then
		debug_echo "Couldn't find library directory $gamedir/$version\n"
	else
		debug_echo "\nEntering library directory $gamedir/$version"
		for file in $(ls "$gamedir/$version"); do
			# sort out libs that need to be ignored
			validlib $file
			if [ $? -eq 1 ]; then
				debug_echo "\tignored file: $file"
				continue
			fi
			debug_echo -n "\tfound library file: $file"
			file=$(trunklibnam "$file")
			debug_echo -n " -> $file"
			inarray $file ${needlibarray}
			if [ $? -eq 0 ]; then
				debug_echo " - already in array"
			elif [ $? -eq 1 ]; then
				needlibarray="$needlibarray$file "
				debug_echo ""
			else
				echo "\n\t - ERROR: inarray returned with unexpected error\n"
				exit 1
			fi
		done
		debug_echo "Done with library directory $gamedir/$version"
	fi
}

check_remove_steamworks()	# check if Steamworks.NET.dll is present, move it away if stub is in $monopath
{
	# remove leading and trailing ':'
	monopath="$(echo "$monopath" | sed -E 's/^://' | sed -E 's/:$//')"
	IFS=$SAVEIFS
	if [ -e "$gamedir/Steamworks.NET.dll" \
		-a -n "$monopath" -a -n "$(ls $(echo "$monopath" | tr -s ':' ' ') | grep -m 1 "Steamworks\.NET\.dll")" ]
	then
		if $(ls $(echo "$monopath" | tr -s ':' ' ') | grep -q "Steamworks\.NET\.dll"); then
			debug_echo "\nFound Steamworks.NET.dll in gamedir and in monopath, moving it out of gamedir"
			mkdir -p "$gamedir/fnaify-backup"
			mv "$gamedir/Steamworks.NET.dll" "$gamedir/fnaify-backup/"
		fi
	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
	echo "\nInstalling FNA ${fna_version} ..."
	licenses="${licenses}FNA:\t\tMs-PL (https://github.com/FNA-XNA/FNA/blob/master/licenses/LICENSE)${newline}"
	lastdir="$PWD"
	cd /tmp
	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 \) -a \( x$2 = 'xxna_bridge' \) ]; then
		echo "\nInstalling FNA.NetStub for XNA ..."
		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"
}

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 \) -a \( "x$interaction" = "xi" \) ] ; then
			echo -n "\nInstall NLog 4.6.5 from www.nuget.org? [y/n] "
			response=
			until [ \( "x$response" = "xy" \) -o \( "x$response" = "xn" \) ]
			do
				read response
			done
		elif [ \( $nlogmajor -lt 4 \) -a \( "x$interaction" = "xy" \) ] ; then
			response="y"
		else
			response="n"
		fi
		if [ "x$response" = "xy" ] ; 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
			echo "\nInstalling NLog 4.6.5 ..."
			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"
			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=1 ;;
			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
	}
	"$FNAIFY_MONO" --version 2>/dev/null >/dev/null
	if [ "$?" -gt 0 ]; 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
	monodis 2>/dev/null
	if [ "$?" -eq 127 ]; 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"
	IFS='
'
	for f in $(ls "$gamedir" | egrep "\.exe"); do
		IFS=$SAVEIFS
		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
				;;
			"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
				;;
		esac
	done
	IFS=$SAVEIFS
}

xnasetup()
{
	if [ ! -d "$fnadir" ] ; then
		if [ "x$interaction" = "xi" ] ; then
			echo -n "\nInstall FNA files from GitHub to for this XNA application? [y/n] "
			response=
			until [ \( "x$response" = "xy" \) -o \( "x$response" = "xn" \) ]
			do
				read response
			done
		elif [ "x$interaction" = "xy" ] ; then
			response="y"
		else
			response="n"
		fi

		if [ "$response" = "y" ]; then
			install_fna latest xna_bridge
		else
			echo "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"
	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"
	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
		echo "$(monodis --assembly "$1" | fgrep 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()
{
	( [ -n "$(ls "$gamedir" | grep -m 1 "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"

	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|Terraria.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 "
			[ "x$gameconfig" = "x$(readlink "${f}.config")" ] \
				|| ln -sf "$gameconfig" "${f}.config"
		}
	done
	gameconfigsymlink="${gameconfigsymlink}${my_exe}.config "
	[ "x$gameconfig" = "x$(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" \) -a \( -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"
	debug_echo "gamedir: $PWD"	# cd'd into direct above

	debug_echo "Setting up the framework files"
	get_setup_frameworkfile
	debug_echo "	framework file:			$setup_frameworkfile"
	setup_frameworkversion="$(get_frameworkversion "$setup_frameworkfile")"
	setup_frameworkmajor="$(get_frameworkmajor $frameworkversion)"
	setup_frameworkminor="$(get_frameworkminor $frameworkversion)"

	debug_echo "	framework version:	$setup_frameworkversion"

	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 [ -n "$(ls "$gamedir" | grep -m 1 "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
				inarray libatomstb.so $needlibarray
				if [ $? -eq 1 ]; then
					needlibarray="${needlibarray}libatomstb.so "
					debug_echo "\tlibatomstb.so added to array"
				fi
			fi
		elif [ \( -n "$(ls "$gamedir" | grep -im 1 "eliza.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -im 1 "molek-syntez.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -im 1 "exapunks.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -im 1 "lightning.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -im 1 "shenzhen.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -im 1 "spacechem.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -m 1 "Hammerwatch.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -m 1 "SOR4.exe")" \) \
			-o \( -n "$(ls "$gamedir" | grep -m 1 "MobiusFront83.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 {} \; \
				| fgrep "Mono/.Net assembly"); do
				f="$(echo $f | cut -d : -f 1)"
				debug_echo "Checking $f for XNA"
				[ -n "$(monodis --assemblyref "$f" 2>&1 | grep -im 1 "Microsoft.Xna.Framework")" ] \
					&& { found_xna=1; break; }
			done
			IFS=$SAVEIFS
			[ $found_xna -eq 1 ] \
				&& { debug_echo "Found XNA reference"; xnasetup; } \
				|| { echo "No FNA, MonoGame or XNA reference found"; exit 1; }
		fi
	fi

	if [ "x$setup_frameworkfile" = "xFNA.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="
"
	for cfile in $(ls "$gamedir" | grep "\.config$"); do
		debug_echo "\tfound config file: $cfile"
		configfilesarray[$((${#configfilesarray[*]} + 1))]="$cfile"
		nconfigfilesarray=$((nconfigfilesarray + 1))
	done
	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
		MidBoss=0
		ls "$gamedir" | grep -iqm 1 "midboss"
		if [ $? -eq 0 ]; then
			MidBoss=1
			needlibarray="${needlibarray}libSDL2_image_compact.so "
		fi

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

		debug_echo "Obtaining library names from the following 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")
				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
					validlib $libstring
					if [ $? -eq 1 ]; then
						debug_echo " - ignored"
						continue
					fi
					libstring=$(trunklibnam "$libstring")
					debug_echo -n " -> $libstring"
					# add to libstring to needlibarray if not in there yet
					inarray $libstring $needlibarray
					if [ $? -eq 0 ]; then
						debug_echo " - already in array"
					elif [ $? -eq 1 ]; then
						needlibarray="$needlibarray$libstring "
						debug_echo " - added to array"
					else
						echo "\n\t - ERROR: inarray returned with unexpected error\n"
						exit 1
					fi
				done
			done
			debug_echo "Done with identifying libraries in config files"
		fi
		debug_echo "Done with identification of needed libraries."
	fi

	debug_echo -n "Fixing libpng filenames if present..."
	needlibarray=$(echo "${needlibarray}" | sed -E "s/(libpng)..(\.so.*)/\1\2/")
	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 -n "Checking installed libraries... "
	debug_echo ""

	# 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
		echo "\nCould not find the following libraries:\n
		${missinglibs}\n"
		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" \) -a -n "$(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 [ "x$interaction" = "xi" ] ; then
			echo "\nWARNING: version of FNA.dll potentially incompatble!"
			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" -o "$fna_replace" = "n" ]; do
				read fna_replace
			done
		elif [ "x$interaction" = "xy" ] ; then
			fna_replace="y"
		else
			fna_replace="n"
		fi

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

	check_remove_steamworks
	symlink_mg_libs
	iomap

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

	touch .fnaify_ready
}

###	END OF VARIABLES AND FUNCTIONS	###

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

process_options $@
[ $force_setup -eq 1 ] && (setup; run) || \
	( [ -e "$gamedir/.fnaify_ready" ] && run || (setup; run) )