Changing my save data location to APPDATA

BlitzMax Forums/BlitzMax Programming/Changing my save data location to APPDATA

Grey Alien(Posted 2012) [#1]
Hi all, for years I've been using CSIDL_COMMON_APPDATA to store my game's ini file, high scores and profiles. However to use that I had to set read/write file permissions for everyone otherwise it wouldn't always work. Sometimes I still get error reports where this process has gone a bit wrong due to some weirdness on their system.

Anyway I'm thinking of changing to CSIDL_APPDATA (not CSIDL_LOCAL_APPDATA). CSIDL_APPDATA supports roaming. Anyway, because it belongs to the user I'm pretty sure I won't need to set all those file permissions like before but I'd like some confirmation. Is anyone else using this location and is is straightforward? Thanks!


Grey Alien(Posted 2012) [#2]
Actually maybe it's better to save in Documents/My Games so that players can make backups of their data and transfer to another computer etc easily because AppData is normally a hidden folder.

Only problem is to get that path you have to use FOLDERID_SavedGames which is only on Vista/Win7 so for XP you still need to use APPDATA. I'm getting hold of some sample code in C++ which may help me do that and I'll see if I can post it soon.


BlitzSupport(Posted 2012) [#3]
I use CSIDL_APPDATA in a little PureBasic utility, where I create my own folder if it doesn't exist, then read/write my settings file from there. Just tested on a non-Administrator account and it creates from scratch, reads and writes just fine. It returns the 'Roaming' folder without any special action on my part:

C:\Users\Test\AppData\Roaming\[MyApp]


Also, here's some crude mainstream-Windows detection code for Max, not widely tested!

Last edited 2012


Grey Alien(Posted 2012) [#4]
Perfect, just what I wanted to know thanks James!


Pengwin(Posted 2012) [#5]
Have you tried Brucey's Volumes module for a cross platform solution?


Grey Alien(Posted 2012) [#6]
No I haven't but I've got the right path on the Mac so I'm good thx. Might do Linux in the future, we'll see.

Last edited 2012


Grey Alien(Posted 2012) [#7]
OK here's the C++ which does:

a) See if the OS is Vista and above (which we have some BMax equivalent for above via James)
b) load in a Vista specific dll symbol
c) get the FOLDERID_SavedGames path.

And if the above fail because it's XP then it uses CSIDL_LOCAL_APPDATA although using just CSIDL_APPDATA (which points at the roaming folder) would also be fine.

What I'd love some help with is converting this code (or the bits which load in the Vista dll symobol and get the path) because I already have this code that works in XP:

' -----------------------------------------------------------------------------
' ccGetSpecialFolder: Returns a special Windows folder
' By Dave Kirk
' -----------------------------------------------------------------------------
?win32
Extern "win32"
	Function SHGetPathFromIDList(pidList%, lpBuffer:Byte Ptr)
	Function SHGetSpecialFolderLocation(hwndOwner%, nFolder%, pIdl:Byte Ptr)
End Extern

'For use with ccGetSpecialFolder
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 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


Anyway here's the C++ code. Anyone feeling helpful? Thanks in advance :-)

#include <string>
#include <cassert>

#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <shlobj.h>
#include <Shellapi.h>

// Check if Vista is supported
bool SupportsVistaFunctionality()
{
	OSVERSIONINFO versionInfo;
	ZeroMemory(&versionInfo, sizeof(OSVERSIONINFO));
	versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&versionInfo);
	return versionInfo.dwMajorVersion >= 6;
}

// Declare function pointers to be dynamically-loaded
typedef HRESULT (WINAPI* tSHGetKnownFolderPath)(const GUID& rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath);
static tSHGetKnownFolderPath GetKnownFolderPath = 0;
static bool s_loadedVistaFuncs = false;

// Dynamically-load the Vista-specific symbol (prevents DLL-crash on XP machines)
void LoadVistaFunctions()
{
	static HINSTANCE s_Shell32DLLInst = LoadLibrary(TEXT("Shell32.dll"));
	GetKnownFolderPath = (tSHGetKnownFolderPath)GetProcAddress(s_Shell32DLLInst, "SHGetKnownFolderPath");
	assert(GetKnownFolderPath); // If we don't find this function, something is fundamentally wrong
	s_loadedVistaFuncs = true;
}

// Get the final saved-games path
std::string GetSavedGamesPath()
{
	std::string path;
	if(SupportsVistaFunctionality())
	{
		if(!s_loadedVistaFuncs)
			LoadVistaFunctions();
		char rootPath[MAX_PATH];
		PWSTR uniRootPath;
		HRESULT ret = GetKnownFolderPath(FOLDERID_SavedGames, 0, 0, &uniRootPath);
		wcstombs(rootPath, uniRootPath, MAX_PATH);
		CoTaskMemFree(uniRootPath);
		if(ret == S_OK)
			path = &rootPath[0];
	}
	else
	{
		char tempPath[MAX_PATH];
		// If you compile with the unicode character set, you have to do some conversion here.
		if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, tempPath) == S_OK) // Others recommended CSIDL_APPDATA, but I think CSIDL_LOCAL_APPDATA is a better fit. 
		{ 
			path = tempPath;
		}
	}
	return path;
}

int main(int argv, char** argc)
{
	std::string path = GetSavedGamesPath();
	assert(path != ""); // NOTE: Once you put in your own XP logic, this should never happen
	path = path + "/Retro City Rampage";
	printf("%s\n", path.c_str());
	return 0;
}


Last edited 2012