Game Development Community

Windows 7 Security Permission issue

by PGames · in Torque 3D Professional · 05/03/2011 (1:44 pm) · 10 replies

I'm having a huge problem with the Windows 7 folder permission problem. Once I install our game, whenever it writes to disk, it crashes because it can't, due to the user not having the permission to write into the folder. This can be fixed by just having the security permission of the folder changed to full control, but I'm not gonna bet on all end user knowing what to do (even when I've already put a prompt in there saying exactly that).

So, any fixes on the engine side?

#1
05/03/2011 (4:45 pm)
The main things that get written to the drive are:

1) DSO's (turned off by default already)
2) Procedural Shaders (this *should* be handling a non-writable situation by mounting a memory file system to write to but it is possible something isn't playing nice with this anymore)
3) The prefs for the user (the game shouldn't crash if it can't write this out and doesn't *need* them to run...the user just won't be able to change any prefs...the proper fix is to read/write the prefs to the user's documents directory or to AppData but that would require some work).
4) Fonts (you may need to pre-generate these and include them in the build)
5) Imposters (maybe able to pre-generate these as well)
6) The Material scripts that the Collada loader generates (these should also be pre-generated)

Most likely the crash is being caused by something that either assumes it can read/write or something that fails catastrophically if it can't write out.

Take a look at this code:

void ShaderGen::initShaderGen()
{   
   if (mInit)
      return;

   const GFXAdapterType adapterType = GFX->getAdapterType();
   if (!mInitDelegates[adapterType])
      return;

   mInitDelegates[adapterType](this);
   mFeatureInitSignal.trigger( adapterType );
   mInit = true;

   String shaderPath = Con::getVariable( "$pref::video::shaderGenPath");
#if defined(TORQUE_SHADERGEN) && ( defined(TORQUE_OS_XENON) || defined(TORQUE_OS_PS3) )
   // If this is a console build, and TORQUE_SHADERGEN is defined 
   // (signifying that new shaders should be generated) then clear the shader
   // path so that the MemFileSystem is used instead.
   shaderPath.clear();
#endif

   if (!shaderPath.equal( "shadergen:" ) && !shaderPath.isEmpty() )
   {
      // this is necessary, especially under Windows with UAC enabled
      if (!Torque::FS::VerifyWriteAccess(shaderPath))
      {
         // we don't have write access so enable the virtualized memory store
         Con::warnf("ShaderGen: Write permission unavailable, switching to virtualized memory storage");
         shaderPath.clear();
      }

   }

   if ( shaderPath.equal( "shadergen:" ) || shaderPath.isEmpty() )
   {
      // If we didn't get a path then we're gonna cache the shaders to
      // a virtualized memory file system.
      mMemFS = new Torque::Mem::MemFileSystem( "shadergen:/" ); 
      Torque::FS::Mount( "shadergen", mMemFS );
   }
   else
      Torque::FS::Mount( "shadergen", shaderPath + "/" );

   // Delete the auto-generated conditioner include file.
   Torque::FS::Remove( "shadergen:/" + ConditionerFeature::ConditionerIncludeFileName );
}

This is a good example of how to properly handle a non-writable situation. I would suggest searching the codebase for Torque::FS::File::Write and Torque::FS::File::ReadWrite and checking to make sure that everything that is supposed to be reading and writing either fails gracefully with no write permission or creates an appropriate MemFileSystem if it needs it.
#2
05/03/2011 (6:41 pm)
Matt, how do you write to file (like create a save file) without the write permission? The example above seems to only suggest how to delete a file. Here's how we're doing it right now:

function saveGameRecord(%saveId)
{
   if(!%saveId)
      %saveId = 0;
   %saveFile = new FileObject();   
   %dataFile = expandFilename("data/" @ $Game::GameRecord[%saveId]@ ".cs"); 
   %dataFile_DOS = expandFilename("data/" @ $Game::GameRecord[%saveId] @ ".cs.dso");
   if(isFile(%dataFile)) fileDelete(%dataFile);
   if(isFile(%dataFile_DOS)) fileDelete(%dataFile_DOS);
   //Use the 'openForAppend' work around since loading files does not   
   //  work unless they are already in memory, which openForAppend will   
   //  put them in memory.   
   %saveFile.openForWrite(%dataFile);  
   
   if (%saveId != 0)   
      saveAllRecord(%saveFile); 
   else
      saveGlobalRecord(%saveFile);

   %saveFile.close();   
   %saveFile.delete();  
   exec(%dataFile);
   fileDelete(%dataFile);
}
#3
05/03/2011 (8:54 pm)
@PGames The easiest way of fixing this from what I found is turning off the UAC option in Visual Studio. Doing this Win7 will virtually create a directory for your game somewhere else and link that directory with your game as they are one. Any new file that are created will be created in that virtual directory instead. More info can be found here
[Edit** Note this is a not a recommended way to fix this problem, I ran into the same problem where I had a few hours to resubmit the build with the fixes]

http://msdn.microsoft.com/en-us/library/ee419001%28v=vs.85%29.aspx

And

http://msdn.microsoft.com/en-us/gg465070

The correct way of doing this is to have everything read/write from the user document directory as Matt suggested but that would require some work (TGB does this so if you want you can have a look at TGB).
#4
05/03/2011 (10:15 pm)
I had the same problem with a previous project. I had to alter the NSIS installer script. May want to take a look here:

nsis.sourceforge.net/UAC_plug-in
#5
05/04/2011 (4:33 am)
Quote:
The correct way of doing this is to have everything read/write from the user document directory as Matt suggested
I've always wondered why this wasn't stock for files like prefs.cs ...
#6
05/05/2011 (6:11 pm)
Yeah, got this to work by just throwing all the save files directly to the My Documents folder. Should have thought of that before.
#7
05/09/2011 (10:32 am)
There are script functions that return paths to user folders. These can be useful to re-direct writes to:

getUserHomeDirectory() // C:\Users\<userName>\
getUserDataDirectory() // C:\Users\<userName>\AppData\Local
getTemporaryDirectory() // C:\Users\<userName>\AppData\Local\Temp
#8
05/09/2011 (11:09 am)
Can the engine write to those locations? I always thought that writes were restricted to levels beneath the main folder containing the exe. Would be great if that now works.
#9
05/14/2011 (11:39 am)
We solved this issue by using the script functions that Manoel wrote about - we made a directory for the game in the dir returned by getUserDataDirectory() (for me it returns AppData\Roaming btw...) and made sure that everything that needs to write files writes there. Also that all those files are looked for there as well.

I remember having to change the code in a few places to make this work without a problem, but those changes might have been in already altered code or in custom objects altogether - I can't remember. I *think* adding the directory to the game's mounted directories didn't work out of the box, but don't take my word for it.

The bottom line is that those directories are the way to go imho. Perhaps saving screenshots in the My Pictures or My Documents folder is in order, but I think it's best to dump the procedural stuff and prefs and such in your app's own directory within the user's data directory.
#10
05/18/2011 (11:15 am)
A small prob I've run into with this is saving/loading large files using the Roaming directory (as Konrad points out is returned by getUserDataDirectory on Vista+). This can stall on a network with large files if the user's home directory is not local. It is a trivial change to return the Local data directory...

Here is a bit of info I had bookmarked about the locations for specific types of data on Win systems:

RoamingAppData for user-specific data and settings. This is the directory to use for user-specific non-temporary data. Anything placed here will be available on any machine that a given user logs on to in networks where this is configured. Do not place large files here though, because they slow down login/logout in such environments.

LocalAppData for user-and-machine-specific data and settings. This data differs for every user and every machine. This is also where very large user-specific data should be placed.

ProgramData for machine-specific data and settings. These are the same regardless of which user is logged on, and will not roam to other machines in a network.

GetTempPath for all files that may be wiped without loss of data when not in use. This is also the place for things like caches, because like temporary data, a cache does not need to be backed up. Place your huge cache here and you'll save your user some backup trouble.