src/filesystem/winrt/SDL_sysfilesystem.cpp
author David Ludwig
Sat, 29 Nov 2014 10:09:30 -0500
changeset 9247 eddb899239fe
parent 9242 f4d353bd5d16
child 9255 c2ef0d8d6da0
permissions -rw-r--r--
WinRT: bug and data-integrity fixes for SDL_GetPrefPath()

This change does a few things, all with regards to the WinRT implementation of
SDL_GetPrefPath():

1. it fixes a bug whereby SDL_GetPrefPath() did not create the directory it
returned. On other SDL platforms, SDL_GetPrefPath() will create separate
directories for its 'org' and 'app' folders. Without this, attempts to create
files in the pref-path would fail, unless those directories were first created
by the app, or by some other library the app used. This change makes sure
that these directories get created, before SDL_GetPrefPath() returns to its
caller(s).


2. it defaults to having SDL_GetPrefPath() return a WinRT 'Local' folder
on all platforms. Previously, for Windows Store apps, it would have used a
different, 'Roaming' folder. Files in Roaming folders can be automatically,
and synchronized across multiple devices by Windows. This synchronization can
happen while the app runs, with new files being copied into a running app's
pref-path. Unless an app is specifically designed to handle this scenario,
there is a chance that save-data could be overwritten in unwanted or
unexpected ways.

The default is now to use a Local folder, which does not get synchronized, and
which is arguably a bit safer to use. Apps that wish to use Roaming folders
can do so by setting SDL_HINT_WINRT_PREF_PATH_ROOT to "roaming", however it
is recommended that one first read Microsoft's documentation for Roaming
files, a link to which is provided in README-winrt.md.

To preserve older pref-path selection behavior (found in SDL 2.0.3, as well as
many pre-2.0.4 versions of SDL from hg.libsdl.org), which uses a Roaming path
in Windows Store apps, and a Local path in Windows Phone, set
SDL_HINT_WINRT_PREF_PATH_ROOT to "old".

Please note that Roaming paths are not supported on Windows Phone 8.0, due to
limitations in the OS itself. Attempts to use this will fail.
(Windows Phone 8.1 does not have this limitation, however.)


3. It makes SDL_GetPrefPath(), when on Windows Phone 8.0, and when
SDL_HINT_WINRT_PREF_PATH_ROOT is set to "roaming", return NULL, rather than
silently defaulting to a Local path (then switching to a Roaming path if and
when the user upgraded to Windows Phone 8.1).
slouken@8616
     1
/*
slouken@8616
     2
  Simple DirectMedia Layer
slouken@8616
     3
  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
slouken@8616
     4
slouken@8616
     5
  This software is provided 'as-is', without any express or implied
slouken@8616
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@8616
     7
  arising from the use of this software.
slouken@8616
     8
slouken@8616
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@8616
    10
  including commercial applications, and to alter it and redistribute it
slouken@8616
    11
  freely, subject to the following restrictions:
slouken@8616
    12
slouken@8616
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@8616
    14
     claim that you wrote the original software. If you use this software
slouken@8616
    15
     in a product, an acknowledgment in the product documentation would be
slouken@8616
    16
     appreciated but is not required.
slouken@8616
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@8616
    18
     misrepresented as being the original software.
slouken@8616
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@8616
    20
*/
slouken@8616
    21
#include "../../SDL_internal.h"
slouken@8616
    22
dludwig@9241
    23
/* TODO, WinRT: remove the need to compile this with C++/CX (/ZW) extensions, and if possible, without C++ at all
slouken@8582
    24
*/
slouken@8582
    25
slouken@8582
    26
#ifdef __WINRT__
slouken@8582
    27
slouken@8582
    28
extern "C" {
slouken@8582
    29
#include "SDL_filesystem.h"
slouken@8582
    30
#include "SDL_error.h"
dludwig@9242
    31
#include "SDL_hints.h"
slouken@8582
    32
#include "SDL_stdinc.h"
slouken@8582
    33
#include "SDL_system.h"
slouken@8582
    34
#include "../../core/windows/SDL_windows.h"
slouken@8582
    35
}
slouken@8582
    36
slouken@8582
    37
#include <string>
slouken@8582
    38
#include <unordered_map>
slouken@8582
    39
slouken@8582
    40
using namespace std;
slouken@8582
    41
using namespace Windows::Storage;
slouken@8582
    42
slouken@8582
    43
extern "C" const wchar_t *
slouken@8582
    44
SDL_WinRTGetFSPathUNICODE(SDL_WinRT_Path pathType)
slouken@8582
    45
{
slouken@8582
    46
    switch (pathType) {
slouken@8582
    47
        case SDL_WINRT_PATH_INSTALLED_LOCATION:
slouken@8582
    48
        {
slouken@8582
    49
            static wstring path;
slouken@8582
    50
            if (path.empty()) {
slouken@8582
    51
                path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path->Data();
slouken@8582
    52
            }
slouken@8582
    53
            return path.c_str();
slouken@8582
    54
        }
slouken@8582
    55
slouken@8582
    56
        case SDL_WINRT_PATH_LOCAL_FOLDER:
slouken@8582
    57
        {
slouken@8582
    58
            static wstring path;
slouken@8582
    59
            if (path.empty()) {
slouken@8582
    60
                path = ApplicationData::Current->LocalFolder->Path->Data();
slouken@8582
    61
            }
slouken@8582
    62
            return path.c_str();
slouken@8582
    63
        }
slouken@8582
    64
dludwig@9228
    65
#if (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION > NTDDI_WIN8)
slouken@8582
    66
        case SDL_WINRT_PATH_ROAMING_FOLDER:
slouken@8582
    67
        {
slouken@8582
    68
            static wstring path;
slouken@8582
    69
            if (path.empty()) {
slouken@8582
    70
                path = ApplicationData::Current->RoamingFolder->Path->Data();
slouken@8582
    71
            }
slouken@8582
    72
            return path.c_str();
slouken@8582
    73
        }
slouken@8582
    74
slouken@8582
    75
        case SDL_WINRT_PATH_TEMP_FOLDER:
slouken@8582
    76
        {
slouken@8582
    77
            static wstring path;
slouken@8582
    78
            if (path.empty()) {
slouken@8582
    79
                path = ApplicationData::Current->TemporaryFolder->Path->Data();
slouken@8582
    80
            }
slouken@8582
    81
            return path.c_str();
slouken@8582
    82
        }
slouken@8582
    83
#endif
slouken@8582
    84
slouken@8582
    85
        default:
slouken@8582
    86
            break;
slouken@8582
    87
    }
slouken@8582
    88
slouken@8582
    89
    SDL_Unsupported();
slouken@8582
    90
    return NULL;
slouken@8582
    91
}
slouken@8582
    92
slouken@8582
    93
extern "C" const char *
slouken@8582
    94
SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType)
slouken@8582
    95
{
slouken@8582
    96
    typedef unordered_map<SDL_WinRT_Path, string> UTF8PathMap;
slouken@8582
    97
    static UTF8PathMap utf8Paths;
slouken@8582
    98
slouken@8582
    99
    UTF8PathMap::iterator searchResult = utf8Paths.find(pathType);
slouken@8582
   100
    if (searchResult != utf8Paths.end()) {
slouken@8582
   101
        return searchResult->second.c_str();
slouken@8582
   102
    }
slouken@8582
   103
slouken@8582
   104
    const wchar_t * ucs2Path = SDL_WinRTGetFSPathUNICODE(pathType);
slouken@8582
   105
    if (!ucs2Path) {
slouken@8582
   106
        return NULL;
slouken@8582
   107
    }
slouken@8582
   108
slouken@8582
   109
    char * utf8Path = WIN_StringToUTF8(ucs2Path);
slouken@8582
   110
    utf8Paths[pathType] = utf8Path;
slouken@8582
   111
    SDL_free(utf8Path);
slouken@8582
   112
    return utf8Paths[pathType].c_str();
slouken@8582
   113
}
slouken@8582
   114
slouken@8582
   115
extern "C" char *
slouken@8582
   116
SDL_GetBasePath(void)
slouken@8582
   117
{
slouken@8582
   118
    const char * srcPath = SDL_WinRTGetFSPathUTF8(SDL_WINRT_PATH_INSTALLED_LOCATION);
slouken@8582
   119
    size_t destPathLen;
slouken@8582
   120
    char * destPath = NULL;
slouken@8582
   121
slouken@8582
   122
    if (!srcPath) {
slouken@8582
   123
        SDL_SetError("Couldn't locate our basepath: %s", SDL_GetError());
slouken@8582
   124
        return NULL;
slouken@8582
   125
    }
slouken@8582
   126
slouken@8582
   127
    destPathLen = SDL_strlen(srcPath) + 2;
slouken@8582
   128
    destPath = (char *) SDL_malloc(destPathLen);
slouken@8582
   129
    if (!destPath) {
slouken@8582
   130
        SDL_OutOfMemory();
slouken@8582
   131
        return NULL;
slouken@8582
   132
    }
slouken@8582
   133
slouken@8582
   134
    SDL_snprintf(destPath, destPathLen, "%s\\", srcPath);
slouken@8582
   135
    return destPath;
slouken@8582
   136
}
slouken@8582
   137
slouken@8582
   138
extern "C" char *
slouken@8582
   139
SDL_GetPrefPath(const char *org, const char *app)
slouken@8582
   140
{
slouken@8582
   141
    /* WinRT note: The 'SHGetFolderPath' API that is used in Windows 7 and
slouken@8582
   142
     * earlier is not available on WinRT or Windows Phone.  WinRT provides
slouken@8582
   143
     * a similar API, but SHGetFolderPath can't be called, at least not
slouken@8582
   144
     * without violating Microsoft's app-store requirements.
slouken@8582
   145
     */
slouken@8582
   146
dludwig@9247
   147
    /* Default to using a Local/non-Roaming path.  WinRT will often attempt
dludwig@9247
   148
     * to synchronize files in Roaming paths, and will do so while an app is
dludwig@9247
   149
     * running.  Using a Local path prevents the possibility that an app's
dludwig@9247
   150
     * save-data files will get changed from underneath it, without it
dludwig@9247
   151
     * being ready.
dludwig@9247
   152
     *
dludwig@9247
   153
     * This behavior can be changed via use of the
dludwig@9247
   154
     * SDL_HINT_WINRT_PREF_PATH_ROOT hint.
dludwig@9228
   155
     */
dludwig@9242
   156
    SDL_WinRT_Path pathType = SDL_WINRT_PATH_LOCAL_FOLDER;
slouken@8582
   157
dludwig@9242
   158
    const char * hint = SDL_GetHint(SDL_HINT_WINRT_PREF_PATH_ROOT);
dludwig@9242
   159
    if (hint) {
dludwig@9242
   160
        if (SDL_strcasecmp(hint, "local") == 0) {
dludwig@9242
   161
            pathType = SDL_WINRT_PATH_LOCAL_FOLDER;
dludwig@9247
   162
        } else if (SDL_strcasecmp(hint, "roaming") == 0) {
dludwig@9242
   163
#if (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (NTDDI_VERSION > NTDDI_WIN8)
dludwig@9242
   164
            pathType = SDL_WINRT_PATH_ROAMING_FOLDER;
dludwig@9247
   165
#else
dludwig@9247
   166
            /* Don't apply a 'Roaming' path on Windows Phone 8.0.  Roaming
dludwig@9247
   167
             * data is not supported by that version of the operating system.
dludwig@9247
   168
             */
dludwig@9247
   169
            SDL_SetError("A Roaming path was specified via SDL_HINT_WINRT_PREF_PATH_ROOT, but Roaming is not supported on Windows Phone 8.0");
dludwig@9247
   170
            return NULL;
dludwig@9247
   171
#endif
dludwig@9247
   172
        } else if (SDL_strcasecmp(hint, "old") == 0) {
dludwig@9247
   173
            /* Older versions of SDL/WinRT, including 2.0.3, would return a
dludwig@9247
   174
             * pref-path that used a Roaming folder on non-Phone versions of
dludwig@9247
   175
             * Windows, such as Windows 8.0 and Windows 8.1.  This has since
dludwig@9247
   176
             * been reverted to using a Local folder, in order to prevent
dludwig@9247
   177
             * problems arising from WinRT automatically synchronizing files
dludwig@9247
   178
             * during an app's lifetime.  In case this functionality is
dludwig@9247
   179
             * desired, setting SDL_HINT_WINRT_PREF_PATH_ROOT to "old" will
dludwig@9247
   180
             * trigger the older behavior.
dludwig@9247
   181
             */
dludwig@9247
   182
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
dludwig@9247
   183
            pathType = SDL_WINRT_PATH_LOCAL_FOLDER;
dludwig@9247
   184
#else
dludwig@9247
   185
            pathType = SDL_WINRT_PATH_ROAMING_FOLDER;
dludwig@9247
   186
#endif
dludwig@9242
   187
        }
dludwig@9242
   188
    }
dludwig@9242
   189
dludwig@9247
   190
    const WCHAR * srcPath = NULL;
dludwig@9247
   191
    WCHAR path[MAX_PATH];
dludwig@9247
   192
    char *retval = NULL;
dludwig@9247
   193
    WCHAR* worg = NULL;
dludwig@9247
   194
    WCHAR* wapp = NULL;
dludwig@9247
   195
    size_t new_wpath_len = 0;
dludwig@9247
   196
    BOOL api_result = FALSE;
slouken@8582
   197
dludwig@9247
   198
    srcPath = SDL_WinRTGetFSPathUNICODE(pathType);
dludwig@9247
   199
    if ( ! srcPath) {
dludwig@9247
   200
        SDL_SetError("Unable to find a source path");
slouken@8582
   201
        return NULL;
slouken@8582
   202
    }
slouken@8582
   203
dludwig@9247
   204
    if (SDL_wcslen(srcPath) >= MAX_PATH) {
dludwig@9247
   205
        SDL_SetError("Path too long.");
dludwig@9247
   206
        return NULL;
dludwig@9247
   207
    }
dludwig@9247
   208
    SDL_wcslcpy(path, srcPath, SDL_arraysize(path));
dludwig@9247
   209
dludwig@9247
   210
    worg = WIN_UTF8ToString(org);
dludwig@9247
   211
    if (worg == NULL) {
slouken@8582
   212
        SDL_OutOfMemory();
slouken@8582
   213
        return NULL;
slouken@8582
   214
    }
slouken@8582
   215
dludwig@9247
   216
    wapp = WIN_UTF8ToString(app);
dludwig@9247
   217
    if (wapp == NULL) {
dludwig@9247
   218
        SDL_free(worg);
dludwig@9247
   219
        SDL_OutOfMemory();
dludwig@9247
   220
        return NULL;
dludwig@9247
   221
    }
dludwig@9247
   222
dludwig@9247
   223
    new_wpath_len = SDL_wcslen(worg) + SDL_wcslen(wapp) + SDL_wcslen(path) + 3;
dludwig@9247
   224
dludwig@9247
   225
    if ((new_wpath_len + 1) > MAX_PATH) {
dludwig@9247
   226
        SDL_free(worg);
dludwig@9247
   227
        SDL_free(wapp);
dludwig@9247
   228
        SDL_SetError("Path too long.");
dludwig@9247
   229
        return NULL;
dludwig@9247
   230
    }
dludwig@9247
   231
dludwig@9247
   232
    SDL_wcslcat(path, L"\\", new_wpath_len + 1);
dludwig@9247
   233
    SDL_wcslcat(path, worg, new_wpath_len + 1);
dludwig@9247
   234
    SDL_free(worg);
dludwig@9247
   235
dludwig@9247
   236
    api_result = CreateDirectoryW(path, NULL);
dludwig@9247
   237
    if (api_result == FALSE) {
dludwig@9247
   238
        if (GetLastError() != ERROR_ALREADY_EXISTS) {
dludwig@9247
   239
            SDL_free(wapp);
dludwig@9247
   240
            WIN_SetError("Couldn't create a prefpath.");
dludwig@9247
   241
            return NULL;
dludwig@9247
   242
        }
dludwig@9247
   243
    }
dludwig@9247
   244
dludwig@9247
   245
    SDL_wcslcat(path, L"\\", new_wpath_len + 1);
dludwig@9247
   246
    SDL_wcslcat(path, wapp, new_wpath_len + 1);
dludwig@9247
   247
    SDL_free(wapp);
dludwig@9247
   248
dludwig@9247
   249
    api_result = CreateDirectoryW(path, NULL);
dludwig@9247
   250
    if (api_result == FALSE) {
dludwig@9247
   251
        if (GetLastError() != ERROR_ALREADY_EXISTS) {
dludwig@9247
   252
            WIN_SetError("Couldn't create a prefpath.");
dludwig@9247
   253
            return NULL;
dludwig@9247
   254
        }
dludwig@9247
   255
    }
dludwig@9247
   256
dludwig@9247
   257
    SDL_wcslcat(path, L"\\", new_wpath_len + 1);
dludwig@9247
   258
dludwig@9247
   259
    retval = WIN_StringToUTF8(path);
dludwig@9247
   260
dludwig@9247
   261
    return retval;
slouken@8582
   262
}
slouken@8582
   263
slouken@8582
   264
#endif /* __WINRT__ */