Shared Data on Vista
BlitzMax Forums/BlitzMax Programming/Shared Data on Vista
| ||
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). |
| ||
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; } |
| ||
Just to throw a spanner in the works..... GetSpecialFolder(CSIDL_COMMON_APPDATA) does not work, it still virtualises! use GetEnvironmentVariable("ALLUSERSPROFILE", stringpointer, MAX_PATH); |
| ||
? 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. |
| ||
Are you on a user or an admin account? Makes a fair difference as well (as enabled / disabled UAC) |
| ||
I'm currently testing on Standard user because if I can get it to work with that then it should work with Admin accounts. |
| ||
Just to throw a spanner in the works..... 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! GetSpecialFolder(CSIDL_COMMON_APPDATA) does not work, it still virtualises! use GetEnvironmentVariable("ALLUSERSPROFILE", stringpointer, MAX_PATH); |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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!!! |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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! |
| ||
Yes I want read/write/execute permissions for all users - your method does not give me that. |
| ||
Well it does now. |
| ||
I'm afraid it does not on this vista pc :( File writes are still virtualised. |
| ||
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. |
| ||
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 :) |
| ||
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! :-) |
| ||
Bump, helloooo Tim, are you there? ;-) |
| ||
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... |
| ||
Yes that's exactly what my code does in the top example. |