Sync to Wine-0_9_3:
[reactos.git] / reactos / lib / shell32 / shellpath.c
index 1b63d82..fe6aef1 100644 (file)
@@ -2,6 +2,7 @@
  * Path Functions
  *
  * Copyright 1998, 1999, 2000 Juergen Schmied
+ * Copyright 2004 Juan Lang
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 #include "winuser.h"
 
 #include "shlobj.h"
+#include "shresdef.h"
 #include "shell32_main.h"
 #include "undocshell.h"
+#include "pidl.h"
 #include "wine/unicode.h"
 #include "shlwapi.h"
 
@@ -236,29 +239,41 @@ void WINAPI PathRemoveExtensionAW(LPVOID lpszPath)
 /*************************************************************************
  * PathGetShortPathA [internal]
  */
-LPSTR WINAPI PathGetShortPathA(LPSTR lpszPath)
+static void PathGetShortPathA(LPSTR pszPath)
 {
-       FIXME("%s stub\n", lpszPath);
-       return NULL;
+       CHAR path[MAX_PATH];
+
+       TRACE("%s\n", pszPath);
+
+       if (GetShortPathNameA(pszPath, path, MAX_PATH))
+       {
+         lstrcpyA(pszPath, path);
+       }
 }
 
 /*************************************************************************
  * PathGetShortPathW [internal]
  */
-LPWSTR WINAPI PathGetShortPathW(LPWSTR lpszPath)
+static void PathGetShortPathW(LPWSTR pszPath)
 {
-       FIXME("%s stub\n", debugstr_w(lpszPath));
-       return NULL;
+       WCHAR path[MAX_PATH];
+
+       TRACE("%s\n", debugstr_w(pszPath));
+
+       if (GetShortPathNameW(pszPath, path, MAX_PATH))
+       {
+         lstrcpyW(pszPath, path);
+       }
 }
 
 /*************************************************************************
  * PathGetShortPath [SHELL32.92]
  */
-LPVOID WINAPI PathGetShortPathAW(LPVOID lpszPath)
+VOID WINAPI PathGetShortPathAW(LPVOID pszPath)
 {
        if(SHELL_OsIsUnicode())
-         return PathGetShortPathW(lpszPath);
-       return PathGetShortPathA(lpszPath);
+         PathGetShortPathW(pszPath);
+       PathGetShortPathA(pszPath);
 }
 
 /*************************************************************************
@@ -350,7 +365,7 @@ static BOOL PathIsExeA (LPCSTR lpszPath)
        TRACE("path=%s\n",lpszPath);
 
        for(i=0; lpszExtensions[i]; i++)
-         if (!strcasecmp(lpszExtension,lpszExtensions[i])) return TRUE;
+         if (!lstrcmpiA(lpszExtension,lpszExtensions[i])) return TRUE;
 
        return FALSE;
 }
@@ -543,11 +558,76 @@ BOOL WINAPI PathFindOnPathAW(LPVOID sFile, LPCVOID sOtherDirs)
 
 /*************************************************************************
  * PathCleanupSpec     [SHELL32.171]
+ *
+ * lpszFile is changed in place.
  */
-DWORD WINAPI PathCleanupSpecAW (LPCVOID x, LPVOID y)
+int WINAPI PathCleanupSpec( LPCWSTR lpszPathW, LPWSTR lpszFileW )
 {
-    FIXME("(%p, %p) stub\n",x,y);
-    return TRUE;
+    int i = 0;
+    DWORD rc = 0;
+    int length = 0;
+
+    if (SHELL_OsIsUnicode())
+    {
+        LPWSTR p = lpszFileW;
+
+        TRACE("Cleanup %s\n",debugstr_w(lpszFileW));
+
+        if (lpszPathW)
+            length = strlenW(lpszPathW);
+
+        while (*p)
+        {
+            int gct = PathGetCharTypeW(*p);
+            if (gct == GCT_INVALID || gct == GCT_WILD || gct == GCT_SEPARATOR)
+            {
+                lpszFileW[i]='-';
+                rc |= PCS_REPLACEDCHAR;
+            }
+            else
+                lpszFileW[i]=*p;
+            i++;
+            p++;
+            if (length + i == MAX_PATH)
+            {
+                rc |= PCS_FATAL | PCS_PATHTOOLONG;
+                break;
+            }
+        }
+        lpszFileW[i]=0;
+    }
+    else
+    {
+        LPSTR lpszFileA = (LPSTR)lpszFileW;
+        LPCSTR lpszPathA = (LPSTR)lpszPathW;
+        LPSTR p = lpszFileA;
+
+        TRACE("Cleanup %s\n",debugstr_a(lpszFileA));
+
+        if (lpszPathA)
+            length = strlen(lpszPathA);
+
+        while (*p)
+        {
+            int gct = PathGetCharTypeA(*p);
+            if (gct == GCT_INVALID || gct == GCT_WILD || gct == GCT_SEPARATOR)
+            {
+                lpszFileA[i]='-';
+                rc |= PCS_REPLACEDCHAR;
+            }
+            else
+                lpszFileA[i]=*p;
+            i++;
+            p++;
+            if (length + i == MAX_PATH)
+            {
+                rc |= PCS_FATAL | PCS_PATHTOOLONG;
+                break;
+            }
+        }
+        lpszFileA[i]=0;
+    }
+    return rc;
 }
 
 /*************************************************************************
@@ -676,508 +756,1135 @@ VOID WINAPI PathSetDlgItemPathAW(HWND hDlg, int id, LPCVOID pszPath)
             PathSetDlgItemPathA(hDlg, id, pszPath);
 }
 
-/*************************************************************************
- * SHGetFolderPathW                    [SHELL32.@]
- *
- * converts csidl to path
- */
-
+static const WCHAR szCurrentVersion[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\0'};
+static const WCHAR Administrative_ToolsW[] = {'A','d','m','i','n','i','s','t','r','a','t','i','v','e',' ','T','o','o','l','s','\0'};
+static const WCHAR AppDataW[] = {'A','p','p','D','a','t','a','\0'};
+static const WCHAR CacheW[] = {'C','a','c','h','e','\0'};
+static const WCHAR CD_BurningW[] = {'C','D',' ','B','u','r','n','i','n','g','\0'};
+static const WCHAR Common_Administrative_ToolsW[] = {'C','o','m','m','o','n',' ','A','d','m','i','n','i','s','t','r','a','t','i','v','e',' ','T','o','o','l','s','\0'};
+static const WCHAR Common_AppDataW[] = {'C','o','m','m','o','n',' ','A','p','p','D','a','t','a','\0'};
+static const WCHAR Common_DesktopW[] = {'C','o','m','m','o','n',' ','D','e','s','k','t','o','p','\0'};
+static const WCHAR Common_DocumentsW[] = {'C','o','m','m','o','n',' ','D','o','c','u','m','e','n','t','s','\0'};
+static const WCHAR CommonFilesDirW[] = {'C','o','m','m','o','n','F','i','l','e','s','D','i','r','\0'};
+static const WCHAR CommonMusicW[] = {'C','o','m','m','o','n','M','u','s','i','c','\0'};
+static const WCHAR CommonPicturesW[] = {'C','o','m','m','o','n','P','i','c','t','u','r','e','s','\0'};
+static const WCHAR Common_ProgramsW[] = {'C','o','m','m','o','n',' ','P','r','o','g','r','a','m','s','\0'};
+static const WCHAR Common_StartUpW[] = {'C','o','m','m','o','n',' ','S','t','a','r','t','U','p','\0'};
+static const WCHAR Common_Start_MenuW[] = {'C','o','m','m','o','n',' ','S','t','a','r','t',' ','M','e','n','u','\0'};
+static const WCHAR Common_TemplatesW[] = {'C','o','m','m','o','n',' ','T','e','m','p','l','a','t','e','s','\0'};
+static const WCHAR CommonVideoW[] = {'C','o','m','m','o','n','V','i','d','e','o','\0'};
+static const WCHAR CookiesW[] = {'C','o','o','k','i','e','s','\0'};
+static const WCHAR DesktopW[] = {'D','e','s','k','t','o','p','\0'};
+static const WCHAR FavoritesW[] = {'F','a','v','o','r','i','t','e','s','\0'};
+static const WCHAR FontsW[] = {'F','o','n','t','s','\0'};
+static const WCHAR HistoryW[] = {'H','i','s','t','o','r','y','\0'};
+static const WCHAR Local_AppDataW[] = {'L','o','c','a','l',' ','A','p','p','D','a','t','a','\0'};
+static const WCHAR My_MusicW[] = {'M','y',' ','M','u','s','i','c','\0'};
+static const WCHAR My_PicturesW[] = {'M','y',' ','P','i','c','t','u','r','e','s','\0'};
+static const WCHAR My_VideoW[] = {'M','y',' ','V','i','d','e','o','\0'};
+static const WCHAR NetHoodW[] = {'N','e','t','H','o','o','d','\0'};
+static const WCHAR PersonalW[] = {'P','e','r','s','o','n','a','l','\0'};
+static const WCHAR PrintHoodW[] = {'P','r','i','n','t','H','o','o','d','\0'};
+static const WCHAR ProgramFilesDirW[] = {'P','r','o','g','r','a','m','F','i','l','e','s','D','i','r','\0'};
+static const WCHAR ProgramsW[] = {'P','r','o','g','r','a','m','s','\0'};
+static const WCHAR RecentW[] = {'R','e','c','e','n','t','\0'};
+static const WCHAR ResourcesW[] = {'R','e','s','o','u','r','c','e','s','\0'};
+static const WCHAR SendToW[] = {'S','e','n','d','T','o','\0'};
+static const WCHAR StartUpW[] = {'S','t','a','r','t','U','p','\0'};
+static const WCHAR Start_MenuW[] = {'S','t','a','r','t',' ','M','e','n','u','\0'};
+static const WCHAR TemplatesW[] = {'T','e','m','p','l','a','t','e','s','\0'};
+static const WCHAR DefaultW[] = {'.','D','e','f','a','u','l','t','\0'};
+static const WCHAR AllUsersProfileW[] = {'%','A','L','L','U','S','E','R','S','P','R','O','F','I','L','E','%','\0'};
+static const WCHAR UserProfileW[] = {'%','U','S','E','R','P','R','O','F','I','L','E','%','\0'};
+static const WCHAR SystemDriveW[] = {'%','S','y','s','t','e','m','D','r','i','v','e','%','\0'};
+static const WCHAR ProfileListW[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s',' ','N','T','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','P','r','o','f','i','l','e','L','i','s','t',0};
+static const WCHAR ProfilesDirectoryW[] = {'P','r','o','f','i','l','e','s','D','i','r','e','c','t','o','r','y',0};
+static const WCHAR AllUsersProfileValueW[] = {'A','l','l','U','s','e','r','s','P','r','o','f','i','l','e','\0'};
 static const WCHAR szSHFolders[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','E','x','p','l','o','r','e','r','\\','S','h','e','l','l',' ','F','o','l','d','e','r','s','\0'};
 static const WCHAR szSHUserFolders[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','E','x','p','l','o','r','e','r','\\','U','s','e','r',' ','S','h','e','l','l',' ','F','o','l','d','e','r','s','\0'};
-static const WCHAR szSetup[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','E','x','p','l','o','r','e','r','\\','S','e','t','u','p','\0'};
-static const WCHAR szCurrentVersion[] = {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\0'};
+/* This defaults to L"Documents and Settings" on Windows 2000/XP, but we're
+ * acting more Windows 9x-like for now.
+ */
+static const WCHAR szDefaultProfileDirW[] = {'p','r','o','f','i','l','e','s','\0'};
+static const WCHAR AllUsersW[] = {'A','l','l',' ','U','s','e','r','s','\0'};
+
+typedef enum _CSIDL_Type {
+    CSIDL_Type_User,
+    CSIDL_Type_AllUsers,
+    CSIDL_Type_CurrVer,
+    CSIDL_Type_Disallowed,
+    CSIDL_Type_NonExistent,
+    CSIDL_Type_WindowsPath,
+    CSIDL_Type_SystemPath,
+} CSIDL_Type;
 
 typedef struct
 {
-    DWORD dwFlags;
-    HKEY hRootKey;
-    LPCSTR szValueName;
-    LPCSTR szDefaultPath; /* fallback string; sub dir of windows directory */
+    CSIDL_Type type;
+    LPCWSTR    szValueName;
+    LPCWSTR    szDefaultPath; /* fallback string or resource ID */
 } CSIDL_DATA;
 
-#define CSIDL_MYFLAG_SHFOLDER  1
-#define CSIDL_MYFLAG_SETUP     2
-#define CSIDL_MYFLAG_CURRVER   4
-#define CSIDL_MYFLAG_RELATIVE  8
-
-#define HKLM HKEY_LOCAL_MACHINE
-#define HKCU HKEY_CURRENT_USER
 static const CSIDL_DATA CSIDL_Data[] =
 {
-    { /* CSIDL_DESKTOP */
-       9, HKCU,
-       "Desktop",
-       "Desktop"
+    { /* 0x00 - CSIDL_DESKTOP */
+        CSIDL_Type_User,
+        DesktopW,
+        MAKEINTRESOURCEW(IDS_DESKTOPDIRECTORY)
+    },
+    { /* 0x01 - CSIDL_INTERNET */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL
+    },
+    { /* 0x02 - CSIDL_PROGRAMS */
+        CSIDL_Type_User,
+        ProgramsW,
+        MAKEINTRESOURCEW(IDS_PROGRAMS)
+    },
+    { /* 0x03 - CSIDL_CONTROLS (.CPL files) */
+        CSIDL_Type_SystemPath,
+        NULL,
+        NULL
     },
-    { /* CSIDL_INTERNET */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       NULL,
+    { /* 0x04 - CSIDL_PRINTERS */
+        CSIDL_Type_SystemPath,
+        NULL,
+        NULL
     },
-    { /* CSIDL_PROGRAMS */
-       9, HKCU,
-       "Programs",
-       "Start Menu\\Programs"
+    { /* 0x05 - CSIDL_PERSONAL */
+        CSIDL_Type_User,
+        PersonalW,
+        MAKEINTRESOURCEW(IDS_PERSONAL)
     },
-    { /* CSIDL_CONTROLS (.CPL files) */
-       10, HKLM,
-       "SysDir",
-       "SYSTEM"
+    { /* 0x06 - CSIDL_FAVORITES */
+        CSIDL_Type_User,
+        FavoritesW,
+        MAKEINTRESOURCEW(IDS_FAVORITES)
     },
-    { /* CSIDL_PRINTERS */
-       10, HKLM,
-       "SysDir",
-       "SYSTEM"
+    { /* 0x07 - CSIDL_STARTUP */
+        CSIDL_Type_User,
+        StartUpW,
+        MAKEINTRESOURCEW(IDS_STARTUP)
     },
-    { /* CSIDL_PERSONAL */
-       1, HKCU,
-       "Personal",
-       "My Documents"
+    { /* 0x08 - CSIDL_RECENT */
+        CSIDL_Type_User,
+        RecentW,
+        MAKEINTRESOURCEW(IDS_RECENT)
     },
-    { /* CSIDL_FAVORITES */
-       9, HKCU,
-       "Favorites",
-       "Favorites"
+    { /* 0x09 - CSIDL_SENDTO */
+        CSIDL_Type_User,
+        SendToW,
+        MAKEINTRESOURCEW(IDS_SENDTO)
     },
-    { /* CSIDL_STARTUP */
-       9, HKCU,
-       "StartUp",
-       "Start Menu\\Programs\\StartUp"
+    { /* 0x0a - CSIDL_BITBUCKET - Recycle Bin */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL,
     },
-    { /* CSIDL_RECENT */
-       9, HKCU,
-       "Recent",
-       "Recent"
+    { /* 0x0b - CSIDL_STARTMENU */
+        CSIDL_Type_User,
+        Start_MenuW,
+        MAKEINTRESOURCEW(IDS_STARTMENU)
     },
-    { /* CSIDL_SENDTO */
-       9, HKCU,
-       "SendTo",
-       "SendTo"
+    { /* 0x0c - CSIDL_MYDOCUMENTS */
+        CSIDL_Type_Disallowed, /* matches WinXP--can't get its path */
+        NULL,
+        NULL
     },
-    { /* CSIDL_BITBUCKET - Recycle Bin */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       "recycled"
+    { /* 0x0d - CSIDL_MYMUSIC */
+        CSIDL_Type_User,
+        My_MusicW,
+        MAKEINTRESOURCEW(IDS_MYMUSIC)
     },
-    { /* CSIDL_STARTMENU */
-       9, HKCU,
-       "Start Menu",
-       "Start Menu"
+    { /* 0x0e - CSIDL_MYVIDEO */
+        CSIDL_Type_User,
+        My_VideoW,
+        MAKEINTRESOURCEW(IDS_MYVIDEO)
     },
-    { /* CSIDL_MYDOCUMENTS */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x0f - unassigned */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL,
     },
-    { /* CSIDL_MYMUSIC */
-       1, HKCU,
-       "My Music",
-       "My Documents\\My Music"
+    { /* 0x10 - CSIDL_DESKTOPDIRECTORY */
+        CSIDL_Type_User,
+        DesktopW,
+        MAKEINTRESOURCEW(IDS_DESKTOPDIRECTORY)
     },
-    { /* CSIDL_MYMUSIC */
-       1, HKCU,
-       "My Video",
-       "My Documents\\My Video"
+    { /* 0x11 - CSIDL_DRIVES */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL,
     },
-    { /* unassigned */
-       0, 0,
-       NULL,
-       NULL,
+    { /* 0x12 - CSIDL_NETWORK */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL,
     },
-    { /* CSIDL_DESKTOPDIRECTORY */
-       9, HKCU,
-       "Desktop",
-       "Desktop"
+    { /* 0x13 - CSIDL_NETHOOD */
+        CSIDL_Type_User,
+        NetHoodW,
+        MAKEINTRESOURCEW(IDS_NETHOOD)
     },
-    { /* CSIDL_DRIVES */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       "My Computer"
+    { /* 0x14 - CSIDL_FONTS */
+        CSIDL_Type_WindowsPath,
+        NULL,
+        FontsW
     },
-    { /* CSIDL_NETWORK */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       "Network Neighborhood"
+    { /* 0x15 - CSIDL_TEMPLATES */
+        CSIDL_Type_User,
+        TemplatesW,
+        MAKEINTRESOURCEW(IDS_TEMPLATES)
     },
-    { /* CSIDL_NETHOOD */
-       9, HKCU,
-       "NetHood",
-       "NetHood"
+    { /* 0x16 - CSIDL_COMMON_STARTMENU */
+        CSIDL_Type_AllUsers,
+        Common_Start_MenuW,
+        MAKEINTRESOURCEW(IDS_STARTMENU)
     },
-    { /* CSIDL_FONTS */
-       9, HKCU,
-       "Fonts",
-       "Fonts"
+    { /* 0x17 - CSIDL_COMMON_PROGRAMS */
+        CSIDL_Type_AllUsers,
+        Common_ProgramsW,
+        MAKEINTRESOURCEW(IDS_PROGRAMS)
     },
-    { /* CSIDL_TEMPLATES */
-       9, HKCU,
-       "Templates",
-       "ShellNew"
+    { /* 0x18 - CSIDL_COMMON_STARTUP */
+        CSIDL_Type_AllUsers,
+        Common_StartUpW,
+        MAKEINTRESOURCEW(IDS_STARTUP)
     },
-    { /* CSIDL_COMMON_STARTMENU */
-       9, HKLM,
-       "Common Start Menu",
-       "Start Menu"
+    { /* 0x19 - CSIDL_COMMON_DESKTOPDIRECTORY */
+        CSIDL_Type_AllUsers,
+        Common_DesktopW,
+        MAKEINTRESOURCEW(IDS_DESKTOP)
     },
-    { /* CSIDL_COMMON_PROGRAMS */
-       9, HKLM,
-       "Common Programs",
-       ""
+    { /* 0x1a - CSIDL_APPDATA */
+        CSIDL_Type_User,
+        AppDataW,
+        MAKEINTRESOURCEW(IDS_APPDATA)
     },
-    { /* CSIDL_COMMON_STARTUP */
-       9, HKLM,
-       "Common StartUp",
-       "All Users\\Start Menu\\Programs\\StartUp"
+    { /* 0x1b - CSIDL_PRINTHOOD */
+        CSIDL_Type_User,
+        PrintHoodW,
+        MAKEINTRESOURCEW(IDS_PRINTHOOD)
     },
-    { /* CSIDL_COMMON_DESKTOPDIRECTORY */
-       9, HKLM,
-       "Common Desktop",
-       "Desktop"
+    { /* 0x1c - CSIDL_LOCAL_APPDATA */
+        CSIDL_Type_User,
+        Local_AppDataW,
+        MAKEINTRESOURCEW(IDS_LOCAL_APPDATA)
     },
-    { /* CSIDL_APPDATA */
-       9, HKCU,
-       "AppData",
-       "Application Data"
+    { /* 0x1d - CSIDL_ALTSTARTUP */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_PRINTHOOD */
-       9, HKCU,
-       "PrintHood",
-       "PrintHood"
+    { /* 0x1e - CSIDL_COMMON_ALTSTARTUP */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_LOCAL_APPDATA (win2k only/undocumented) */
-       1, HKCU,
-       "Local AppData",
-       "Local Settings\\Application Data",
+    { /* 0x1f - CSIDL_COMMON_FAVORITES */
+        CSIDL_Type_AllUsers,
+        FavoritesW,
+        MAKEINTRESOURCEW(IDS_FAVORITES)
     },
-    { /* CSIDL_ALTSTARTUP */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x20 - CSIDL_INTERNET_CACHE */
+        CSIDL_Type_User,
+        CacheW,
+        MAKEINTRESOURCEW(IDS_INTERNET_CACHE)
     },
-    { /* CSIDL_COMMON_ALTSTARTUP */
-       0, (HKEY)1, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x21 - CSIDL_COOKIES */
+        CSIDL_Type_User,
+        CookiesW,
+        MAKEINTRESOURCEW(IDS_COOKIES)
     },
-    { /* CSIDL_COMMON_FAVORITES */
-       9, HKCU,
-       "Favorites",
-       "Favorites"
+    { /* 0x22 - CSIDL_HISTORY */
+        CSIDL_Type_User,
+        HistoryW,
+        MAKEINTRESOURCEW(IDS_HISTORY)
     },
-    { /* CSIDL_INTERNET_CACHE (32) */
-       9, HKCU,
-       "Cache",
-       "Temporary Internet Files"
+    { /* 0x23 - CSIDL_COMMON_APPDATA */
+        CSIDL_Type_AllUsers,
+        Common_AppDataW,
+        MAKEINTRESOURCEW(IDS_APPDATA)
     },
-    { /* CSIDL_COOKIES (33) */
-       9, HKCU,
-       "Cookies",
-       "Cookies"
+    { /* 0x24 - CSIDL_WINDOWS */
+        CSIDL_Type_WindowsPath,
+        NULL,
+        NULL
     },
-    { /* CSIDL_HISTORY (34) */
-       9, HKCU,
-       "History",
-       "History"
+    { /* 0x25 - CSIDL_SYSTEM */
+        CSIDL_Type_SystemPath,
+        NULL,
+        NULL
     },
-    { /* CSIDL_COMMON_APPDATA */
-       9, HKLM,
-       "Common AppData",
-       "All Users\\Application Data"
+    { /* 0x26 - CSIDL_PROGRAM_FILES */
+        CSIDL_Type_CurrVer,
+        ProgramFilesDirW,
+        MAKEINTRESOURCEW(IDS_PROGRAM_FILES)
     },
-    { /* CSIDL_WINDOWS */
-       2, HKLM,
-       "WinDir",
-       "Windows"
+    { /* 0x27 - CSIDL_MYPICTURES */
+        CSIDL_Type_User,
+        My_PicturesW,
+        MAKEINTRESOURCEW(IDS_MYPICTURES)
     },
-    { /* CSIDL_SYSTEM */
-       10, HKLM,
-       "SysDir",
-       "SYSTEM"
+    { /* 0x28 - CSIDL_PROFILE */
+        CSIDL_Type_User,
+        NULL,
+        NULL
     },
-    { /* CSIDL_PROGRAM_FILES */
-       4, HKLM,
-       "ProgramFilesDir",
-       "Program Files"
+    { /* 0x29 - CSIDL_SYSTEMX86 */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_MYPICTURES */
-       1, HKCU,
-       "My Pictures",
-       "My Documents\\My Pictures"
+    { /* 0x2a - CSIDL_PROGRAM_FILESX86 */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_PROFILE */
-       10, HKLM,
-       "WinDir", /* correct ? */
-       ""
+    { /* 0x2b - CSIDL_PROGRAM_FILES_COMMON */
+        CSIDL_Type_CurrVer,
+        CommonFilesDirW,
+        MAKEINTRESOURCEW(IDS_PROGRAM_FILES_COMMON)
     },
-    { /* CSIDL_SYSTEMX86 */
-       10, HKLM,
-       "SysDir",
-       "SYSTEM"
+    { /* 0x2c - CSIDL_PROGRAM_FILES_COMMONX86 */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_PROGRAM_FILESX86 */
-       4, HKLM,
-       "ProgramFilesDir",
-       "Program Files"
+    { /* 0x2d - CSIDL_COMMON_TEMPLATES */
+        CSIDL_Type_AllUsers,
+        Common_TemplatesW,
+        MAKEINTRESOURCEW(IDS_TEMPLATES)
     },
-    { /* CSIDL_PROGRAM_FILES_COMMON */
-       4, HKLM,
-       "CommonFilesDir",
-       "Program Files\\Common Files" /* ? */
+    { /* 0x2e - CSIDL_COMMON_DOCUMENTS */
+        CSIDL_Type_AllUsers,
+        Common_DocumentsW,
+        MAKEINTRESOURCEW(IDS_COMMON_DOCUMENTS)
     },
-    { /* CSIDL_PROGRAM_FILES_COMMONX86 */
-       4, HKLM,
-       "CommonFilesDir",
-       "Program Files\\Common Files" /* ? */
+    { /* 0x2f - CSIDL_COMMON_ADMINTOOLS */
+        CSIDL_Type_AllUsers,
+        Common_Administrative_ToolsW,
+        MAKEINTRESOURCEW(IDS_ADMINTOOLS)
     },
-    { /* CSIDL_COMMON_TEMPLATES */
-       9, HKLM,
-       "Common Templates",
-       /*"Documents and Settings\\"*/"All Users\\Templates"
+    { /* 0x30 - CSIDL_ADMINTOOLS */
+        CSIDL_Type_User,
+        Administrative_ToolsW,
+        MAKEINTRESOURCEW(IDS_ADMINTOOLS)
     },
-    { /* CSIDL_COMMON_DOCUMENTS */
-       9, HKLM,
-       "Common Documents",
-       /*"Documents and Settings\\"*/"All Users\\Documents"
+    { /* 0x31 - CSIDL_CONNECTIONS */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL
     },
-    { /* CSIDL_COMMON_ADMINTOOLS */
-       9, HKLM,
-       "Common Administrative Tools",
-       /*"Documents and Settings\\"*/"All Users\\Start Menu\\Programs\\Administrative Tools"
+    { /* 0x32 - unassigned */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL
     },
-    { /* CSIDL_ADMINTOOLS */
-       9, HKCU,
-       "Administrative Tools",
-       "Start Menu\\Programs\\Administrative Tools"
+    { /* 0x33 - unassigned */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL
     },
-    { /* CSIDL_CONNECTIONS */
-       0, 0, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x34 - unassigned */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL
     },
-    { /* CSIDL_COMMON_MUSIC */
-       9, HKLM,
-       "CommonMusic",
-       /*"Documents and Settings\\"*/"All Users\\Documents\\My Music"
+    { /* 0x35 - CSIDL_COMMON_MUSIC */
+        CSIDL_Type_AllUsers,
+        CommonMusicW,
+        MAKEINTRESOURCEW(IDS_COMMON_MUSIC)
     },
-    { /* CSIDL_COMMON_PICTURES */
-       9, HKLM,
-       "CommonPictures",
-       /*"Documents and Settings\\"*/"All Users\\Documents\\My Pictures"
+    { /* 0x36 - CSIDL_COMMON_PICTURES */
+        CSIDL_Type_AllUsers,
+        CommonPicturesW,
+        MAKEINTRESOURCEW(IDS_COMMON_PICTURES)
     },
-    { /* CSIDL_COMMON_VIDEO */
-       9, HKLM,
-       "CommonVideo",
-       /*"Documents and Settings\\"*/"All Users\\Documents\\My Video"
+    { /* 0x37 - CSIDL_COMMON_VIDEO */
+        CSIDL_Type_AllUsers,
+        CommonVideoW,
+        MAKEINTRESOURCEW(IDS_COMMON_VIDEO)
     },
-    { /* CSIDL_RESOURCES */
-       0, 0, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x38 - CSIDL_RESOURCES */
+        CSIDL_Type_WindowsPath,
+        NULL,
+        ResourcesW
     },
-    { /* CSIDL_RESOURCES_LOCALIZED */
-       0, 0, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x39 - CSIDL_RESOURCES_LOCALIZED */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_COMMON_OEM_LINKS */
-       0, 0, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x3a - CSIDL_COMMON_OEM_LINKS */
+        CSIDL_Type_NonExistent,
+        NULL,
+        NULL
     },
-    { /* CSIDL_CDBURN_AREA */
-       1, HKCU,
-       "CD Burning",
-       "Local Settings\\Application Data\\Microsoft\\CD Burning"
+    { /* 0x3b - CSIDL_CDBURN_AREA */
+        CSIDL_Type_User,
+        CD_BurningW,
+        MAKEINTRESOURCEW(IDS_CDBURN_AREA)
     },
-    { /* unassigned 3C */
-       0, 0,
-       NULL,
-       NULL
+    { /* 0x3c unassigned */
+        CSIDL_Type_Disallowed,
+        NULL,
+        NULL
     },
-    { /* CSIDL_COMPUTERSNEARME */
-       0, 0, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x3d - CSIDL_COMPUTERSNEARME */
+        CSIDL_Type_Disallowed, /* FIXME */
+        NULL,
+        NULL
     },
-    { /* CSIDL_PROFILES */
-       0, 0, /* FIXME */
-       NULL,
-       NULL
+    { /* 0x3e - CSIDL_PROFILES */
+        CSIDL_Type_Disallowed, /* oddly, this matches WinXP */
+        NULL,
+        NULL
     }
 };
-#undef HKCU
-#undef HKLM
 
-HRESULT WINAPI SHGetFolderPathW(
-       HWND hwndOwner,
-       int csidl,
-       HANDLE hToken,  /* [in] FIXME: get paths for specific user */
-       DWORD dwFlags,  /* [in] FIXME: SHGFP_TYPE_CURRENT|SHGFP_TYPE_DEFAULT */
-       LPWSTR pszPath)
-{
-       WCHAR   szValueName[MAX_PATH], szDefaultPath[MAX_PATH], szBuildPath[MAX_PATH];
-       HKEY    hRootKey, hKey;
-       DWORD   dwCsidlFlags;
-       DWORD   dwType, dwDisp, dwPathLen = MAX_PATH;
-       DWORD   folder = csidl & CSIDL_FOLDER_MASK;
-       WCHAR   *p;
-
-       TRACE("%p,%p,csidl=0x%04x\n", hwndOwner,pszPath,csidl);
-
-       if ((folder >= sizeof(CSIDL_Data) / sizeof(CSIDL_Data[0])) ||
-           (CSIDL_Data[folder].hRootKey == 0))
-       {
-           ERR("folder 0x%04lx unknown or not allowed\n", folder);
-           return E_FAIL;
-       }
-       if (CSIDL_Data[folder].hRootKey == (HKEY)1)
-       {
-           FIXME("folder 0x%04lx unknown, please add.\n", folder);
-           return E_FAIL;
-       }
+static HRESULT _SHExpandEnvironmentStrings(LPCWSTR szSrc, LPWSTR szDest);
+
+/* Gets the value named value from the registry key
+ * rootKey\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
+ * (or from rootKey\userPrefix\... if userPrefix is not NULL) into path, which
+ * is assumed to be MAX_PATH WCHARs in length.
+ * If it exists, expands the value and writes the expanded value to
+ * rootKey\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
+ * Returns successful error code if the value was retrieved from the registry,
+ * and a failure otherwise.
+ */
+static HRESULT _SHGetUserShellFolderPath(HKEY rootKey, LPCWSTR userPrefix,
+ LPCWSTR value, LPWSTR path)
+{
+    HRESULT hr;
+    WCHAR shellFolderPath[MAX_PATH], userShellFolderPath[MAX_PATH];
+    LPCWSTR pShellFolderPath, pUserShellFolderPath;
+    DWORD dwDisp, dwType, dwPathLen = MAX_PATH;
+    HKEY userShellFolderKey, shellFolderKey;
+
+    TRACE("%p,%s,%s,%p\n",rootKey, debugstr_w(userPrefix), debugstr_w(value),
+     path);
+
+    if (userPrefix)
+    {
+        strcpyW(shellFolderPath, userPrefix);
+        PathAddBackslashW(shellFolderPath);
+        strcatW(shellFolderPath, szSHFolders);
+        pShellFolderPath = shellFolderPath;
+        strcpyW(userShellFolderPath, userPrefix);
+        PathAddBackslashW(userShellFolderPath);
+        strcatW(userShellFolderPath, szSHUserFolders);
+        pUserShellFolderPath = userShellFolderPath;
+    }
+    else
+    {
+        pUserShellFolderPath = szSHUserFolders;
+        pShellFolderPath = szSHFolders;
+    }
 
-       dwCsidlFlags = CSIDL_Data[folder].dwFlags;
-       hRootKey = CSIDL_Data[folder].hRootKey;
-       MultiByteToWideChar(CP_ACP, 0, CSIDL_Data[folder].szValueName, -1, szValueName, MAX_PATH);
-       MultiByteToWideChar(CP_ACP, 0, CSIDL_Data[folder].szDefaultPath, -1, szDefaultPath, MAX_PATH);
+    if (RegCreateKeyExW(rootKey, pShellFolderPath, 0, NULL, 0, KEY_ALL_ACCESS,
+     NULL, &shellFolderKey, &dwDisp))
+    {
+        TRACE("Failed to create %s\n", debugstr_w(pShellFolderPath));
+        return E_FAIL;
+    }
+    if (RegCreateKeyExW(rootKey, pUserShellFolderPath, 0, NULL, 0,
+     KEY_ALL_ACCESS, NULL, &userShellFolderKey, &dwDisp))
+    {
+        TRACE("Failed to create %s\n",
+         debugstr_w(pUserShellFolderPath));
+        RegCloseKey(shellFolderKey);
+        return E_FAIL;
+    }
 
-       if (dwCsidlFlags & CSIDL_MYFLAG_SHFOLDER)
-       {
-         /*   user shell folders */
-         if   (RegCreateKeyExW(hRootKey,szSHUserFolders,0,NULL,0,KEY_ALL_ACCESS,NULL,&hKey,&dwDisp)) return E_FAIL;
-
-         if   (RegQueryValueExW(hKey,szValueName,NULL,&dwType,(LPBYTE)pszPath,&dwPathLen))
-         {
-           RegCloseKey(hKey);
-
-           /* shell folders */
-           if (RegCreateKeyExW(hRootKey,szSHFolders,0,NULL,0,KEY_ALL_ACCESS,NULL,&hKey,&dwDisp)) return E_FAIL;
-
-           if (RegQueryValueExW(hKey,szValueName,NULL,&dwType,(LPBYTE)pszPath,&dwPathLen))
-           {
-             /* value not existing */
-             if (dwCsidlFlags & CSIDL_MYFLAG_RELATIVE)
-             {
-               GetWindowsDirectoryW(pszPath, MAX_PATH);
-               PathAddBackslashW(pszPath);
-               strcatW(pszPath, szDefaultPath);
-             }
-             else
-             {
-               GetSystemDirectoryW(pszPath, MAX_PATH);
-               strcpyW(pszPath + 3, szDefaultPath);
-             }
-              dwType=REG_SZ;
-             RegSetValueExW(hKey,szValueName,0,REG_SZ,(LPBYTE)pszPath,strlenW(pszPath)+1);
-           }
-         }
-         RegCloseKey(hKey);
+    if (!RegQueryValueExW(userShellFolderKey, value, NULL, &dwType,
+     (LPBYTE)path, &dwPathLen) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ))
+    {
+        LONG ret;
+
+        path[dwPathLen / sizeof(WCHAR)] = '\0';
+        if (dwType == REG_EXPAND_SZ && path[0] == '%')
+        {
+            WCHAR szTemp[MAX_PATH];
+
+            _SHExpandEnvironmentStrings(path, szTemp);
+            lstrcpynW(path, szTemp, MAX_PATH);
         }
-       else
-       {
-         LPCWSTR pRegPath;
-
-         if (dwCsidlFlags & CSIDL_MYFLAG_SETUP)
-           pRegPath = szSetup;
-         else if (dwCsidlFlags & CSIDL_MYFLAG_CURRVER)
-           pRegPath = szCurrentVersion;
-         else
-         {
-           ERR("folder settings broken, please correct !\n");
-           return E_FAIL;
-         }
-
-         if   (RegCreateKeyExW(hRootKey,pRegPath,0,NULL,0,KEY_ALL_ACCESS,NULL,&hKey,&dwDisp)) return E_FAIL;
-
-         if   (RegQueryValueExW(hKey,szValueName,NULL,&dwType,(LPBYTE)pszPath,&dwPathLen))
-         {
-           /* value not existing */
-           if (dwCsidlFlags & CSIDL_MYFLAG_RELATIVE)
-           {
-             GetWindowsDirectoryW(pszPath, MAX_PATH);
-             PathAddBackslashW(pszPath);
-             strcatW(pszPath, szDefaultPath);
-           }
-           else
-           {
-             GetSystemDirectoryW(pszPath, MAX_PATH);
-             strcpyW(pszPath + 3, szDefaultPath);
-           }
-            dwType=REG_SZ;
-           RegSetValueExW(hKey,szValueName,0,REG_SZ,(LPBYTE)pszPath,strlenW(pszPath)+1);
-         }
-         RegCloseKey(hKey);
-       }
+        ret = RegSetValueExW(shellFolderKey, value, 0, REG_SZ, (LPBYTE)path,
+         (strlenW(path) + 1) * sizeof(WCHAR));
+        if (ret != ERROR_SUCCESS)
+            hr = HRESULT_FROM_WIN32(ret);
+        else
+            hr = S_OK;
+    }
+    else
+        hr = E_FAIL;
+    RegCloseKey(shellFolderKey);
+    RegCloseKey(userShellFolderKey);
+    TRACE("returning 0x%08lx\n", hr);
+    return hr;
+}
 
-       /* expand paths like %USERPROFILE% */
-       if (dwType == REG_EXPAND_SZ)
-       {
-         ExpandEnvironmentStringsW(pszPath, szDefaultPath, MAX_PATH);
-         strcpyW(pszPath, szDefaultPath);
-       }
+/* Gets a 'semi-expanded' default value of the CSIDL with index folder into
+ * pszPath, based on the entries in CSIDL_Data.  By semi-expanded, I mean:
+ * - The entry's szDefaultPath may be either a string value or an integer
+ *   resource identifier.  In the latter case, the string value of the resource
+ *   is written.
+ * - Depending on the entry's type, the path may begin with an (unexpanded)
+ *   environment variable name.  The caller is responsible for expanding
+ *   environment strings if so desired.
+ *   The types that are prepended with environment variables are:
+ *   CSIDL_Type_User:     %USERPROFILE%
+ *   CSIDL_Type_AllUsers: %ALLUSERSPROFILE%
+ *   CSIDL_Type_CurrVer:  %SystemDrive%
+ *   (Others might make sense too, but as yet are unneeded.)
+ */
+static HRESULT _SHGetDefaultValue(BYTE folder, LPWSTR pszPath)
+{
+    HRESULT hr;
+    WCHAR resourcePath[MAX_PATH];
+    LPCWSTR pDefaultPath = NULL;
+
+    TRACE("0x%02x,%p\n", folder, pszPath);
+
+    if (folder >= sizeof(CSIDL_Data) / sizeof(CSIDL_Data[0]))
+        return E_INVALIDARG;
+    if (!pszPath)
+        return E_INVALIDARG;
+
+    /* Try special cases first */
+    hr = E_FAIL;
+    switch (folder)
+    {
+        case CSIDL_PERSONAL:
+        case CSIDL_MYMUSIC:
+        case CSIDL_MYPICTURES:
+        case CSIDL_MYVIDEO:
+        {
+            const char *home = getenv("HOME");
+
+            /* special case for "My Documents", map to $HOME */
+            if (home)
+            {
+                WCHAR homeW[MAX_PATH];
+
+                MultiByteToWideChar(CP_ACP, 0, home, -1, homeW, MAX_PATH);
+                if (GetFullPathNameW(homeW, MAX_PATH, pszPath, NULL) != 0 &&
+                 PathIsDirectoryW(pszPath))
+                    hr = S_OK;
+            }
+            break;
+        }
+        case CSIDL_DESKTOP:
+        case CSIDL_DESKTOPDIRECTORY:
+        {
+            const char *home = getenv("HOME");
+
+            /* special case for Desktop, map to $HOME/Desktop if it exists */
+            if (home)
+            {
+                WCHAR desktopW[MAX_PATH];
+
+                MultiByteToWideChar(CP_ACP, 0, home, -1, desktopW, MAX_PATH);
+                PathAppendW(desktopW, DesktopW);
+                if (GetFullPathNameW(desktopW, MAX_PATH, pszPath, NULL) != 0 &&
+                 PathIsDirectoryW(pszPath))
+                    hr = S_OK;
+            }
+            break;
+        }
+    }
+    if (SUCCEEDED(hr))
+        return hr;
+
+    /* Either the folder was unhandled, or a suitable default wasn't found,
+     * so use one of the resource-based defaults
+     */
+    if (CSIDL_Data[folder].szDefaultPath &&
+     IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath))
+    {
+        if (LoadStringW(shell32_hInstance,
+         LOWORD(CSIDL_Data[folder].szDefaultPath), resourcePath, MAX_PATH))
+        {
+            hr = S_OK;
+            pDefaultPath = resourcePath;
+        }
+        else
+        {
+            FIXME("(%d,%s), LoadString failed, missing translation?\n", folder,
+             debugstr_w(pszPath));
+            hr = E_FAIL;
+        }
+    }
+    else
+    {
+        hr = S_OK;
+        pDefaultPath = CSIDL_Data[folder].szDefaultPath;
+    }
+    if (SUCCEEDED(hr))
+    {
+        switch (CSIDL_Data[folder].type)
+        {
+            case CSIDL_Type_User:
+                strcpyW(pszPath, UserProfileW);
+                break;
+            case CSIDL_Type_AllUsers:
+                strcpyW(pszPath, AllUsersProfileW);
+                break;
+            case CSIDL_Type_CurrVer:
+                strcpyW(pszPath, SystemDriveW);
+                break;
+            default:
+                ; /* no corresponding env. var, do nothing */
+        }
+        if (pDefaultPath)
+        {
+            PathAddBackslashW(pszPath);
+            strcatW(pszPath, pDefaultPath);
+        }
+    }
+    TRACE("returning 0x%08lx\n", hr);
+    return hr;
+}
 
-       /* if we don't care about existing directories we are ready */
-       if(csidl & CSIDL_FLAG_DONT_VERIFY) return S_OK;
+/* Gets the (unexpanded) value of the folder with index folder into pszPath.
+ * The folder's type is assumed to be CSIDL_Type_CurrVer.  Its default value
+ * can be overridden in the HKLM\\szCurrentVersion key.
+ * If dwFlags has SHGFP_TYPE_DEFAULT set or if the value isn't overridden in
+ * the registry, uses _SHGetDefaultValue to get the value.
+ */
+static HRESULT _SHGetCurrentVersionPath(DWORD dwFlags, BYTE folder,
+ LPWSTR pszPath)
+{
+    HRESULT hr;
+
+    TRACE("0x%08lx,0x%02x,%p\n", dwFlags, folder, pszPath);
+
+    if (folder >= sizeof(CSIDL_Data) / sizeof(CSIDL_Data[0]))
+        return E_INVALIDARG;
+    if (CSIDL_Data[folder].type != CSIDL_Type_CurrVer)
+        return E_INVALIDARG;
+    if (!pszPath)
+        return E_INVALIDARG;
+
+    if (dwFlags & SHGFP_TYPE_DEFAULT)
+        hr = _SHGetDefaultValue(folder, pszPath);
+    else
+    {
+        HKEY hKey;
+        DWORD dwDisp;
+
+        if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, szCurrentVersion, 0,
+         NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, &dwDisp))
+            hr = E_FAIL;
+        else
+        {
+            DWORD dwType, dwPathLen = MAX_PATH * sizeof(WCHAR);
+
+            if (RegQueryValueExW(hKey, CSIDL_Data[folder].szValueName, NULL,
+             &dwType, (LPBYTE)pszPath, &dwPathLen) ||
+             (dwType != REG_SZ && dwType != REG_EXPAND_SZ))
+            {
+                hr = _SHGetDefaultValue(folder, pszPath);
+                dwType = REG_EXPAND_SZ;
+                RegSetValueExW(hKey, CSIDL_Data[folder].szValueName, 0, dwType,
+                 (LPBYTE)pszPath, (strlenW(pszPath)+1)*sizeof(WCHAR));
+            }
+            else
+            {
+                pszPath[dwPathLen / sizeof(WCHAR)] = '\0';
+                hr = S_OK;
+            }
+            RegCloseKey(hKey);
+        }
+    }
+    TRACE("returning 0x%08lx (output path is %s)\n", hr, debugstr_w(pszPath));
+    return hr;
+}
 
-       if (PathFileExistsW(pszPath)) return S_OK;
+/* Gets the user's path (unexpanded) for the CSIDL with index folder:
+ * If SHGFP_TYPE_DEFAULT is set, calls _SHGetDefaultValue for it.  Otherwise
+ * calls _SHGetUserShellFolderPath for it.  Where it looks depends on hToken:
+ * - if hToken is -1, looks in HKEY_USERS\.Default
+ * - otherwise looks first in HKEY_CURRENT_USER, followed by HKEY_LOCAL_MACHINE
+ *   if HKEY_CURRENT_USER doesn't contain any entries.  If both fail, finally
+ *   calls _SHGetDefaultValue for it.
+ */
+static HRESULT _SHGetUserProfilePath(HANDLE hToken, DWORD dwFlags, BYTE folder,
+ LPWSTR pszPath)
+{
+    HRESULT hr;
+
+    TRACE("%p,0x%08lx,0x%02x,%p\n", hToken, dwFlags, folder, pszPath);
+
+    if (folder >= sizeof(CSIDL_Data) / sizeof(CSIDL_Data[0]))
+        return E_INVALIDARG;
+    if (CSIDL_Data[folder].type != CSIDL_Type_User)
+        return E_INVALIDARG;
+    if (!pszPath)
+        return E_INVALIDARG;
+
+    /* Only the current user and the default user are supported right now
+     * I'm afraid.
+     * FIXME: should be able to call GetTokenInformation on the token,
+     * then call ConvertSidToStringSidW on it to get the user prefix.
+     * But Wine's registry doesn't store user info by sid, it stores it
+     * by user name (and I don't know how to convert from a token to a
+     * user name).
+     */
+    if (hToken != NULL && hToken != (HANDLE)-1)
+    {
+        FIXME("unsupported for user other than current or default\n");
+        return E_FAIL;
+    }
 
-       /* not existing but we are not allowed to create it */
-       if (!(csidl & CSIDL_FLAG_CREATE)) return E_FAIL;
+    if (dwFlags & SHGFP_TYPE_DEFAULT)
+        hr = _SHGetDefaultValue(folder, pszPath);
+    else
+    {
+        LPCWSTR userPrefix = NULL;
+        HKEY hRootKey;
+
+        if (hToken == (HANDLE)-1)
+        {
+            hRootKey = HKEY_USERS;
+            userPrefix = DefaultW;
+        }
+        else /* hToken == NULL, other values disallowed above */
+            hRootKey = HKEY_CURRENT_USER;
+        hr = _SHGetUserShellFolderPath(hRootKey, userPrefix,
+         CSIDL_Data[folder].szValueName, pszPath);
+        if (FAILED(hr) && hRootKey != HKEY_LOCAL_MACHINE)
+            hr = _SHGetUserShellFolderPath(HKEY_LOCAL_MACHINE, NULL,
+             CSIDL_Data[folder].szValueName, pszPath);
+        if (FAILED(hr))
+            hr = _SHGetDefaultValue(folder, pszPath);
+    }
+    TRACE("returning 0x%08lx (output path is %s)\n", hr, debugstr_w(pszPath));
+    return hr;
+}
 
-       /* create directory/directories */
-       strcpyW(szBuildPath, pszPath);
-       p = strchrW(szBuildPath, '\\');
-       while (p)
-       {
-           *p = 0;
-           if (!PathFileExistsW(szBuildPath))
-           {
-               if (!CreateDirectoryW(szBuildPath,NULL))
-               {
-                   ERR("Failed to create directory '%s'.\n", debugstr_w(pszPath));
-                   return E_FAIL;
-               }
-           }
-           *p = '\\';
-           p = strchrW(p+1, '\\');
-       }
-       /* last component must be created too. */
-       if (!PathFileExistsW(szBuildPath))
-       {
-           if (!CreateDirectoryW(szBuildPath,NULL))
-           {
-               ERR("Failed to create directory '%s'.\n", debugstr_w(pszPath));
-               return E_FAIL;
-           }
-       }
+/* Gets the (unexpanded) path for the CSIDL with index folder.  If dwFlags has
+ * SHGFP_TYPE_DEFAULT set, calls _SHGetDefaultValue.  Otherwise calls
+ * _SHGetUserShellFolderPath for it, looking only in HKEY_LOCAL_MACHINE.
+ * If this fails, falls back to _SHGetDefaultValue.
+ */
+static HRESULT _SHGetAllUsersProfilePath(DWORD dwFlags, BYTE folder,
+ LPWSTR pszPath)
+{
+    HRESULT hr;
+
+    TRACE("0x%08lx,0x%02x,%p\n", dwFlags, folder, pszPath);
+
+    if (folder >= sizeof(CSIDL_Data) / sizeof(CSIDL_Data[0]))
+        return E_INVALIDARG;
+    if (CSIDL_Data[folder].type != CSIDL_Type_AllUsers)
+        return E_INVALIDARG;
+    if (!pszPath)
+        return E_INVALIDARG;
+
+    if (dwFlags & SHGFP_TYPE_DEFAULT)
+        hr = _SHGetDefaultValue(folder, pszPath);
+    else
+    {
+        hr = _SHGetUserShellFolderPath(HKEY_LOCAL_MACHINE, NULL,
+         CSIDL_Data[folder].szValueName, pszPath);
+        if (FAILED(hr))
+            hr = _SHGetDefaultValue(folder, pszPath);
+    }
+    TRACE("returning 0x%08lx (output path is %s)\n", hr, debugstr_w(pszPath));
+    return hr;
+}
+
+/* From the original Wine source:
+ *
+ * Attempts to expand environment variables from szSrc into szDest, which is
+ * assumed to be MAX_PATH characters in length.  Before referring to the
+ * environment, handles a few variables directly, because the environment
+ * variables may not be set when this is called (as during Wine's installation
+ * when default values are being written to the registry).
+ * The directly handled environment variables, and their source, are:
+ * - ALLUSERSPROFILE, USERPROFILE: reads from the registry
+ * - SystemDrive: uses GetSystemDirectoryW and uses the drive portion of its
+ *   path
+ * If one of the directly handled environment variables is expanded, only
+ * expands a single variable, and only in the beginning of szSrc.
+ *
+ * That's fine for Wine, but it breaks in ReactOS where we have profile paths
+ * like "c:\documents and settings\Administrator.REACTOS". Anyway, we have the
+ * environment variables handy so we'll just use them instead of hacking around
+ */
+static HRESULT _SHExpandEnvironmentStrings(LPCWSTR szSrc, LPWSTR szDest)
+{
+    HRESULT hr = S_OK;
+    WCHAR szTemp[MAX_PATH];
+
+    TRACE("%s, %p\n", debugstr_w(szSrc), szDest);
+
+    if (!szSrc || !szDest) return E_INVALIDARG;
+
+    /* short-circuit if there's nothing to expand */
+    if (szSrc[0] != '%')
+    {
+        strcpyW(szDest, szSrc);
+        hr = S_OK;
+        goto end;
+    }
+
+    *szDest = 0;
+    strcpyW(szTemp, szSrc);
+    while (SUCCEEDED(hr) && szTemp[0] == '%')
+    {
+        DWORD ret = ExpandEnvironmentStringsW(szSrc, szDest, MAX_PATH);
+
+        if (ret > MAX_PATH)
+            hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+        else if (ret == 0)
+            hr = HRESULT_FROM_WIN32(GetLastError());
+        else
+            hr = S_OK;
+        if (SUCCEEDED(hr) && szDest[0] == '%')
+            strcpyW(szTemp, szDest);
+        else
+        {
+            /* terminate loop */
+            szTemp[0] = '\0';
+        }
+    }
+end:
+    TRACE("returning 0x%08lx (input was %s, output is %s)\n", hr,
+     debugstr_w(szSrc), debugstr_w(szDest));
+    return hr;
+}
+
+/*************************************************************************
+ * SHGetFolderPathW                    [SHELL32.@]
+ *
+ * Convert nFolder to path.  
+ *
+ * RETURNS
+ *  Success: S_OK
+ *  Failure: standard HRESULT error codes.
+ *
+ * NOTES
+ * Most values can be overridden in either
+ * HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
+ * or in the same location in HKLM.
+ * The registry usage is explained by the following tech note:
+ * http://www.microsoft.com/windows2000/techinfo/reskit/en-us/default.asp?url=/windows2000/techinfo/reskit/en-us/regentry/36173.asp
+ * The "Shell Folders" registry key was used in NT4 and earlier systems.
+ * Beginning with Windows 2000, the "User Shell Folders" key is used, so
+ * changes made to it are made to the former key too.  This synchronization is
+ * done on-demand: not until someone requests the value of one of these paths
+ * (by calling one of the SHGet functions) is the value synchronized.
+ * Furthermore, as explained here:
+ * http://www.microsoft.com/windows2000/techinfo/reskit/en-us/default.asp?url=/windows2000/techinfo/reskit/en-us/regentry/36276.asp
+ * the HKCU paths take precedence over the HKLM paths.
+ *
+ */
+HRESULT WINAPI SHGetFolderPathW(
+       HWND hwndOwner,    /* [I] owner window */
+       int nFolder,       /* [I] CSIDL identifying the folder */
+       HANDLE hToken,     /* [I] access token */
+       DWORD dwFlags,     /* [I] which path to return */
+       LPWSTR pszPath)    /* [O] converted path */
+{
+    HRESULT    hr;
+    WCHAR      szBuildPath[MAX_PATH], szTemp[MAX_PATH];
+    DWORD      folder = nFolder & CSIDL_FOLDER_MASK;
+    CSIDL_Type type;
+    int        ret;
+
+    TRACE("%p,%p,nFolder=0x%04x\n", hwndOwner,pszPath,nFolder);
+
+    /* Windows always NULL-terminates the resulting path regardless of success
+     * or failure, so do so first
+     */
+    if (pszPath)
+        *pszPath = '\0';
+    if (folder >= sizeof(CSIDL_Data) / sizeof(CSIDL_Data[0]))
+        return E_INVALIDARG;
+    szTemp[0] = 0;
+    type = CSIDL_Data[folder].type;
+    switch (type)
+    {
+        case CSIDL_Type_Disallowed:
+            hr = E_INVALIDARG;
+            break;
+        case CSIDL_Type_NonExistent:
+            hr = S_FALSE;
+            break;
+        case CSIDL_Type_WindowsPath:
+            GetWindowsDirectoryW(szTemp, MAX_PATH);
+            if (CSIDL_Data[folder].szDefaultPath &&
+             !IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
+             *CSIDL_Data[folder].szDefaultPath)
+            {
+                PathAddBackslashW(szTemp);
+                strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
+            }
+            hr = S_OK;
+            break;
+        case CSIDL_Type_SystemPath:
+            GetSystemDirectoryW(szTemp, MAX_PATH);
+            if (CSIDL_Data[folder].szDefaultPath &&
+             !IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
+             *CSIDL_Data[folder].szDefaultPath)
+            {
+                PathAddBackslashW(szTemp);
+                strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
+            }
+            hr = S_OK;
+            break;
+        case CSIDL_Type_CurrVer:
+            hr = _SHGetCurrentVersionPath(dwFlags, folder, szTemp);
+            break;
+        case CSIDL_Type_User:
+            hr = _SHGetUserProfilePath(hToken, dwFlags, folder, szTemp);
+            break;
+        case CSIDL_Type_AllUsers:
+            hr = _SHGetAllUsersProfilePath(dwFlags, folder, szTemp);
+            break;
+        default:
+            FIXME("bogus type %d, please fix\n", type);
+            hr = E_INVALIDARG;
+            break;
+    }
 
-       TRACE("Created missing system directory '%s'\n", debugstr_w(pszPath));
-       return S_OK;
+    /* Expand environment strings if necessary */
+    if (*szTemp == '%')
+        hr = _SHExpandEnvironmentStrings(szTemp, szBuildPath);
+    else
+        strcpyW(szBuildPath, szTemp);
+    /* Copy the path if it's available before we might return */
+    if (SUCCEEDED(hr) && pszPath)
+        strcpyW(pszPath, szBuildPath);
+
+    if (FAILED(hr)) goto end;
+
+    /* if we don't care about existing directories we are ready */
+    if(nFolder & CSIDL_FLAG_DONT_VERIFY) goto end;
+
+    if (PathFileExistsW(szBuildPath)) goto end;
+
+    /* not existing but we are not allowed to create it.  The return value
+     * is verified against shell32 version 6.0.
+     */
+    if (!(nFolder & CSIDL_FLAG_CREATE))
+    {
+        hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+        goto end;
+    }
+
+    /* create directory/directories */
+    ret = SHCreateDirectoryExW(hwndOwner, szBuildPath, NULL);
+    if (ret && ret != ERROR_ALREADY_EXISTS)
+    {
+        ERR("Failed to create directory '%s'.\n", debugstr_w(szBuildPath));
+        hr = E_FAIL;
+        goto end;
+    }
+
+    TRACE("Created missing system directory '%s'\n", debugstr_w(szBuildPath));
+end:
+    TRACE("returning 0x%08lx (final path is %s)\n", hr, debugstr_w(szBuildPath));
+    return hr;
 }
 
 /*************************************************************************
  * SHGetFolderPathA                    [SHELL32.@]
+ *
+ * See SHGetFolderPathW.
  */
 HRESULT WINAPI SHGetFolderPathA(
        HWND hwndOwner,
-       int csidl,
+       int nFolder,
        HANDLE hToken,
        DWORD dwFlags,
        LPSTR pszPath)
 {
-       WCHAR szTemp[MAX_PATH];
-       HRESULT hr;
+    WCHAR szTemp[MAX_PATH];
+    HRESULT hr;
 
-       hr = SHGetFolderPathW(hwndOwner, csidl, hToken, dwFlags, szTemp);
-       if (hr == S_OK)
-       {
-            if (!WideCharToMultiByte( CP_ACP, 0, szTemp, -1, pszPath, MAX_PATH, NULL, NULL ))
-                pszPath[MAX_PATH - 1] = 0;
+    TRACE("%p,%p,nFolder=0x%04x\n",hwndOwner,pszPath,nFolder);
+
+    if (pszPath)
+        *pszPath = '\0';
+    hr = SHGetFolderPathW(hwndOwner, nFolder, hToken, dwFlags, szTemp);
+    if (SUCCEEDED(hr) && pszPath)
+        WideCharToMultiByte(CP_ACP, 0, szTemp, -1, pszPath, MAX_PATH, NULL,
+         NULL);
+
+    return hr;
+}
+
+/* For each folder in folders, if its value has not been set in the registry,
+ * calls _SHGetUserProfilePath or _SHGetAllUsersProfilePath (depending on the
+ * folder's type) to get the unexpanded value first.
+ * Writes the unexpanded value to User Shell Folders, and queries it with
+ * SHGetFolderPathW to force the creation of the directory if it doesn't
+ * already exist.  SHGetFolderPathW also returns the expanded value, which
+ * this then writes to Shell Folders.
+ */
+static HRESULT _SHRegisterFolders(HKEY hRootKey, HANDLE hToken,
+ LPCWSTR szUserShellFolderPath, LPCWSTR szShellFolderPath, const UINT folders[],
+ UINT foldersLen)
+{
+    UINT i;
+    WCHAR path[MAX_PATH];
+    HRESULT hr = S_OK;
+    HKEY hUserKey = NULL, hKey = NULL;
+    DWORD dwDisp, dwType, dwPathLen;
+    LONG ret;
+
+    TRACE("%p,%p,%s,%p,%u\n", hRootKey, hToken,
+     debugstr_w(szUserShellFolderPath), folders, foldersLen);
+
+    ret = RegCreateKeyExW(hRootKey, szUserShellFolderPath, 0, NULL, 0,
+     KEY_ALL_ACCESS, NULL, &hUserKey, &dwDisp);
+    if (ret)
+        hr = HRESULT_FROM_WIN32(ret);
+    else
+    {
+        ret = RegCreateKeyExW(hRootKey, szShellFolderPath, 0, NULL, 0,
+         KEY_ALL_ACCESS, NULL, &hKey, &dwDisp);
+        if (ret)
+            hr = HRESULT_FROM_WIN32(ret);
+    }
+    for (i = 0; SUCCEEDED(hr) && i < foldersLen; i++)
+    {
+        dwPathLen = MAX_PATH * sizeof(WCHAR);
+        if (RegQueryValueExW(hUserKey, CSIDL_Data[folders[i]].szValueName, NULL,
+         &dwType, (LPBYTE)path, &dwPathLen) || (dwType != REG_SZ &&
+         dwType != REG_EXPAND_SZ))
+        {
+            *path = '\0';
+            if (CSIDL_Data[folders[i]].type == CSIDL_Type_User)
+                _SHGetUserProfilePath(hToken, SHGFP_TYPE_DEFAULT, folders[i],
+                 path);
+            else if (CSIDL_Data[folders[i]].type == CSIDL_Type_AllUsers)
+                _SHGetAllUsersProfilePath(SHGFP_TYPE_DEFAULT, folders[i], path);
+            else
+                hr = E_FAIL;
+            if (*path)
+            {
+                ret = RegSetValueExW(hUserKey,
+                 CSIDL_Data[folders[i]].szValueName, 0, REG_EXPAND_SZ,
+                 (LPBYTE)path, (strlenW(path) + 1) * sizeof(WCHAR));
+                if (ret)
+                    hr = HRESULT_FROM_WIN32(ret);
+                else
+                {
+                    hr = SHGetFolderPathW(NULL, folders[i] | CSIDL_FLAG_CREATE,
+                     hToken, SHGFP_TYPE_DEFAULT, path);
+                    ret = RegSetValueExW(hKey,
+                     CSIDL_Data[folders[i]].szValueName, 0, REG_SZ,
+                     (LPBYTE)path, (strlenW(path) + 1) * sizeof(WCHAR));
+                    if (ret)
+                        hr = HRESULT_FROM_WIN32(ret);
+                }
+            }
         }
+    }
+    if (hUserKey)
+        RegCloseKey(hUserKey);
+    if (hKey)
+        RegCloseKey(hKey);
 
-       TRACE("%p,%p,csidl=0x%04x\n",hwndOwner,pszPath,csidl);
+    TRACE("returning 0x%08lx\n", hr);
+    return hr;
+}
 
-       return hr;
+static HRESULT _SHRegisterUserShellFolders(BOOL bDefault)
+{
+    static const UINT folders[] = {
+     CSIDL_PROGRAMS,
+     CSIDL_PERSONAL,
+     CSIDL_FAVORITES,
+     CSIDL_APPDATA,
+     CSIDL_STARTUP,
+     CSIDL_RECENT,
+     CSIDL_SENDTO,
+     CSIDL_STARTMENU,
+     CSIDL_MYMUSIC,
+     CSIDL_MYVIDEO,
+     CSIDL_DESKTOPDIRECTORY,
+     CSIDL_NETHOOD,
+     CSIDL_TEMPLATES,
+     CSIDL_PRINTHOOD,
+     CSIDL_COOKIES,
+     CSIDL_HISTORY,
+     CSIDL_MYPICTURES
+    };
+    WCHAR userShellFolderPath[MAX_PATH], shellFolderPath[MAX_PATH];
+    LPCWSTR pUserShellFolderPath, pShellFolderPath;
+    HRESULT hr = S_OK;
+    HKEY hRootKey;
+    HANDLE hToken;
+
+    TRACE("%s\n", bDefault ? "TRUE" : "FALSE");
+    if (bDefault)
+    {
+        hToken = (HANDLE)-1;
+        hRootKey = HKEY_USERS;
+        strcpyW(userShellFolderPath, DefaultW);
+        PathAddBackslashW(userShellFolderPath);
+        strcatW(userShellFolderPath, szSHUserFolders);
+        pUserShellFolderPath = userShellFolderPath;
+        strcpyW(shellFolderPath, DefaultW);
+        PathAddBackslashW(shellFolderPath);
+        strcatW(shellFolderPath, szSHFolders);
+        pShellFolderPath = shellFolderPath;
+    }
+    else
+    {
+        hToken = NULL;
+        hRootKey = HKEY_CURRENT_USER;
+        pUserShellFolderPath = szSHUserFolders;
+        pShellFolderPath = szSHFolders;
+    }
+
+    hr = _SHRegisterFolders(hRootKey, hToken, pUserShellFolderPath,
+     pShellFolderPath, folders, sizeof(folders) / sizeof(folders[0]));
+    TRACE("returning 0x%08lx\n", hr);
+    return hr;
+}
+
+static HRESULT _SHRegisterCommonShellFolders(void)
+{
+    static const UINT folders[] = {
+     CSIDL_COMMON_STARTMENU,
+     CSIDL_COMMON_PROGRAMS,
+     CSIDL_COMMON_STARTUP,
+     CSIDL_COMMON_DESKTOPDIRECTORY,
+     CSIDL_COMMON_FAVORITES,
+     CSIDL_COMMON_APPDATA,
+     CSIDL_COMMON_TEMPLATES,
+     CSIDL_COMMON_DOCUMENTS,
+    };
+    HRESULT hr;
+
+    TRACE("\n");
+    hr = _SHRegisterFolders(HKEY_LOCAL_MACHINE, NULL, szSHUserFolders,
+     szSHFolders, folders, sizeof(folders) / sizeof(folders[0]));
+    TRACE("returning 0x%08lx\n", hr);
+    return hr;
+}
+
+/* Register the default values in the registry, as some apps seem to depend
+ * on their presence.  The set registered was taken from Windows XP.
+ */
+HRESULT SHELL_RegisterShellFolders(void)
+{
+    HRESULT hr = _SHRegisterUserShellFolders(TRUE);
+
+    if (SUCCEEDED(hr))
+        hr = _SHRegisterUserShellFolders(FALSE);
+    if (SUCCEEDED(hr))
+        hr = _SHRegisterCommonShellFolders();
+    return hr;
 }
 
 /*************************************************************************
@@ -1186,12 +1893,12 @@ HRESULT WINAPI SHGetFolderPathA(
 BOOL WINAPI SHGetSpecialFolderPathA (
        HWND hwndOwner,
        LPSTR szPath,
-       int csidl,
+       int nFolder,
        BOOL bCreate)
 {
        return (SHGetFolderPathA(
                hwndOwner,
-               csidl + (bCreate ? CSIDL_FLAG_CREATE : 0),
+               nFolder + (bCreate ? CSIDL_FLAG_CREATE : 0),
                NULL,
                0,
                szPath)) == S_OK ? TRUE : FALSE;
@@ -1203,12 +1910,12 @@ BOOL WINAPI SHGetSpecialFolderPathA (
 BOOL WINAPI SHGetSpecialFolderPathW (
        HWND hwndOwner,
        LPWSTR szPath,
-       int csidl,
+       int nFolder,
        BOOL bCreate)
 {
        return (SHGetFolderPathW(
                hwndOwner,
-               csidl + (bCreate ? CSIDL_FLAG_CREATE : 0),
+               nFolder + (bCreate ? CSIDL_FLAG_CREATE : 0),
                NULL,
                0,
                szPath)) == S_OK ? TRUE : FALSE;
@@ -1220,11 +1927,138 @@ BOOL WINAPI SHGetSpecialFolderPathW (
 BOOL WINAPI SHGetSpecialFolderPathAW (
        HWND hwndOwner,
        LPVOID szPath,
-       int csidl,
+       int nFolder,
        BOOL bCreate)
 
 {
        if (SHELL_OsIsUnicode())
-         return SHGetSpecialFolderPathW (hwndOwner, szPath, csidl, bCreate);
-       return SHGetSpecialFolderPathA (hwndOwner, szPath, csidl, bCreate);
+         return SHGetSpecialFolderPathW (hwndOwner, szPath, nFolder, bCreate);
+       return SHGetSpecialFolderPathA (hwndOwner, szPath, nFolder, bCreate);
+}
+
+/*************************************************************************
+ * SHGetFolderLocation [SHELL32.@]
+ *
+ * Gets the folder locations from the registry and creates a pidl.
+ *
+ * PARAMS
+ *   hwndOwner  [I]
+ *   nFolder    [I] CSIDL_xxxxx
+ *   hToken     [I] token representing user, or NULL for current user, or -1 for
+ *                  default user
+ *   dwReserved [I] must be zero
+ *   ppidl      [O] PIDL of a special folder
+ *
+ * RETURNS
+ *  Success: S_OK
+ *  Failure: Standard OLE-defined error result, S_FALSE or E_INVALIDARG
+ *
+ * NOTES
+ *  Creates missing reg keys and directories.
+ *  Mostly forwards to SHGetFolderPathW, but a few values of nFolder return
+ *  virtual folders that are handled here.
+ */
+HRESULT WINAPI SHGetFolderLocation(
+       HWND hwndOwner,
+       int nFolder,
+       HANDLE hToken,
+       DWORD dwReserved,
+       LPITEMIDLIST *ppidl)
+{
+    HRESULT hr = E_INVALIDARG;
+
+    TRACE("%p 0x%08x %p 0x%08lx %p\n",
+     hwndOwner, nFolder, hToken, dwReserved, ppidl);
+    
+    if (!ppidl)
+        return E_INVALIDARG;
+    if (dwReserved)
+        return E_INVALIDARG;
+
+    /* The virtual folders' locations are not user-dependent */
+    *ppidl = NULL;
+    switch (nFolder)
+    {
+        case CSIDL_DESKTOP:
+            *ppidl = _ILCreateDesktop();
+            break;
+
+        case CSIDL_INTERNET:
+            *ppidl = _ILCreateIExplore();
+            break;
+
+        case CSIDL_CONTROLS:
+            *ppidl = _ILCreateControlPanel();
+            break;
+
+        case CSIDL_PRINTERS:
+            *ppidl = _ILCreatePrinters();
+            break;
+
+        case CSIDL_FONTS:
+            FIXME("virtual font folder");
+            break;
+
+        case CSIDL_BITBUCKET:
+            *ppidl = _ILCreateBitBucket();
+            break;
+
+        case CSIDL_DRIVES:
+            *ppidl = _ILCreateMyComputer();
+            break;
+
+        case CSIDL_NETWORK:
+            *ppidl = _ILCreateNetwork();
+            break;
+
+        default:
+        {
+            WCHAR szPath[MAX_PATH];
+
+            hr = SHGetFolderPathW(hwndOwner, nFolder, hToken,
+             SHGFP_TYPE_CURRENT, szPath);
+            if (SUCCEEDED(hr))
+            {
+                DWORD attributes=0;
+
+                TRACE("Value=%s\n", debugstr_w(szPath));
+                hr = SHILCreateFromPathW(szPath, ppidl, &attributes);
+            }
+            else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
+            {
+                /* unlike SHGetFolderPath, SHGetFolderLocation in shell32
+                 * version 6.0 returns E_FAIL for nonexistent paths
+                 */
+                hr = E_FAIL;
+            }
+        }
+    }
+    if(*ppidl)
+        hr = NOERROR;
+
+    TRACE("-- (new pidl %p)\n",*ppidl);
+    return hr;
+}
+
+/*************************************************************************
+ * SHGetSpecialFolderLocation          [SHELL32.@]
+ *
+ * NOTES
+ *   In NT5, SHGetSpecialFolderLocation needs the <winntdir>/Recent
+ *   directory.
+ */
+HRESULT WINAPI SHGetSpecialFolderLocation(
+       HWND hwndOwner,
+       INT nFolder,
+       LPITEMIDLIST * ppidl)
+{
+    HRESULT hr = E_INVALIDARG;
+
+    TRACE("(%p,0x%x,%p)\n", hwndOwner,nFolder,ppidl);
+
+    if (!ppidl)
+        return E_INVALIDARG;
+
+    hr = SHGetFolderLocation(hwndOwner, nFolder, NULL, 0, ppidl);
+    return hr;
 }