SDL: Add SDL_GetPath() for default OS folders (#7665)

From c1dab7745ad029d04295d11fc6119cd3dc6e3b8d Mon Sep 17 00:00:00 2001
From: Semphriss <[EMAIL REDACTED]>
Date: Thu, 4 May 2023 14:38:11 -0400
Subject: [PATCH] Add SDL_GetPath() for default OS folders (#7665)

---
 include/SDL3/SDL_filesystem.h              |  77 ++++++
 src/dynapi/SDL_dynapi.sym                  |   1 +
 src/dynapi/SDL_dynapi_overrides.h          |   1 +
 src/dynapi/SDL_dynapi_procs.h              |   1 +
 src/filesystem/cocoa/SDL_sysfilesystem.m   | 114 +++++++++
 src/filesystem/dummy/SDL_sysfilesystem.c   |   7 +
 src/filesystem/unix/SDL_sysfilesystem.c    | 284 +++++++++++++++++++++
 src/filesystem/windows/SDL_sysfilesystem.c | 187 ++++++++++++++
 8 files changed, 672 insertions(+)

diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h
index b45f41380f2b..8811a9ae956e 100644
--- a/include/SDL3/SDL_filesystem.h
+++ b/include/SDL3/SDL_filesystem.h
@@ -138,6 +138,83 @@ extern DECLSPEC char *SDLCALL SDL_GetBasePath(void);
  */
 extern DECLSPEC char *SDLCALL SDL_GetPrefPath(const char *org, const char *app);
 
+/**
+ * The type of the OS-provided default folder for a specific purpose.
+ *
+ * Note that the Trash folder isn't included here, because trashing files usually
+ * involves extra OS-specific functionality to remember the file's original
+ * location.
+ *
+ * \sa SDL_GetPath
+ */
+typedef enum
+{
+  /** The folder which contains all of the current user's data, preferences,
+      and documents. It usually contains most of the other folders. If a
+      requested folder does not exist, the home folder can be considered a safe
+      fallback to store a user's documents. Supported on Windows, macOS and
+      Unix with XDG. */
+  SDL_FOLDER_HOME,
+  /** The folder of files that are displayed on the desktop. Note that the
+      existence of a desktop folder does not guarantee that the system does
+      show icons on its desktop; certain GNU/Linux distros with a graphical
+      environment may not have desktop icons. Supported on Windows, macOS and
+      Unix with XDG. */
+  SDL_FOLDER_DESKTOP,
+  /** General document files, possibly application-specific. This is a good
+      place to save a user's projects. Supported on Windows, macOS and Unix
+      with XDG. */
+  SDL_FOLDER_DOCUMENTS,
+  /** Generic landing folder for files downloaded from the internet. Supported
+      on Windows Vista and later, macOS and Unix with XDG. */
+  SDL_FOLDER_DOWNLOADS,
+  /** Music files that can be played using a standard music player (mp3,
+      ogg...). Supported on Windows, macOS and Unix with XDG. */
+  SDL_FOLDER_MUSIC,
+  /** Image files that can be displayed using a standard viewer (png,
+      jpg...). Supported on Windows, macOS and Unix with XDG. */
+  SDL_FOLDER_PICTURES,
+  /** Files that are meant to be shared with other users on the same
+      computer. Supported on macOS and Unix with XDG. */
+  SDL_FOLDER_PUBLICSHARE,
+  /** Save files for games. Supported on Windows Vista and later. */
+  SDL_FOLDER_SAVEDGAMES,
+  /** Application screenshots. Supported on Windows Vista and later. */
+  SDL_FOLDER_SCREENSHOTS,
+  /** Template files to be used when the user requests the desktop environment
+      to create a new file in a certain folder, such as "New Text File.txt".
+      Any file in the Templates folder can be used as a starting point for a
+      new file. Supported on Windows, macOS and Unix with XDG. */
+  SDL_FOLDER_TEMPLATES,
+  /** Video files that can be played using a standard video player (mp4,
+      webm...). On macOS, this is the "Movies" folder. Supported on Windows,
+      macOS and Unix with XDG. */
+  SDL_FOLDER_VIDEOS,
+} SDL_Folder;
+
+/**
+ * Finds the most suitable OS-provided folder for @p folder, and returns its
+ * path in OS-specific notation.
+ *
+ * Many OSes provide certain standard folders for certain purposes, such as
+ * storing pictures, music or videos for a certain user. This function gives
+ * the path for many of those special locations.
+ *
+ * Note that the function is expensive, and should be called once at the
+ * beginning of the execution and kept for as long as needed.
+ *
+ * The returned value is owned by the caller and should be freed with
+ * SDL_free().
+ *
+ * If NULL is returned, the error may be obtained with SDL_GetError().
+ *
+ * \returns Either a null-terminated C string containing the full path to the
+ *          folder, or NULL if an error happened.
+ *
+ * \sa SDL_Folder
+ */
+extern DECLSPEC char *SDLCALL SDL_GetPath(SDL_Folder folder);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 3870424b209d..3927b26234ac 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -851,6 +851,7 @@ SDL3_0.0.0 {
     SDL_TryLockRWLockForWriting;
     SDL_UnlockRWLock;
     SDL_DestroyRWLock;
+    SDL_GetPath;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 5c78a8c00e26..d96acd6150a2 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -877,3 +877,4 @@
 #define SDL_TryLockRWLockForWriting SDL_TryLockRWLockForWriting_REAL
 #define SDL_UnlockRWLock SDL_UnlockRWLock_REAL
 #define SDL_DestroyRWLock SDL_DestroyRWLock_REAL
+#define SDL_GetPath SDL_GetPath_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index e59b2d9de32d..9ebfecd4e541 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -922,3 +922,4 @@ SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForReading,(SDL_RWLock *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_RWLock *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_RWLock *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_DestroyRWLock,(SDL_RWLock *a),(a),)
+SDL_DYNAPI_PROC(char*,SDL_GetPath,(SDL_Folder a),(a),return)
diff --git a/src/filesystem/cocoa/SDL_sysfilesystem.m b/src/filesystem/cocoa/SDL_sysfilesystem.m
index ddf86ddcfd3e..c9880efe4596 100644
--- a/src/filesystem/cocoa/SDL_sysfilesystem.m
+++ b/src/filesystem/cocoa/SDL_sysfilesystem.m
@@ -132,4 +132,118 @@
     }
 }
 
+char *
+SDL_GetPath(SDL_Folder folder)
+{
+    @autoreleasepool {
+#if TARGET_OS_TV
+        SDL_SetError("tvOS does not have persistent storage");
+        return NULL;
+#else
+        char *retval = NULL;
+        const char* base;
+        NSArray *array;
+        NSSearchPathDirectory dir;
+        NSString *str;
+        char *ptr;
+
+        switch (folder)
+        {
+            case SDL_FOLDER_HOME:
+                base = SDL_getenv("HOME");
+
+                if (!base)
+                {
+                    SDL_SetError("No $HOME environment variable available");
+                }
+
+                retval = SDL_strdup(base);
+
+                if (!retval)
+                    SDL_OutOfMemory();
+
+                return retval;
+
+            case SDL_FOLDER_DESKTOP:
+                dir = NSDesktopDirectory;
+                break;
+
+            case SDL_FOLDER_DOCUMENTS:
+                dir = NSDocumentDirectory;
+                break;
+
+            case SDL_FOLDER_DOWNLOADS:
+                dir = NSDownloadsDirectory;
+                break;
+
+            case SDL_FOLDER_MUSIC:
+                dir = NSMusicDirectory;
+                break;
+
+            case SDL_FOLDER_PICTURES:
+                dir = NSPicturesDirectory;
+                break;
+
+            case SDL_FOLDER_PUBLICSHARE:
+                dir = NSSharedPublicDirectory;
+                break;
+
+            case SDL_FOLDER_SAVEDGAMES:
+                SDL_SetError("Saved games folder not supported on Cocoa");
+                return NULL;
+
+            case SDL_FOLDER_SCREENSHOTS:
+                SDL_SetError("Screenshots folder not supported on Cocoa");
+                return NULL;
+
+            case SDL_FOLDER_TEMPLATES:
+                SDL_SetError("Templates folder not supported on Cocoa");
+                return NULL;
+
+            case SDL_FOLDER_VIDEOS:
+                dir = NSMoviesDirectory;
+                break;
+
+            default:
+                SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
+                return NULL;
+        };
+
+        array = NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES);
+
+        if ([array count] <= 0)
+        {
+            SDL_SetError("Directory not found");
+            return NULL;
+        }
+
+        str = [array objectAtIndex:0];
+        base = [str fileSystemRepresentation];
+        if (!base)
+        {
+            SDL_SetError("Couldn't get folder path");
+            return NULL;
+        }
+
+        retval = SDL_strdup(base);
+        if (retval == NULL)
+        {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+
+        for (ptr = retval + 1; *ptr; ptr++) {
+            if (*ptr == '/') {
+                *ptr = '\0';
+                mkdir(retval, 0700);
+                *ptr = '/';
+            }
+        }
+        mkdir(retval, 0700);
+
+        return retval;
+#endif /* TARGET_OS_TV */
+    }
+}
+
 #endif /* SDL_FILESYSTEM_COCOA */
diff --git a/src/filesystem/dummy/SDL_sysfilesystem.c b/src/filesystem/dummy/SDL_sysfilesystem.c
index 644a3bc8a6b6..d341c740f5db 100644
--- a/src/filesystem/dummy/SDL_sysfilesystem.c
+++ b/src/filesystem/dummy/SDL_sysfilesystem.c
@@ -39,4 +39,11 @@ SDL_GetPrefPath(const char *org, const char *app)
     return NULL;
 }
 
+char *
+SDL_GetPath(SDL_Folder folder)
+{
+    SDL_Unsupported();
+    return NULL;
+}
+
 #endif /* SDL_FILESYSTEM_DUMMY || SDL_FILESYSTEM_DISABLED */
diff --git a/src/filesystem/unix/SDL_sysfilesystem.c b/src/filesystem/unix/SDL_sysfilesystem.c
index cd4af65a7fbb..e3ca8e198739 100644
--- a/src/filesystem/unix/SDL_sysfilesystem.c
+++ b/src/filesystem/unix/SDL_sysfilesystem.c
@@ -27,6 +27,7 @@
 
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
@@ -332,4 +333,287 @@ SDL_GetPrefPath(const char *org, const char *app)
     return retval;
 }
 
+/*
+  The two functions below (prefixed with `xdg_`) have been copied from:
+  https://gitlab.freedesktop.org/xdg/xdg-user-dirs/-/blob/master/xdg-user-dir-lookup.c
+  and have been adapted to work with SDL. They are licensed under the following
+  terms:
+
+  Copyright (c) 2007 Red Hat, Inc.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions: 
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software. 
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+*/
+static char *
+xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback)
+{
+  FILE *file;
+  char *home_dir, *config_home, *config_file;
+  char buffer[512];
+  char *user_dir;
+  char *p, *d;
+  int len;
+  int relative;
+  size_t l;
+  
+  home_dir = SDL_getenv ("HOME");
+
+  if (home_dir == NULL)
+    goto error;
+
+  config_home = SDL_getenv ("XDG_CONFIG_HOME");
+  if (config_home == NULL || config_home[0] == 0)
+    {
+      l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1;
+      config_file = (char*) SDL_malloc (l);
+      if (config_file == NULL)
+        goto error;
+
+      SDL_strlcpy (config_file, home_dir, l);
+      SDL_strlcat (config_file, "/.config/user-dirs.dirs", l);
+    }
+  else
+    {
+      l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1;
+      config_file = (char*) SDL_malloc (l);
+      if (config_file == NULL)
+        goto error;
+
+      SDL_strlcpy (config_file, config_home, l);
+      SDL_strlcat (config_file, "/user-dirs.dirs", l);
+    }
+
+  file = fopen (config_file, "r");
+  SDL_free (config_file);
+  if (file == NULL)
+    goto error;
+
+  user_dir = NULL;
+  while (fgets (buffer, sizeof (buffer), file))
+    {
+      /* Remove newline at end */
+      len = SDL_strlen (buffer);
+      if (len > 0 && buffer[len-1] == '\n')
+        buffer[len-1] = 0;
+      
+      p = buffer;
+      while (*p == ' ' || *p == '\t')
+        p++;
+      
+      if (SDL_strncmp (p, "XDG_", 4) != 0)
+        continue;
+      p += 4;
+      if (SDL_strncmp (p, type, SDL_strlen (type)) != 0)
+        continue;
+      p += strlen (type);
+      if (SDL_strncmp (p, "_DIR", 4) != 0)
+        continue;
+      p += 4;
+
+      while (*p == ' ' || *p == '\t')
+        p++;
+
+      if (*p != '=')
+        continue;
+      p++;
+      
+      while (*p == ' ' || *p == '\t')
+        p++;
+
+      if (*p != '"')
+        continue;
+      p++;
+      
+      relative = 0;
+      if (SDL_strncmp (p, "$HOME/", 6) == 0)
+        {
+          p += 6;
+          relative = 1;
+        }
+      else if (*p != '/')
+        continue;
+
+      SDL_free (user_dir);
+      if (relative)
+        {
+          l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1;
+          user_dir = (char*) SDL_malloc (l);
+          if (user_dir == NULL)
+            goto error2;
+
+          SDL_strlcpy (user_dir, home_dir, l);
+          SDL_strlcat (user_dir, "/", l);
+        }
+      else
+        {
+          user_dir = (char*) SDL_malloc (SDL_strlen (p) + 1);
+          if (user_dir == NULL)
+            goto error2;
+
+          *user_dir = 0;
+        }
+      
+      d = user_dir + SDL_strlen (user_dir);
+      while (*p && *p != '"')
+        {
+          if ((*p == '\\') && (*(p+1) != 0))
+            p++;
+          *d++ = *p++;
+        }
+      *d = 0;
+    }
+error2:
+  fclose (file);
+
+  if (user_dir)
+    return user_dir;
+
+ error:
+  if (fallback)
+    return SDL_strdup (fallback);
+  return NULL;
+}
+
+static char *
+xdg_user_dir_lookup (const char *type)
+{
+    char *dir, *home_dir, *user_dir;
+
+    dir = xdg_user_dir_lookup_with_fallback(type, NULL);
+    if (dir != NULL)
+        return dir;
+
+    home_dir = SDL_getenv("HOME");
+
+    if (home_dir == NULL)
+        return NULL;
+
+    /* Special case desktop for historical compatibility */
+    if (SDL_strcmp(type, "DESKTOP") == 0)
+    {
+        user_dir = (char*) SDL_malloc(SDL_strlen(home_dir) +
+                                      SDL_strlen("/Desktop") + 1);
+        if (user_dir == NULL)
+            return NULL;
+
+        strcpy(user_dir, home_dir);
+        strcat(user_dir, "/Desktop");
+        return user_dir;
+    }
+
+    return NULL;
+}
+
+char *
+SDL_GetPath(SDL_Folder folder)
+{
+    const char *param = NULL;
+    char *retval;
+
+    /* According to `man xdg-user-dir`, the possible values are:
+        DESKTOP
+        DOWNLOAD
+        TEMPLATES
+        PUBLICSHARE
+        DOCUMENTS
+        MUSIC
+        PICTURES
+        VIDEOS
+    */
+    switch(folder)
+    {
+        case SDL_FOLDER_HOME:
+            param = SDL_getenv("HOME");
+
+            if (!param)
+            {
+                SDL_SetError("No $HOME environment variable available");
+            }
+
+            retval = SDL_strdup(param);
+
+            if (!retval)
+                SDL_OutOfMemory();
+
+            return retval;
+
+        case SDL_FOLDER_DESKTOP:
+            param = "DESKTOP";
+            break;
+
+        case SDL_FOLDER_DOCUMENTS:
+            param = "DOCUMENTS";
+            break;
+
+        case SDL_FOLDER_DOWNLOADS:
+            param = "DOWNLOAD";
+            break;
+
+        case SDL_FOLDER_MUSIC:
+            param = "MUSIC";
+            break;
+
+        case SDL_FOLDER_PICTURES:
+            param = "PICTURES";
+            break;
+
+        case SDL_FOLDER_PUBLICSHARE:
+            param = "PUBLICSHARE";
+            break;
+
+        case SDL_FOLDER_SAVEDGAMES:
+            SDL_SetError("Saved Games folder unavailable on XDG");
+            return NULL;
+
+        case SDL_FOLDER_SCREENSHOTS:
+            SDL_SetError("Screenshots folder unavailable on XDG");
+            return NULL;
+
+        case SDL_FOLDER_TEMPLATES:
+            param = "TEMPLATES";
+            break;
+
+        case SDL_FOLDER_VIDEOS:
+            param = "VIDEOS";
+            break;
+
+        default:
+            SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
+            return NULL;
+    }
+
+    /* param *should* to be set to something at this point, but just in case */
+    if (!param)
+    {
+        SDL_SetError("No corresponding XDG user directory");
+        return NULL;
+    }
+
+    retval = xdg_user_dir_lookup(param);
+
+    if (!retval)
+    {
+        SDL_SetError("XDG directory not available");
+    }
+
+    return retval;
+}
+
 #endif /* SDL_FILESYSTEM_UNIX */
diff --git a/src/filesystem/windows/SDL_sysfilesystem.c b/src/filesystem/windows/SDL_sysfilesystem.c
index 753ca091edaa..46acf098bb59 100644
--- a/src/filesystem/windows/SDL_sysfilesystem.c
+++ b/src/filesystem/windows/SDL_sysfilesystem.c
@@ -26,7 +26,14 @@
 /* System dependent filesystem routines                                */
 
 #include "../../core/windows/SDL_windows.h"
+#include <errhandlingapi.h>
+#include <fileapi.h>
 #include <shlobj.h>
+#include <libloaderapi.h>
+/* Lowercase is necessary for Wine */
+#include <knownfolders.h>
+#include <initguid.h>
+#include <windows.h>
 
 char *
 SDL_GetBasePath(void)
@@ -166,6 +173,179 @@ SDL_GetPrefPath(const char *org, const char *app)
     return retval;
 }
 
+char *
+SDL_GetPath(SDL_Folder folder)
+{
+    typedef HRESULT (*SHGKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR *);
+    char *retval;
+    HMODULE lib = LoadLibrary(L"Shell32.dll");
+    SHGKFP SHGetKnownFolderPath_ = (SHGKFP) GetProcAddress(lib,
+                                                       "SHGetKnownFolderPath");
+
+    if (!SHGetKnownFolderPath_)
+    {
+        int type;
+        HRESULT result;
+    	wchar_t path[MAX_PATH];
+
+        switch (folder)
+        {
+            case SDL_FOLDER_HOME:
+                type = CSIDL_PROFILE;
+                break;
+
+            case SDL_FOLDER_DESKTOP:
+                type = CSIDL_DESKTOP;
+                break;
+
+            case SDL_FOLDER_DOCUMENTS:
+                type = CSIDL_MYDOCUMENTS;
+                break;
+
+            case SDL_FOLDER_DOWNLOADS:
+                SDL_SetError("Downloads folder unavailable before Vista");
+                return NULL;
+
+            case SDL_FOLDER_MUSIC:
+                type = CSIDL_MYMUSIC;
+                break;
+
+            case SDL_FOLDER_PICTURES:
+                type = CSIDL_MYPICTURES;
+                break;
+
+            case SDL_FOLDER_PUBLICSHARE:
+                SDL_SetError("Public share unavailable on Windows");
+                return NULL;
+
+            case SDL_FOLDER_SAVEDGAMES:
+                SDL_SetError("Saved games unavailable before Vista");
+                return NULL;
+
+            case SDL_FOLDER_SCREENSHOTS:
+                SDL_SetError("Screenshots folder unavailable before Vista");
+                return NULL;
+
+            case SDL_FOLDER_TEMPLATES:
+                type = CSIDL_TEMPLATES;
+                break;
+
+            case SDL_FOLDER_VIDEOS:
+                type = CSIDL_MYVIDEO;
+                break;
+
+            default:
+                SDL_SetError("Unsupported SDL_Folder on Windows before Vista: %d",
+                              (int) folder);
+                return NULL;
+        };
+
+        /* Create the OS-specific folder if it doesn't already exist */
+        type |= CSIDL_FLAG_CREATE;
+
+#if 0
+        /* Apparently the oldest, but not supported in modern Windows */
+        HRESULT result = SHGetSpecialFolderPath(NULL, path, type, TRUE);
+#endif
+
+        /* Windows 2000/XP and later, deprecated as of Windows 10 (still
+           available), available in Wine (tested 6.0.3) */
+        result = SHGetFolderPathW(NULL, type, NULL, SHGFP_TYPE_CURRENT, path);
+
+        /* use `!= TRUE` for SHGetSpecialFolderPath */
+        if (result != S_OK)
+        {
+            SDL_SetError("Couldn't get folder, windows-specific error: %ld",
+                         result);
+            return NULL;
+        }
+
+        retval = (char *) SDL_malloc((SDL_wcslen(path) + 1) * 2);
+        if (retval == NULL)
+        {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+        retval = WIN_StringToUTF8W(path);
+        return retval;
+    }
+    else
+    {
+        KNOWNFOLDERID type;
+        HRESULT result;
+    	wchar_t *path;
+
+        switch (folder)
+        {
+            case SDL_FOLDER_HOME:
+                type = FOLDERID_Profile;
+                break;
+
+            case SDL_FOLDER_DESKTOP:
+                type = FOLDERID_Desktop;
+                break;
+
+            case SDL_FOLDER_DOCUMENTS:
+                type = FOLDERID_Documents;
+                break;
+
+            case SDL_FOLDER_DOWNLOADS:
+                type = FOLDERID_Downloads;
+                break;
+
+            case SDL_FOLDER_MUSIC:
+                type = FOLDERID_Music;
+                break;
+
+            case SDL_FOLDER_PICTURES:
+                type = FOLDERID_Pictures;
+                break;
+
+            case SDL_FOLDER_PUBLICSHARE:
+                SDL_SetError("Public share unavailable on Windows");
+                return NULL;
+
+            case SDL_FOLDER_SAVEDGAMES:
+                type = FOLDERID_SavedGames;
+                break;
+
+            case SDL_FOLDER_SCREENSHOTS:
+                type = FOLDERID_Screenshots;
+                break;
+
+            case SDL_FOLDER_TEMPLATES:
+                type = FOLDERID_Templates;
+                break;
+
+            case SDL_FOLDER_VIDEOS:
+                type = FOLDERID_Videos;
+                break;
+
+            default:
+                SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
+                return NULL;
+        };
+
+        result = SHGetKnownFolderPath_(&type, KF_FLAG_CREATE, NULL, &path);
+
+        if (result != S_OK)
+        {
+            SDL_SetError("Couldn't get folder, windows-specific error: %ld",
+                         result);
+            return NULL;
+        }
+
+        retval = (char *) SDL_malloc((SDL_wcslen(path) + 1) * 2);
+        if (retval == NULL)
+        {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+        retval = WIN_StringToUTF8W(path);
+        return retval;
+    }
+}
+
 #endif /* SDL_FILESYSTEM_WINDOWS */
 
 #ifdef SDL_FILESYSTEM_XBOX
@@ -182,4 +362,11 @@ SDL_GetPrefPath(const char *org, const char *app)
     SDL_Unsupported();
     return NULL;
 }
+
+char *
+SDL_GetPath(SDL_Folder folder)
+{
+    SDL_Unsupported();
+    return NULL;
+}
 #endif /* SDL_FILESYSTEM_XBOX */