Jump to content

  • Log in with Facebook Log in with Twitter Log In with Google      Sign In   
  • Create Account

Tutorial info

  • Added on: Nov 21 2013 07:11 PM
  • Date Updated: Jun 01 2015 11:31 AM
  • Views: 3111


* * * * *
0 Ratings

Linking save files to the user Library

Old PC games placed their save files where the game executable was, but modern operating systems have a dedicated place for such data. This tutorial will explain how to hook up everything to the user Library.

Posted by HiPhish on Nov 21 2013 07:11 PM
Before the advent of Windows XP, and even after, PC games used to place their save files, -folders and user settings files into the same directory as the game's executable. If you port such a game the same will still hold true and your save files and settings will be stored inside the wrapper. This has several disadvantages, for example all users will be accessing the same files or if your wrapper is lost, so are the savegames. On OS X settings made by the user are supposed to be saved in the Library, so we will fix that using symlinks.

Skills needed
You must know how to open and edit files on OS X. Knowledge of Shell scripting would be useful as well, but not required, I will explain everything you need to know.

Tools needed
The wrapper and a text editor, the OS-included TextEdit is good enough.

Prerequisites
None, you can do this either before or after installing the game.

Let's get started; navigate to your wrapper, right-click it, click Show Package Contents, navigate to Contents/Resources and open the file WineskinStartupScript in your text editor. This is a Bash script file, it contains a number of commands that will be executed every time the wrapper is launched. Its contents should look like this:
#!/bin/bash
cd "$(dirname "$0")/../"
CONTENTSFOLD="$PWD"
####################################################
# this script will run when Wineskin is starting up
# before the X server or Wine starts
# $CONTENTSFOLD is the full path to the current
#	   contents folder in the wrapper.
####################################################

# ENTER COMMANDS HERE
This is a script that will be called whenever you launch the wrapper. We will create a new script that does general symlinking of game resources and call that script with parameters from the startup script.

In this tutorial I'll use the game Planescape: Torment as my example. What we want to achieve is link the savegame folder and the Torment.ini settings file. To make things more complicated, the game needs a valid INI file to even start, so we'll need to be extra careful not to delete it. As an extra challenge we'll make the wrapper so it can handle both the CD version and the GOG version. If you want to apply all this to another game simply change the names and paths in the startup script.

Let's start with creating the symlink script. Create a new file named "symlink_game_resources.sh" and save it in the same directory as the startup script.

Let's start by creating some variables. These will hold the boilerplate prefixes of the paths we use. As the script runs we will append the rest of the path.
GAME_EXE=""
GAME_PATH="$SCRIPT_PATH/drive_c"
LIBRARY_PATH="$HOME/Library/Application Support"
Anything starting with # is just a comment. The variables are declared by writing their name, followed by and equal sign and their contents (the quotes are needed when there is spaces). We can then refer to the variables by prefixing them with a dollar sign, like $GAME_PATH.

The GAME_EXE is the executable file of the game and used for detecting whether the game is installed. We use it to prevent symlinking of a game that is not even installed. A wrapper could be made to accommodate the default installation paths of different game releases, like a CD release, a Steam release and a GOG release, all of which have different paths, using the same script.

The other two variables contain the fixed prefix of every path. the GAME_PATH is the path where the game is installed in the wrapper and the LIBRARY_PATH is where the resources lie in the Library. Both variables will have the rest appended later.

We will now declare the core function. A function is simply some code we can call later, instead of writing the same thing several times. Unlike real programming languages we don't declare the amount, types and names of arguments, we simply use $1 for the first argument, $2 for the second one and so on.

symlink_target () {
	# The options must be set, otherwise nothing will work
	if [ -z $GAME_EXE ];
	then
		echo "Error: must specify a game EXE."
		print_usage
		exit 1
	fi

	# First make sure the game exists
	if [ ! -f "$GAME_PATH/$GAME_EXE" ]; then return; fi

	# If the target already exists there is nothing to do anymore
	if [ -e "$LIBRARY_PATH/$1" ]
	then
		return
	fi

	# If the library path does not exist create it
	if [ ! -d "$LIBRARY_PATH" ]
	then
		mkdir "$LIBRARY_PATH"
	fi

	# If the backup does not exist create it
	if [ ! -e "$GAME_PATH/$1.backup" ]
	then
		cp -R "$GAME_PATH/$1" "$GAME_PATH/$1.backup"
	fi

	# If the original does not exist restore it from the backup
	if [ ! -e "$GAME_PATH/$1" ]
	then
		cp -R "$GAME_PATH/$1.backup" "$GAME_PATH/$1"
	fi

	# Move the original over to the library
	mv "$GAME_PATH/$1" "$LIBRARY_PATH/"
	# Symlink from library
	ln -s "$LIBRARY_PATH/$1" "$GAME_PATH/$1"
}

In order to use this function one has to pass the name of the target to link as an argument.

Let's go over this one step at a time. First we check if the game's EXE has even be specified. If it wasn't that's an error and we exit with an error code.

Otherwise we check if the game is installed in the specified location. If it isn't there is nothing else to do, so we exit. Similarly, if the game is installed and everything is symlinked there is nothing to do either.

If the previous two checks did not pass it means there is work to do. First we create to Library directory for our game. Next, we back up the unmodified files, we have to do this because some games won't launch if there are no settings files, and we don't want the wrapper to break when it is moved to a different machine or user. If that is the case, i.e. the is no original file and no symlinked target, we have to restore the original from the backup.

Finally we move the original to the Library and create the symlink.

All that is missing now is actually setting the script in motion. This is done through the command-line parameters passed. Place these lines after the function declaration:

# Zero arguments propmt the usage instructions.
if [ $# -eq 0 ]; then print_usage; exit 1; fi

while [ $# -gt 0 ]; do
	case "$1" in
		--install) GAME_PATH="$GAME_PATH/$2"	   ; shift;;
		--exe	) GAME_EXE="$2"				   ; shift;;
		--library) LIBRARY_PATH="$LIBRARY_PATH/$2" ; shift;;

		*) symlink_target "$1";;
	esac
	shift
done

If the number of arguments passed is 0 we print usage instructions (print_usage is a function I have omitted above for brevity) and exit with an error code. Otherwise we loop over the arguments.

If an argument matches one of these pre-defined options we use the next argument to set one of the variables. We also shift the arguments to skip over the argument we have already processed. If it does not match any of the options the argument is taken to mean a symlink target, so we call our symlink function with that argument. In any case the argument list is shifted in the end.

And that's essentially it, this script can symlink game resources for any game. All that is left now is calling it with the right parameters. Launch the startup script again and add the following lines:
# ENTER COMMANDS HERE
Resources/symlink_game_resources.sh \
	--install "Program Files/Black Isle/Torment" \
	--exe "Torment.exe" \
	--library "Planescape Torment" \
	"Torment.ini" \
	"save"

Resources/symlink_game_resources.sh \
	--install "GOG Games/Planescape Torment" \
	--exe "Torment.exe" \
	--library "GOG.com/Planescape Torment" \
	"Torment.ini" \
	"save"

This will take care of both the CD- and GOG release and place save files in different directories. The backslashes at the end of each line as escapes, the tell the shell to ignore the new line and treat everything as if it was written on the same line.

Of course this script will only work if the user has chosen the default paths for the installation as the author of the script, but since it's stored in a variable you only have to change it in one place if you want another target destination. It is also worth noting that you don't necessarily have to symlink to the user Library, you can symlink anywhere as long as you have write permissions. One idea could be to symlink to your Dropbox and have your own cloud save feature. Have fun.

And just for completeness, here is the full symlink script with all comments:
#!/bin/sh 
#===============================================================================
#
#		  FILE: symlink_game_resources.sh
# 
#		 USAGE: symlink_game_resources.sh <options> <target> ... <target>
# 
#   DESCRIPTION: Map the player's save folder and config file into the user's
#				Application Support folder. The destination depends on whether
#				the user is playing  the retail- or GOG copy. Obviously this
#				will only work if the user chooses the default file paths when
#				installing the games.
# 
#	   OPTIONS: --install <path>  Install path of the game relative to the C
#								  drive
#				--exe	 <file>  Name of the game's executable file
#				--library <path>  Path for the symlinks, relative to the
#								  directory "~/Library/Application Support"
#
#				All paths are passed without leading or trailing slashes.
#
#  REQUIREMENTS: ---
#		  BUGS: ---
#		 NOTES: ---
#		AUTHOR: HiPhish
#  ORGANIZATION: ---
#	   CREATED: 2014-11-23
#	  REVISION: 2015-06-01
#===============================================================================

set -o nounset							   # Treat unset variables as an error

# ---------- File Paths --------------------------------------------------------
GAME_EXE=""
GAME_PATH="$(dirname "$0")/drive_c"
LIBRARY_PATH="$HOME/Library/Application Support"
# ------------------------------------------------------------------------------

#===[ FUNCTIONS ]===============================================================

#---[ FUNCTION ]----------------------------------------------------------------
#		  NAME:  symlink_target
#   DESCRIPTION:  Symlinks a specific file or directory with a copy in the
#				 library.
#
#				 If the game does not exist or the target is already in the
#				 library nothing happens. The next steps only happen if they
#				 are necessary. A backup is made is the game directory, the
#				 original is restored from the backup and is then moved to the
#				 library.
#
#  PARAMETER  1:  Name of the target to symlink. Directories must not be termi-
#				 nated with a slash.
#
#	   RETURNS:  ---
#-------------------------------------------------------------------------------
symlink_target () {
	# The options must be set, otherwise nothing will work
	if [ -z $GAME_EXE ];
	then
		echo "Error: must specify a game EXE."
		print_usage
		exit 1
	fi

	# First make sure the game exists
	if [ ! -f "$GAME_PATH/$GAME_EXE" ]; then return; fi

	# If the target already exists there is nothing to do anymore
	if [ -e "$LIBRARY_PATH/$1" ]
	then
		return
	fi

	# If the library path does not exist create it
	if [ ! -d "$LIBRARY_PATH" ]
	then
		mkdir "$LIBRARY_PATH"
	fi

	# If the backup does not exist create it
	if [ ! -e "$GAME_PATH/$1.backup" ]
	then
		cp -R "$GAME_PATH/$1" "$GAME_PATH/$1.backup"
	fi

	# If the original does not exist restore it from the backup
	if [ ! -e "$GAME_PATH/$1" ]
	then
		cp -R "$GAME_PATH/$1.backup" "$GAME_PATH/$1"
	fi

	# Move the original over to the library
	mv "$GAME_PATH/$1" "$LIBRARY_PATH/"
	# Symlink from library
	ln -s "$LIBRARY_PATH/$1" "$GAME_PATH/$1"
}

#---[ FUNCTION ]----------------------------------------------------------------
#		  NAME:  print_usage
#   DESCRIPTION:  Prints usage instructions to the standard output.
#
#	   RETURNS:  ---
#-------------------------------------------------------------------------------
print_usage () {
	echo "Usage: symlink_game_resources.sh [options] target1 ... targetn"
	echo "  --install: Install path of the game relative to the C drive"
	echo "  --exe	: Name of the game's executable file"
	echo "  --library: Path for the symlinks, relative to the directory"
	echo "			 \"~/Library/Application Support\""
	echo "  target1  : First directory of file to symlink."
	echo "  ..."
	echo "  targetn  : Last directory of file to symlink."
	echo ""
	echo "Options can be intermixed with sources, overwriting previous options. Target"
	echo "directories must not be terminated with a slash. Specifing the options is"
	echo "mandatory before specifying a target, but the order of options is irrelevant."
	echo ""
}

#=== Processing Arguments =====================================================

# Zero arguments propmt the usage instructions.
if [ $# -eq 0 ]; then print_usage; exit 1; fi

while [ $# -gt 0 ]; do
	case "$1" in
		--install) GAME_PATH="$GAME_PATH/$2"	   ; shift;;
		--exe	) GAME_EXE="$2"				   ; shift;;
		--library) LIBRARY_PATH="$LIBRARY_PATH/$2" ; shift;;

		*) symlink_target "$1";;
	esac
	shift
done
(If the spacing is broken that's because of website messing things up)
3 Comments
Approved.
  • Report
Why not just create a symbolic link and name it "Saves" or whatever the game save folder is named, and just place it in there?
  • Report
You can do that, in fact that's exactly what my script is doing. The idea behind this tutorial is to explain how to automate the process for porters. You want your wrapper to work as simply as possible, so you don't want to have the user go through the terminal and do it him- or herself.
  • Report
Powered by Tutorials 1.3.1 © 2021, by Michael McCune