Game Development Community

File Writing on Non-Admin accounts

by Robert Fritzen · in Torque 3D Professional · 07/22/2012 (8:50 am) · 7 replies

There is a problem with file writing in T3D (and I think all of the engines). When you run your game on a non-administrator account, fileObjects cannot write to the game's directory (assuming you're installing it to program files or other typical protected folders).

I advise making the game's executable require UAC Elevation to give the game these administrator privileges to avoid these kind of issues. I've experienced multiple access violation errors reading/writing to 0x00000000 due to this issue.

#1
07/22/2012 (9:38 am)
The problem is that modern versions of windows expect programs to use different directories for executables and data. Subdirectories of Program Files should only store executables, data should be saved in a separate user specific location such as AppData. For T3D, this is where compiled shaders, prefs etc should be going, but unfortunately T3D hasn't been brought in line with the requirements of modern OS's.
#2
07/22/2012 (10:26 am)
I remember Thomas Dickerson and I tried to fix this in TGE. It wasn't easy - getting the path to AppData wasn't trivial, unless we were just being really dumb.
#3
07/22/2012 (11:26 am)
Quickly researched hastily tested pseudocode:

#include <windows.h>
...

int size = GetEnvironmentVariable("AppData", buffer, BUFFER_SIZE);
if (size == 0) fail("WTF? No AppData defined?!");
else if (size == BUFFER_SIZE) fail("AppData path too long!")
else we're good!

Should work in anything from XP / Server 2003 and newer, according to MSDN. I tested it on XP.

It should be fairly easy to modify TGE to use the same system that it already uses on Linux to handle home directories.

Thanks for bringing this to my attention. I shall have to fix this in my game. I rail against software developers who require their apps to be run with Admin privileges; there's no way I could release my game in such a state. Frankly, it's shameful that this hasn't been done already in T3D.
#4
07/22/2012 (3:05 pm)
I spent a couple of hours working on this, I hope you guys will find it useful. First we need to implement the ability to access environment variables, so let's do just that. I have the following console function stored in Engine/source/console/scriptFilename.cpp
DefineConsoleFunction( getEnvironmentVariable, const char*, (const char *variableName),,
				"@brief Returns the value of the requested operating system's environment variable.\n"
				"@param variableName Name of the environment variable\n"
				"@return String value of the requested environment variable.\n"
				"@ingroup Console")
{
	// get requested environment variable value
	const char *result = getenv(variableName);

	// verify successful operation
	if(result)
	{
		// success, allocate return space and return the result
		char *ret = Con::getReturnBuffer(dStrlen(result) +1);
		dStrcpy(ret, result);
		return ret;
	}

	// fail, environment variable doesn't exist
	return "";
}

Next, created the following TorqueScript utility function:
// retrieves the absolute path to the game's writable file storage directory
function GetUserDataPath()
{
/*
	Notes on how to determine the operating system.

	$platform values determine OS:
	"windows"	Microsoft Windows
	"macos"		Apple Mac OS
	"x86UNIX"	UNIX (POSIX compliant) operating system, see $platformUnixType
	"xenon"		XBox 360
	
	$platformUnixType values determine which UNIX OS:
	"Linux"		a Linux distribution
	"OpenBSD"	OpenBSD (engine uses __OpenBSD__ macro to determine this)
	"Unknown"	other UNIX operating system
*/
	%separator = "/";

	// retrieve the path to where programs should store their writable files
	if($platform $= "windows")
	{
		// try getting the local application data path (typically available only on Vista and above)
		%path = getEnvironmentVariable("LOCALAPPDATA");
		
		// if it failed then we're on Windows XP or below
		if(%path $= "")
		{
			// get Application Data path, note that this is not the Local Settings variant
			%path = getEnvironmentVariable("APPDATA");
		}
		
		%path = %path @ %separator;
	} else
	{
		// get home path
		%path = getEnvironmentVariable("HOME");
		
		if($platform $= "macos")
			%path = %path @ "/Library/Application Support/";
		else
			%path = %path @ "/.";
	}
	
	// append game specific directory name
	if($appDataDirName $= "")
		%path = %path @ $appName @ %separator;
	else
		%path = %path @ $appDataDirName @ %separator;
	
	// create the directory if it doesn't exist
	// Note: createPath() only accepts '/' (not '\') as a trailing slash
	if(!IsDirectory(%path))
		createPath(%path);

	// return the full path to the game's writable directory
	return %path;
}

Note that the utility function will go ahead and try to create the directory for you so that you can make of use that path immediately after calling it. Here's also a truth table of what to expect out of GetUserDataPath():

/*
GetUserDataPath() Truth Table:


When:
$appDataDirName = ""; // or not defined at all
$appName = "Playground2";

Results:
<= Windows XP: C:\Documents and Settings\{username}\Application Data\Playground2
>= Windows Vista: C:\Users\{username}\AppData\Local\Playground2
Mac OS X: /Users/{username}/Library/Application Support/Playground2
Linux: /home/{username}/.Playground2


When:
$appDataDirName = "MyCompany/Playgnd2";
$appName = "Playground2";

Results:
<= Windows XP: C:\Documents and Settings\{username}\Application Data\MyCompany/Playgnd2
>= Windows Vista: C:\Users\{username}\AppData\Local\MyCompany/Playgnd2
Mac OS X: /Users/{username}/Library/Application Support/MyCompany/Playgnd2
Linux: /home/{username}/.MyCompany/Playgnd2

*/

Updated: Fixed bad handling of paths between Mac OS and UNIX/Linux paths.
#5
07/22/2012 (3:42 pm)
@Nathan,
This should be a resource so it gets the visibility it deserves. This is excellent work.
#6
07/22/2012 (6:52 pm)
Thanks, this will make things run more smoothly now.
#7
07/23/2012 (1:01 am)
I also thought that TGE and TGEA were 'sandboxed' to some extent, and unable to read/write/exec files that were in directories above the one where the .exe resided. I haven't actually checked if this is still the case with T3D.