Game Development Community

dev|Pro Game Development Curriculum

Avoid Windows UAC Put Prefs,cs In User's Documents

by Steve Acaster · 07/01/2013 (10:03 am) · 11 comments

Torque 3D writes it's temporary files locally - which is not going to happen if it's located in the Program Files directory - which may not be so great for the end user. This resource should put all temporary files like prefs in the user's documents folder.

-----------------

First up, you'll have to decide when you want the prefs to be saved to the user's "home" directory - better know as My Documents. For this demonstration we will simply check if the prefs.cs exists in it's usual place of game/scripts/client - if it does we'll keep using that, if it doesn't we'll try to use the user's own directories.

In reality you would probably want to check if the game is for shipping to make this change, possibly by checking whether it's a tools build or not.

//psuedo code
if(isToolBuild())
   echo("prefs go in game directory");
else
   echo("prefs go in user directory");

The key file is **** scripts/client/defaults.cs **** which executes core/scripts/client/defaults.cs and then calls the prefs file to overwrite any changes.

As stock it looks like this:
// First we execute the core default preferences.
exec( "core/scripts/client/defaults.cs" );


// Now add your own game specific client preferences as
// well as any overloaded core defaults here.
$PhysXLogWarnings = false;



// Finally load the preferences saved from the last
// game execution if they exist.
if ( $platform !$= "xenon" )
{
   if ( isFile( "./prefs.cs" ) )
      exec( "./prefs.cs" );
}
else
{
   echo( "Not loading client prefs.cs on Xbox360" );
}

Xenon ... I think we can dispense with that, can't we ... ;)

We will introdue a new $global for storing the destination of prefs.cs = $prefPath. When setting the $prefPath to the user's directory we have a choice of 2 destinations:
Users/My Documents = getUserHomeDirectory();
or
Users/AppData/Roaming = getUserDataDirectory();

Take your pick, we'll use the Home directory - but if it's unavailable (for whatever horrible reason) we'll try Data directory - and if this fails we'll revert to game directory ... which isn't what we wanted ...

We'll also use the $appName of your game to name the folder in the directory.


//...

// Finally load the preferences saved from the last
// game execution if they exist.
if(isFile("./prefs.cs"))//check for local prefs first
   $prefpath = "scripts/client";
else
{
   echo("no prefs in game directory - let's set the directory to the users");
   %temp = getUserHomeDirectory();
   echo(%temp);
   if(!isDirectory(%temp))
   {
      echo("c2cannot find home user document folder");
      %temp = getUserDataDirectory();
      echo(%temp);
         
      if(!isDirectory(%temp))
      {
         echo("c2cannot find home user appdata roaming folder");
         $prefpath = "scripts";
      }
      else
      {
         //put it in appdata/roaming
         $prefpath = %temp @ "/" @ $appName @ "/scripts";
      }
   }
   else
   {
      //put it in user/documents
      $prefPath = %temp @ "/" @ $appName @ "/scripts";
   }
}

echo("path for preferences = " @ $prefPath);

//and finally execute prefs if the file already exists
if(isFile($prefPath @ "/client/prefs.cs");
   exec($prefPath @ "/client/prefs.cs");

And next up ... exporting our prefs to the new directory. Open up scripts/main.cs. This is how it looks in stock.

//...
   echo("Exporting client prefs");//yorks need to check for prefs.cs destination
   export("$pref::*", "./client/prefs.cs", False);
//...

And this is what we want to change it to:

echo("Exporting client prefs");//yorks need to check for prefs.cs destination
   //export("$pref::*", "./client/prefs.cs", False);
   export("$pref::*", $prefpath @ "/client/prefs.cs", False);//yorks

Disclaimer: I've not bothered with the lower multiplayer stuff as my project is single player only and so I simply commented it out.

However you probably want it in so do the same in main.cs for the server preferences and banlist which are right below the client prefs, and the other files to change are scripts/server/defaults.cs which want to use the $prefPath system and also some server/prefs.cs located in corescriptsserverserver.cs.

There is also custom bind keys to think about in scripts/client/config.cs ---- and ShaderGen doesn't seem to want to cache anywhere but in game/shaders/proceedural, I've not looking into why.

Anyhow that's the gist? ... jist? ... of it all.

If you clean your prefs with the .bat file, restart your project, mess with the options, and save, the new client prefs file should be in a new folder in users/documents/projectname.

One last thing to note if you expand this to use the $prefPath system as a save game folder, games saved to user's directory will automatically be relative/expanded so you need to make a check in core/scripts/server/server.cs and change this:
function createServer(%serverType, %level)
{
//...
   // Make sure our level name is relative so that it can send
   // across the network correctly
   //%level = makeRelativePath(%level, getWorkingDirectory());//yorks out!
   //yorks NO! This will prevent saved games from working

   //yorks start in
   %work = getWorkingDirectory();//yorks new
   %gameDir = isFile(%work @ "/" @ %level);//yorks new
   if(%gameDir)//is level in game directory make path relative else path is already relative
      %level = makeRelativePath(%level, getWorkingDirectory());
   
   echo("c4loading from " @ %level @ " inside game directory = " @ %gameDir);
//yorks end

   destroyServer();
//...

#1
07/02/2013 (7:32 am)
And let's stick screenshots in the users home/data folder too. In core/scripts/client/screenshot.cs

function _screenShot( %tiles, %overlap )
{
//...
   %name = expandFileName( %name );

//yorks start
   if($prefPath !$="scripts")
      %name = $prefPath @ "/" @ %name;
//yorks end
   
   $screenshotNumber++;
//...

So that just leaves generated shaders and console.log unable to find the local user's home/data folder ...
#2
07/02/2013 (9:42 am)
I was thinking if it would be better to save the prefs this way. That and save files. IE like documents/my game/[GAMENAME]/
#3
07/02/2013 (9:51 am)
/// This is the path used by ShaderGen to cache procedural
/// shaders.  If left blank ShaderGen will only cache shaders
/// to memory and not to disk.
$shaderGen::cachePath = "shaders/procedural";
#4
07/02/2013 (9:55 am)
Saving things out to the "proper" user directory is/was actually on the agenda :)
#5
07/02/2013 (1:23 pm)
Shaders don't seem to generate outside of getWorkingDirectory() so I guess that's hard coded. I also had a look through C++ to find out how console.log was put together but it was baffling. ;)
#6
07/03/2013 (8:33 am)
shaderGen.cpp uses the torque file system, which may be limited to the working directory. However, the file generation already works with UAC and, if by some small chance it is denied, can create a virtual file system to store the generated files.

console.cpp does not specify a path for the console.log file, but it can use any path you change it to. One option is to change defLogFileName to look up a variable, but you might not be able to set that variable in time. Another option is to do your user directory checks within console.cpp.
#7
07/03/2013 (1:55 pm)
For variation on the theme:
in root main.cs right under

// Set profile directory
$Pref::Video::ProfilePath = "core/profile";

function getPrefpath()
{
	echo("no config in game directory - let's set the directory to the users");
	%temp = getUserHomeDirectory();  
	echo(%temp);  
	if(!isDirectory(%temp))  
	{  
		echo("c2cannot find home user document folder");  
		%temp = getUserDataDirectory();  
		echo(%temp);
		if(!isDirectory(%temp)) 
		{
			echo("c2cannot find home user appdata roaming folder");  
			$prefpath = "scripts";  
		}
		else  
		{
			//put it in appdata/roaming
			$prefpath = %temp @ "/" @ $appName @ "/scripts";  
		}  
	}  
	else  
	{  
		//put it in user/documents  
		$prefPath = %temp @ "/" @ $appName @ "/scripts";  
	}
	return $prefPath;
}

%gameroot%\game\scripts\gui\OptionsDlg.cs
function OptionsDlg::onSleep(%this)
{
   // write out the control config into the rw/config.cs file
   //moveMap.save( "scripts/client/config.cs" );
   moveMap.save( getPrefpath() @ "/client/config.cs" );
}

%gameroot%\game\scripts\client\init.cs
// Default player key bindings
   exec("./default.bind.cs");
   
	// Load the preferences saved from the last  
	// game execution if they exist.  
	if(isFile("./config.cs"))//check for local config first  
		$prefpath = "scripts/client";  
	else  
	{  
		getPrefpath();
	}  
  
	//and finally execute prefs if the file already exists  
	if(isFile($prefPath @ "/client/config.cs"))
		exec($prefPath @ "/client/config.cs");

As a sidenote, once folks decide on a standard, probably be a good idea to extend that functionality to a secondary movemap binding save for the vehicle movemap.
#8
07/03/2013 (1:58 pm)
This might become handy, thanks Mr. Acaster.
#9
07/04/2013 (5:52 am)
Thanks for this great resource Steve Acaster.
#10
07/04/2013 (5:04 pm)
This is a great resource Steve and just the thing needed to actually get things published!
#11
07/05/2013 (7:39 am)
The correct way of doing this is to rework Engine/source/platformWin32/winFileio.cpp to optionally work exactly like Engine/source/platformX86UNIX/x86UNIXFileio.cpp in regards to performing file I/O and redirecting it to a user's home or applications directory instead of to the game directory whenever it can, especially writes. That is the only proper way of doing it in my opinion.