Shared Data on Vista

BlitzMax Forums/BlitzMax Programming/Shared Data on Vista

Grey Alien(Posted 2007) [#1]
OK, so I'm doing the Vista conversion for my frmaework today. My aim is to get a special folder with high scores and other shared data that different Vista users can all read and write to (when UAC is turned on, which it is by default)

So far I've written the following test code (thanks to GfK for the GetSpecialFolder code):

Strict

Extern "Win32"
Function SHGetSpecialFolderLocation(hwndOwner, nFolder, pIdl:Byte  Ptr)
Function SHGetPathFromIDList( pidList ,lpBuffer:Byte  Ptr)
End Extern

Const CSIDL_DESKTOP = $0
Const CSIDL_INTERNET = $1
Const CSIDL_PROGRAMS = $2
Const CSIDL_CONTROLS = $3
Const CSIDL_PRINTERS = $4
Const CSIDL_PERSONAL = $5
Const CSIDL_FAVORITES = $6
Const CSIDL_STARTUP = $7
Const CSIDL_RECENT = $8
Const CSIDL_SENDTO = $9
Const CSIDL_BITBUCKET = $A
Const CSIDL_STARTMENU = $B
Const CSIDL_DESKTOPDIRECTORY = $10
Const CSIDL_DRIVES = $11
Const CSIDL_NETWORK = $12
Const CSIDL_NETHOOD = $13
Const CSIDL_FONTS = $14
Const CSIDL_TEMPLATES = $15
Const CSIDL_COMMON_STARTMENU = $16
Const CSIDL_COMMON_PROGRAMS = $17
Const CSIDL_COMMON_STARTUP = $18
Const CSIDL_COMMON_DESKTOPDIRECTORY = $19
Const CSIDL_APPDATA = $1A
Const CSIDL_PRINTHOOD = $1B
Const CSIDL_LOCAL_APPDATA = $1C
Const CSIDL_ALTSTARTUP = $1D
Const CSIDL_COMMON_ALTSTARTUP = $1E
Const CSIDL_COMMON_FAVORITES = $1F
Const CSIDL_INTERNET_CACHE = $20
Const CSIDL_COOKIES = $21
Const CSIDL_HISTORY = $22
Const CSIDL_COMMON_APPDATA = $23

Function GetSpecialFolder:String(folder_id) 
	Local  idl:TBank = CreateBank (8) 
	Local  pathbank:TBank = CreateBank (260) 
	If SHGetSpecialFolderLocation(0,folder_id,BankBuf(idl)) = 0		
		SHGetPathFromIDList PeekInt( idl,0), BankBuf(pathbank)
		Return String.FromCString(pathbank.Buf()) + "\"
	EndIf
End Function

'Get the correct path
Local path$ = GetSpecialFolder(CSIDL_COMMON_APPDATA)+"Grey Alien Games\My Game\"
Print path

'If the path does not exist, create it.
If ReadDir(path) = 0 Then
	Print "Folder Created="+CreateDir(path,True) 'create subfolders
EndIf

Local f:TStream = WriteStream(path+"test.txt")

If f Then
	Print "Text File Created"
	WriteString(f,"testing123")
	CloseStream(f)
EndIf
	
While Not KeyHit(Key_Escape)
Wend


***Note that I'm compiling this as a non-GUI app so that I can see the print commands in the console window. Do this in the BMax IDE by going to Program/Build Options and making sure that Build GUI App is turned off.***

Here's what I've found out so far, which confirms previous discussion on the topic:


Note that you need to have set folder options to view Hidden and System files otherwise you won't see the Application Data folder on the C drive.

- Log on as a Standard or Admin user. We'll call this User 1.
- Run the code. The code does this:
.....Creates a sub-folder in CSIDL_COMMON_APPDATA in code.
.....Creates a text file in that subfolder in code and writes some text into it.
- Log on as another user (Standard or Admin). We'll call this User 2.
- Go into the new subfolder and open the text file - note how you can read it fine.
- Type some more text and then save it. Note how it will not save! This is because you don't have write permissions to anything under the sub-folder created by User 1!
- Note that you can delete the file however, try it now. Weird - I'd call "deleting" a form or "writing".
- Now run the same code which creates the folder and file. Note how it doesn't say it created a new folder (makes sense) and how it says it successfully creates the text file. BUT look in the folder where you previously deleted the text file. It's not there! That's because the code was unable to write to that subfolder because it still belongs to User1 and thus it has "Virtualised" the text file in: Users\User2\AppData\Local\VirtualStore\ProgramData\GreyAlienGames\MyGames. This is what Vista does so that the app doesn't crash. Problem is that the file is in totally the wrong place for games which may want to share common information like high scores with other users (family members).




Grey Alien(Posted 2007) [#2]
Next up is to convert this C++ code which Indiepath posted a while ago into BMax. Can anyone help please?

bool CgeneralFuncs::CreateDirectoryUserFullAccess(LPCTSTR lpPath)
{
	int f = CreateDirectory(lpPath,NULL);
	if(!f)
		return false;
	
	HANDLE hDir = CreateFile(lpPath,READ_CONTROL|WRITE_DAC,0,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
	if(hDir == INVALID_HANDLE_VALUE)
		return true;
	
	ACL* pOldDACL=NULL;
	SECURITY_DESCRIPTOR* pSD = NULL;
	GetSecurityInfo(hDir,SE_FILE_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,&pOldDACL,NULL,(void**)&pSD);
	
	EXPLICIT_ACCESS ea={0};
	ea.grfAccessMode = GRANT_ACCESS;
	ea.grfAccessPermissions = GENERIC_ALL;
	ea.grfInheritance = CONTAINER_INHERIT_ACE|OBJECT_INHERIT_ACE;
	ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
	ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
	ea.Trustee.ptstrName = TEXT("Users");
	
	ACL* pNewDACL = NULL;
	SetEntriesInAcl(1,&ea,pOldDACL,&pNewDACL);
	
	SetSecurityInfo(hDir,SE_FILE_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDACL,NULL);
	
	LocalFree(pSD);
	LocalFree(pNewDACL);
	CloseHandle(hDir);
	return true;
}



TartanTangerine (was Indiepath)(Posted 2007) [#3]
Just to throw a spanner in the works.....

GetSpecialFolder(CSIDL_COMMON_APPDATA) does not work, it still virtualises! use GetEnvironmentVariable("ALLUSERSPROFILE", stringpointer, MAX_PATH);


Grey Alien(Posted 2007) [#4]
? It worked it my above test. But I'll bear in mind your other option, thanks!

Actually I never tried using that path on another user just to read the file, I'll do that now, then I'll know if it virtualises it.


Dreamora(Posted 2007) [#5]
Are you on a user or an admin account?
Makes a fair difference as well (as enabled / disabled UAC)


Grey Alien(Posted 2007) [#6]
I'm currently testing on Standard user because if I can get it to work with that then it should work with Admin accounts.


Grey Alien(Posted 2007) [#7]
Just to throw a spanner in the works.....

GetSpecialFolder(CSIDL_COMMON_APPDATA) does not work, it still virtualises! use GetEnvironmentVariable("ALLUSERSPROFILE", stringpointer, MAX_PATH);
Tim, can you clarify when GetSpecialFolder does not work please? I have called it when creating my sub-folder and writing the text file into it, then I've changed users (to another standard user) and called it to read the text file from it perfectly fine. So I'm confused as to when it won't work. Thanks!


TartanTangerine (was Indiepath)(Posted 2007) [#8]
GetSpecialFolder creates the folder but always virtualises the damn thing! I found this is Admin and Standard accounts. When I went the GetEnvironmentVariable route virtualisation did not take place and the files were readily available to all users.


Grey Alien(Posted 2007) [#9]
weird because it works here, I logged on as another user and ran some code that called GetSpecialFolder(CSIDL_COMMON_APPDATA) and then read the text file in happily. Certainly if I *WRITE* to that folder it virtualises the write. But the read is fine and safe. Do you think that's what happened for you, you tried writing and it was virtualising?

I presume, even when using the Environment variable code, you still had to change the folder permissions to safely write with all users using that C code of yours yes? I'm halfway through converting it to BMax and it's a PIG.


DStastny(Posted 2007) [#10]
I have spent a fair bit of time dealing with Vista compatibility issues with my company's applications.

Tim, is probably getting virtualized because it virtualized once. Seems once Vista virtualizes exectuable it stays virtualized. Havent figured out how to stop it. Good thing my Q/A can restore to clean machine.

To get it all to work correctly you need to embed a manifest into your executables so that windows loader can forceably disable virtualization. Frankly this is only way to be sure you have coded it all correctly with UAC. As once manifest is in place it will just crash :P if your code is wrong.



Have fun
Doug Stastny


Grey Alien(Posted 2007) [#11]
Hi Budman. Ah yes I've read about manifests. Anyone embedded a manifest in BMax program yet?

Anyway that will prevent virtualisation, but I still want to make this global data store accessible to all users with Tim's code. I damn well hope it works as I've spent all of today converting it painfully into BMax. Although I'm stuck now on creating an array of EA to pass into SetEntriesInAcl() (see other thread) So close and yet so far!!!


JazzieB(Posted 2007) [#12]
Using COMMON_APPDATA works fine here also. Although I currently have the installer set-up the necessary permissions to enable read/write access for everyone.

I've also heard of the manifest thing. I think this is something the BlitzMax compiler should be doing for us, rather than us having to 'hack' it into place following every build.


Grey Alien(Posted 2007) [#13]
Agreed. Yeah I'm trying to make my game set the neccessary permissions on the folder itself so I don't have to rely on an installer which a Portal may not use.


JazzieB(Posted 2007) [#14]
I would have thought that all portals would be using an installer of some description, so should already be setting up the relevant permissions for the folder structure needed for your game/app. Of course, you'll know better than me in this regard, as you've had plenty of experience with portals already, whereas I've had none.

Having said that, it would be good if we could figure out how to do this in code, so we're not having to rely on third parties.


Grey Alien(Posted 2007) [#15]
Absolutely. You can't rely on a portal to get your detailed instructions correct as far as installing is concerned, I'm sure of that!


TartanTangerine (was Indiepath)(Posted 2007) [#16]
Yes I want read/write/execute permissions for all users - your method does not give me that.


Grey Alien(Posted 2007) [#17]
Well it does now.


TartanTangerine (was Indiepath)(Posted 2007) [#18]
I'm afraid it does not on this vista pc :( File writes are still virtualised.


Grey Alien(Posted 2007) [#19]
I'm confused. What you've tried out my code in the other thread? The code which uses GetSpecialFolder, then uses your special C code to give it full permissions, then writes a text file into it?

Sure if I use GetSpecialFolder on it's own and try to write to that folder + subpath (that was created by another user), the file write will fail. But the issue is not with the method of getting the path, it's simply that the write SHOULD fail until the subpath is given full permission (with your code).

Are we singing from the same hymn sheet or am I missing something? Because I don't want to find any problems down the road with the new code.


TartanTangerine (was Indiepath)(Posted 2007) [#20]
Ok, I've debugged it a bit...

1) GetSpecialFolder is not returning a "real" path, it's returning some path that does not exist, at least I can't see it!. The path it should be returing is C:\ProgramData but it's returning C:\Users\All Users\*BULLSHITE*

2) I can't create the correct permissions on a *BULLSHITE* folder :(

3) GetEnvironmentVariable(blah) always returns the correct path :)


Grey Alien(Posted 2007) [#21]
OK, on my XP PC GetSpecialFolder returns:

C:\Documents and Settings\All Users\Application Data\

Exactly what I was expecting. Application Data is a HIDDEN folder by the way. Turn on view hidden files and folders in folder options if you haven't already. Oh wait, you are using Vista, well Vista virtualises to a hidden folder so maybe that's why you can't see the bullshite bit?

What exactly is the "Bullshite" part of the path?

On my Vista laptop it returns C:\ProgramData for me no problems.

Perhaps it's something to with what Budman says about once it's virtualised an exe it keeps on virtualising it and your test code exe is being virtualised for some reason because of some other stuff you are doing?

I've got no problem with switching to GetEnvironmentVar if there's a decent reason. But why are we getting inconsistent results?

Try running this:

Strict

Extern "win32"
	Function SHGetPathFromIDList(pidList, lpBuffer:Byte Ptr)
	Function SHGetSpecialFolderLocation(hwndOwner, nFolder, pIdl:Byte Ptr)
End Extern

Const CSIDL_COMMON_APPDATA = $23

' -----------------------------------------------------------------------------
' ccGetSpecialFolder: Returns a special Windows folder
' (By Dave Kirk)
' -----------------------------------------------------------------------------
Function ccGetSpecialFolder:String(folder_id) 
	?win32
	Local  idl:TBank = CreateBank (8) 
	Local  pathbank:TBank = CreateBank (260) 
	If SHGetSpecialFolderLocation(0,folder_id,BankBuf(idl)) = 0		
		SHGetPathFromIDList PeekInt( idl,0), BankBuf(pathbank)
		Return String.FromCString(pathbank.Buf()) + "\"
	EndIf
	?
	Return ""
End Function

Print ccGetSpecialFolder(CSIDL_COMMON_APPDATA)


It would really help my peace of mind to get to the bottom of why it doesn't work for you. Thanks for all your help! :-)


Grey Alien(Posted 2007) [#22]
Bump, helloooo Tim, are you there? ;-)


BlitzSupport(Posted 2007) [#23]

The path it should be returing is C:\ProgramData but it's returning C:\Users\All Users\*BULLSHITE*


I don't have Vista, but how about creating this folder if it doesn't exist, then creating your sub-folder if that's successful? I assume it's just telling you where it's expecting to find what you're asking for.

Even if you get a different returned path on different systems, it should always be the same for the individual system it's running on, surely?

I mean this sort of thing...




Grey Alien(Posted 2007) [#24]
Yes that's exactly what my code does in the top example.