diff --git a/docs/README-winrt.md b/docs/README-winrt.md index 0ff70ecfa..f573a776c 100644 --- a/docs/README-winrt.md +++ b/docs/README-winrt.md @@ -119,27 +119,64 @@ Here is a rough list of what works, and what doens't: Caveats ------- -#### SDL_GetPrefPath() usage +#### SDL_GetPrefPath() usage when upgrading existing WinRT apps to SDL 2.0.4 -SDL_GetPrefPath() is available for use in WinRT apps, however the following -should be noted: +SDL 2.0.4 fixes two bugs found in SDL_GetPrefPath() which can affect +an app's save data. These bugs only apply to WinRT apps (and not +Windows Desktop / Win32 apps, or to apps on any other SDL platform). +In particular, for older versions of SDL (anything before 2.0.4): -1. It will return different path types, by default, depending on the WinRT - platform. Windows Phone apps will default to using the app's "local" path, - whereas Windows Store (i.e. non-Phone) apps will default to using the app's - "roaming" path. This behavior can be changed by calling SDL_SetHint() with - the key, SDL_HINT_WINRT_PREF_PATH_ROOT, and a value of either "local" or - "roaming". +1. SDL_GetPrefPath() would return an invalid path, one in which attempts + to write files to would fail, in many cases. Some of the path elements + returned by SDL_GetPrefPath() would not get created (as done on other + SDL platforms). Files could be written to this path, however apps would + need to explicitly create the missing directories first. + +2. SDL_GetPrefPath() would return a path inside a WinRT 'Roaming' folder, + the contents of which could get automatically synchronized across multiple + devices, by Windows. This process could occur while an app was running. + Apps which were not explicitly built to handle this scenario could + have their SDL_GetPrefPath-backed save data swapped out by Windows at + unexpected times, which raised potential for data-loss (if apps weren't + designed to support live file-synchronization.) -2. Windows Phone 8.0 does not provide apps access to a "roaming" folder. - Attempts to make SDL_GetPrefPath() return a roaming folder on Windows - Phone 8.0 will be ignored (and a path inside the "local" folder will be - used instead). -Further details on this can be found in the documentation for -SDL_HINT_WINRT_PREF_PATH_ROOT, in SDL_hints.h, as well as the docs for -SDL_WinRT_Path, SDL_WinRTGetFSPathUNICODE, and SDL_WinRTGetFSPathUTF8, -in SDL_system.h. +SDL_GetPrefPath(), starting with SDL 2.0.4, addresses these by: + +1. making sure that SDL_GetPrefPath() returns a directory in which data + can be written to immediately, without first needing to create directories. + +2. basing SDL_GetPrefPath() off of a non-Roaming / 'Local' folder, the + contents of which do not get automatically synchronized across devices, + and which may be safer in terms of data-integrity. + + Apps can, at their discretion, choose to utilize WinRT's Roaming + functionality by calling the following before calling SDL_GetPrefPath(): + + SDL_SetHint(SDL_HINT_WINRT_PREF_PATH_ROOT, "roaming"); + + Alternatively, to restore SDL_GetPrefPath()'s old behavior (found in + SDL 2.0.3, and in many pre-2.0.4 versions of SDL found on hg.libsdl.org), + whereby a Roaming path is returned for Windows Store apps, and a Local + folder is returned for Windows Phone apps, use the following code: + + SDL_SetHint(SDL_HINT_WINRT_PREF_PATH_ROOT, "old"); + + Before using Roaming data in any capacity, it is highly recommended that + one read the following: + + 1. Microsoft's documentation on the Roaming data. Details on this can be + found on MSDN, at: + [Guidelines for roaming app data](http://msdn.microsoft.com/en-us/library/windows/apps/hh465094.aspx). + + 2. the SDL documentation for SDL_HINT_WINRT_PREF_PATH_ROOT, which is + listed inside SDL_hints.h. + + Please note that Roaming support is not available on Windows Phone 8.0, + due to limitations in the OS itself. Attempts to use it will fail, with + SDL_GetPrefPath() returning NULL (if SDL_HINT_WINRT_PREF_PATH_ROOT is + set to "roaming" on that platform). Windows Phone 8.1 does not have this + limitation, and does support Roaming data. diff --git a/include/SDL_hints.h b/include/SDL_hints.h index dd44ba39d..7c9e4dfd8 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -495,16 +495,25 @@ extern "C" { * \brief A variable that dictates what SDL_GetPrefPath() returns in WinRT apps. * * The variable can be set to the following values: - * "local" - Use the app's 'local' folder to store data; default for - * Windows Phone apps. - * "roaming" - Use the app's 'roaming' folder to store data; default for - * Windows Store (non-Phone) apps. On Windows Phone 8.0, this - * setting will be ignored (and the 'local' folder will be used - * instead), as the OS does not support roaming folders. + * * "local" - Use the app's 'local' folder to store data. + * * "roaming" - Use the app's 'roaming' folder to store data. + * On Windows Phone 8.0, this setting is not supported due to + * limitations in the OS itself. Attempts to use this (via + * SDL_GetPrefPath()) on Windows Phone 8.0 will fail, with + * SDL_GetPrefPath() returning NULL. (Windows Phone 8.1 does, + * however, support roaming folders.) + * * "old" - Use the app's 'local' folder on Windows Phone, and 'roaming' + * on non-Phone versions of WinRT. This mimics behavior found + * in SDL 2.0.3's implementation of SDL_GetPrefPath() for WinRT + * (and was changed for SDL 2.0.4, further details of which are + * in the "Caveats" section of SDL's + * [WinRT README file](README-winrt.md). * - * Details on 'local' verses 'roaming' folders can be found on MSDN, in the - * documentation for WinRT's Windows.Storage.ApplicationData class, which is - * available at http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.applicationdata + * The default is to use the app's "local" folder. + * + * Details on 'local' verses 'roaming' folders can be found on MSDN, in + * the documentation for WinRT's Windows.Storage.ApplicationData class, + * (available at http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.applicationdata ). * * The application's local and roaming paths may, alternatively, be retrieved * via the SDL_WinRTGetFSPathUTF8() and SDL_WinRTGetFSPathUNICODE() functions, diff --git a/src/filesystem/winrt/SDL_sysfilesystem.cpp b/src/filesystem/winrt/SDL_sysfilesystem.cpp index 7bb148694..8345b9a78 100644 --- a/src/filesystem/winrt/SDL_sysfilesystem.cpp +++ b/src/filesystem/winrt/SDL_sysfilesystem.cpp @@ -144,49 +144,121 @@ SDL_GetPrefPath(const char *org, const char *app) * without violating Microsoft's app-store requirements. */ -#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP - /* A 'Roaming' folder is not available in Windows Phone 8.0, however a - * 'Local' folder is. Use the 'Local' folder in order to preserve - * compatibility with Windows Phone 8.0, and with app-installs that have - * been updated from 8.0-based, to 8.1-based apps. + /* Default to using a Local/non-Roaming path. WinRT will often attempt + * to synchronize files in Roaming paths, and will do so while an app is + * running. Using a Local path prevents the possibility that an app's + * save-data files will get changed from underneath it, without it + * being ready. + * + * This behavior can be changed via use of the + * SDL_HINT_WINRT_PREF_PATH_ROOT hint. */ SDL_WinRT_Path pathType = SDL_WINRT_PATH_LOCAL_FOLDER; -#else - /* A 'Roaming' folder is available on Windows 8 and 8.1. Use that. - */ - SDL_WinRT_Path pathType = SDL_WINRT_PATH_ROAMING_FOLDER; -#endif const char * hint = SDL_GetHint(SDL_HINT_WINRT_PREF_PATH_ROOT); if (hint) { if (SDL_strcasecmp(hint, "local") == 0) { pathType = SDL_WINRT_PATH_LOCAL_FOLDER; - } + } else if (SDL_strcasecmp(hint, "roaming") == 0) { #if (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION > NTDDI_WIN8) - else if (SDL_strcasecmp(hint, "roaming") == 0) { pathType = SDL_WINRT_PATH_ROAMING_FOLDER; - } +#else + /* Don't apply a 'Roaming' path on Windows Phone 8.0. Roaming + * data is not supported by that version of the operating system. + */ + SDL_SetError("A Roaming path was specified via SDL_HINT_WINRT_PREF_PATH_ROOT, but Roaming is not supported on Windows Phone 8.0"); + return NULL; #endif + } else if (SDL_strcasecmp(hint, "old") == 0) { + /* Older versions of SDL/WinRT, including 2.0.3, would return a + * pref-path that used a Roaming folder on non-Phone versions of + * Windows, such as Windows 8.0 and Windows 8.1. This has since + * been reverted to using a Local folder, in order to prevent + * problems arising from WinRT automatically synchronizing files + * during an app's lifetime. In case this functionality is + * desired, setting SDL_HINT_WINRT_PREF_PATH_ROOT to "old" will + * trigger the older behavior. + */ +#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP + pathType = SDL_WINRT_PATH_LOCAL_FOLDER; +#else + pathType = SDL_WINRT_PATH_ROAMING_FOLDER; +#endif + } } - const char * srcPath = SDL_WinRTGetFSPathUTF8(pathType); - size_t destPathLen; - char * destPath = NULL; + const WCHAR * srcPath = NULL; + WCHAR path[MAX_PATH]; + char *retval = NULL; + WCHAR* worg = NULL; + WCHAR* wapp = NULL; + size_t new_wpath_len = 0; + BOOL api_result = FALSE; - if (!srcPath) { - SDL_SetError("Couldn't locate our basepath: %s", SDL_GetError()); + srcPath = SDL_WinRTGetFSPathUNICODE(pathType); + if ( ! srcPath) { + SDL_SetError("Unable to find a source path"); return NULL; } - destPathLen = SDL_strlen(srcPath) + SDL_strlen(org) + SDL_strlen(app) + 4; - destPath = (char *) SDL_malloc(destPathLen); - if (!destPath) { + if (SDL_wcslen(srcPath) >= MAX_PATH) { + SDL_SetError("Path too long."); + return NULL; + } + SDL_wcslcpy(path, srcPath, SDL_arraysize(path)); + + worg = WIN_UTF8ToString(org); + if (worg == NULL) { SDL_OutOfMemory(); return NULL; } - SDL_snprintf(destPath, destPathLen, "%s\\%s\\%s\\", srcPath, org, app); - return destPath; + wapp = WIN_UTF8ToString(app); + if (wapp == NULL) { + SDL_free(worg); + SDL_OutOfMemory(); + return NULL; + } + + new_wpath_len = SDL_wcslen(worg) + SDL_wcslen(wapp) + SDL_wcslen(path) + 3; + + if ((new_wpath_len + 1) > MAX_PATH) { + SDL_free(worg); + SDL_free(wapp); + SDL_SetError("Path too long."); + return NULL; + } + + SDL_wcslcat(path, L"\\", new_wpath_len + 1); + SDL_wcslcat(path, worg, new_wpath_len + 1); + SDL_free(worg); + + api_result = CreateDirectoryW(path, NULL); + if (api_result == FALSE) { + if (GetLastError() != ERROR_ALREADY_EXISTS) { + SDL_free(wapp); + WIN_SetError("Couldn't create a prefpath."); + return NULL; + } + } + + SDL_wcslcat(path, L"\\", new_wpath_len + 1); + SDL_wcslcat(path, wapp, new_wpath_len + 1); + SDL_free(wapp); + + api_result = CreateDirectoryW(path, NULL); + if (api_result == FALSE) { + if (GetLastError() != ERROR_ALREADY_EXISTS) { + WIN_SetError("Couldn't create a prefpath."); + return NULL; + } + } + + SDL_wcslcat(path, L"\\", new_wpath_len + 1); + + retval = WIN_StringToUTF8(path); + + return retval; } #endif /* __WINRT__ */