Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Windows support routines for PhysicsFS.
  3.  *
  4.  * Please see the file LICENSE.txt in the source's root directory.
  5.  *
  6.  *  This file written by Ryan C. Gordon, and made sane by Gregory S. Read.
  7.  */
  8.  
  9. #define __PHYSICSFS_INTERNAL__
  10. #include "physfs_platforms.h"
  11.  
  12. #ifdef PHYSFS_PLATFORM_WINDOWS
  13.  
  14. /* Forcibly disable UNICODE macro, since we manage this ourselves. */
  15. #ifdef UNICODE
  16. #undef UNICODE
  17. #endif
  18.  
  19. #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
  20. #define _CRT_SECURE_NO_WARNINGS 1
  21. #endif
  22.  
  23. #define WIN32_LEAN_AND_MEAN 1
  24. #include <windows.h>
  25.  
  26. #ifndef PHYSFS_PLATFORM_WINRT
  27. #include <userenv.h>
  28. #include <shlobj.h>
  29. #endif
  30.  
  31. #if !defined(PHYSFS_NO_CDROM_SUPPORT)
  32. #include <dbt.h>
  33. #endif
  34.  
  35. #include <errno.h>
  36. #include <ctype.h>
  37. #include <time.h>
  38.  
  39. #ifdef allocator  /* apparently Windows 10 SDK conflicts here. */
  40. #undef allocator
  41. #endif
  42.  
  43. #include "physfs_internal.h"
  44.  
  45. /*
  46.  * Users without the platform SDK don't have this defined.  The original docs
  47.  *  for SetFilePointer() just said to compare with 0xFFFFFFFF, so this should
  48.  *  work as desired.
  49.  */
  50. #define PHYSFS_INVALID_SET_FILE_POINTER  0xFFFFFFFF
  51.  
  52. /* just in case... */
  53. #define PHYSFS_INVALID_FILE_ATTRIBUTES   0xFFFFFFFF
  54.  
  55. /* Not defined before the Vista SDK. */
  56. #define PHYSFS_FILE_ATTRIBUTE_REPARSE_POINT 0x400
  57. #define PHYSFS_IO_REPARSE_TAG_SYMLINK    0xA000000C
  58.  
  59.  
  60. #define UTF8_TO_UNICODE_STACK(w_assignto, str) { \
  61.     if (str == NULL) \
  62.         w_assignto = NULL; \
  63.     else { \
  64.         const size_t len = (PHYSFS_uint64) ((strlen(str) + 1) * 2); \
  65.         w_assignto = (WCHAR *) __PHYSFS_smallAlloc(len); \
  66.         if (w_assignto != NULL) \
  67.             PHYSFS_utf8ToUtf16(str, (PHYSFS_uint16 *) w_assignto, len); \
  68.     } \
  69. } \
  70.  
  71. /* Note this counts WCHARs, not codepoints! */
  72. static PHYSFS_uint64 wStrLen(const WCHAR *wstr)
  73. {
  74.     PHYSFS_uint64 len = 0;
  75.     while (*(wstr++))
  76.         len++;
  77.     return len;
  78. } /* wStrLen */
  79.  
  80. static char *unicodeToUtf8Heap(const WCHAR *w_str)
  81. {
  82.     char *retval = NULL;
  83.     if (w_str != NULL)
  84.     {
  85.         void *ptr = NULL;
  86.         const PHYSFS_uint64 len = (wStrLen(w_str) * 4) + 1;
  87.         retval = allocator.Malloc(len);
  88.         BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
  89.         PHYSFS_utf8FromUtf16((const PHYSFS_uint16 *) w_str, retval, len);
  90.         ptr = allocator.Realloc(retval, strlen(retval) + 1); /* shrink. */
  91.         if (ptr != NULL)
  92.             retval = (char *) ptr;
  93.     } /* if */
  94.     return retval;
  95. } /* unicodeToUtf8Heap */
  96.  
  97.  
  98. /* Some older APIs aren't in WinRT (only the "Ex" version, etc).
  99.    Since non-WinRT might not have the "Ex" version, we tapdance to use
  100.    the perfectly-fine-and-available-even-on-Win95 API on non-WinRT targets. */
  101.  
  102. static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d)
  103. {
  104.     #ifdef PHYSFS_PLATFORM_WINRT
  105.     return FindFirstFileExW(path, FindExInfoStandard, d,
  106.                             FindExSearchNameMatch, NULL, 0);
  107.     #else
  108.     return FindFirstFileW(path, d);
  109.     #endif
  110. } /* winFindFirstFileW */
  111.  
  112. static inline BOOL winInitializeCriticalSection(LPCRITICAL_SECTION lpcs)
  113. {
  114.     #ifdef PHYSFS_PLATFORM_WINRT
  115.     return InitializeCriticalSectionEx(lpcs, 2000, 0);
  116.     #else
  117.     InitializeCriticalSection(lpcs);
  118.     return TRUE;
  119.     #endif
  120. } /* winInitializeCriticalSection */
  121.  
  122. static inline HANDLE winCreateFileW(const WCHAR *wfname, const DWORD mode,
  123.                                     const DWORD creation)
  124. {
  125.     const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE;
  126.     #ifdef PHYSFS_PLATFORM_WINRT
  127.     return CreateFile2(wfname, mode, share, creation, NULL);
  128.     #else
  129.     return CreateFileW(wfname, mode, share, NULL, creation,
  130.                        FILE_ATTRIBUTE_NORMAL, NULL);
  131.     #endif
  132. } /* winCreateFileW */
  133.  
  134. static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
  135.                               PHYSFS_sint64 *_newpos, const DWORD whence)
  136. {
  137.     #ifdef PHYSFS_PLATFORM_WINRT
  138.     LARGE_INTEGER lipos;
  139.     LARGE_INTEGER linewpos;
  140.     BOOL rc;
  141.     lipos.QuadPart = (LONGLONG) pos;
  142.     rc = SetFilePointerEx(h, lipos, &linewpos, whence);
  143.     if (_newpos)
  144.         *_newpos = (PHYSFS_sint64) linewpos.QuadPart;
  145.     return rc;
  146.     #else
  147.     const LONG low = (LONG) (pos & 0xFFFFFFFF);
  148.     LONG high = (LONG) ((pos >> 32) & 0xFFFFFFFF);
  149.     const DWORD rc = SetFilePointer(h, low, &high, whence);
  150.     /* 0xFFFFFFFF could be valid, so you have to check GetLastError too! */
  151.     if (_newpos)
  152.         *_newpos = ((PHYSFS_sint64) rc) | (((PHYSFS_sint64) high) << 32);
  153.     if ((rc == PHYSFS_INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR))
  154.         return FALSE;
  155.     return TRUE;
  156.     #endif
  157. } /* winSetFilePointer */
  158.  
  159. static PHYSFS_sint64 winGetFileSize(HANDLE h)
  160. {
  161.     #ifdef PHYSFS_PLATFORM_WINRT
  162.     FILE_STANDARD_INFO info;
  163.     const BOOL rc = GetFileInformationByHandleEx(h, FileStandardInfo,
  164.                                                  &info, sizeof (info));
  165.     return rc ? (PHYSFS_sint64) info.EndOfFile.QuadPart : -1;
  166.     #else
  167.     DWORD high = 0;
  168.     const DWORD rc = GetFileSize(h, &high);
  169.     if ((rc == PHYSFS_INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR))
  170.         return -1;
  171.     return (PHYSFS_sint64) ((((PHYSFS_uint64) high) << 32) | rc);
  172.     #endif
  173. } /* winGetFileSize */
  174.  
  175.  
  176. static PHYSFS_ErrorCode errcodeFromWinApiError(const DWORD err)
  177. {
  178.     /*
  179.      * win32 error codes are sort of a tricky thing; Microsoft intentionally
  180.      *  doesn't list which ones a given API might trigger, there are several
  181.      *  with overlapping and unclear meanings...and there's 16 thousand of
  182.      *  them in Windows 7. It looks like the ones we care about are in the
  183.      *  first 500, but I can't say this list is perfect; we might miss
  184.      *  important values or misinterpret others.
  185.      *
  186.      * Don't treat this list as anything other than a work in progress.
  187.      */
  188.     switch (err)
  189.     {
  190.         case ERROR_SUCCESS: return PHYSFS_ERR_OK;
  191.         case ERROR_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION;
  192.         case ERROR_NETWORK_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION;
  193.         case ERROR_NOT_READY: return PHYSFS_ERR_IO;
  194.         case ERROR_CRC: return PHYSFS_ERR_IO;
  195.         case ERROR_SEEK: return PHYSFS_ERR_IO;
  196.         case ERROR_SECTOR_NOT_FOUND: return PHYSFS_ERR_IO;
  197.         case ERROR_NOT_DOS_DISK: return PHYSFS_ERR_IO;
  198.         case ERROR_WRITE_FAULT: return PHYSFS_ERR_IO;
  199.         case ERROR_READ_FAULT: return PHYSFS_ERR_IO;
  200.         case ERROR_DEV_NOT_EXIST: return PHYSFS_ERR_IO;
  201.         case ERROR_BUFFER_OVERFLOW: return PHYSFS_ERR_BAD_FILENAME;
  202.         case ERROR_INVALID_NAME: return PHYSFS_ERR_BAD_FILENAME;
  203.         case ERROR_BAD_PATHNAME: return PHYSFS_ERR_BAD_FILENAME;
  204.         case ERROR_DIRECTORY: return PHYSFS_ERR_BAD_FILENAME;
  205.         case ERROR_FILE_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND;
  206.         case ERROR_PATH_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND;
  207.         case ERROR_DELETE_PENDING: return PHYSFS_ERR_NOT_FOUND;
  208.         case ERROR_INVALID_DRIVE: return PHYSFS_ERR_NOT_FOUND;
  209.         case ERROR_HANDLE_DISK_FULL: return PHYSFS_ERR_NO_SPACE;
  210.         case ERROR_DISK_FULL: return PHYSFS_ERR_NO_SPACE;
  211.         case ERROR_WRITE_PROTECT: return PHYSFS_ERR_READ_ONLY;
  212.         case ERROR_LOCK_VIOLATION: return PHYSFS_ERR_BUSY;
  213.         case ERROR_SHARING_VIOLATION: return PHYSFS_ERR_BUSY;
  214.         case ERROR_CURRENT_DIRECTORY: return PHYSFS_ERR_BUSY;
  215.         case ERROR_DRIVE_LOCKED: return PHYSFS_ERR_BUSY;
  216.         case ERROR_PATH_BUSY: return PHYSFS_ERR_BUSY;
  217.         case ERROR_BUSY: return PHYSFS_ERR_BUSY;
  218.         case ERROR_NOT_ENOUGH_MEMORY: return PHYSFS_ERR_OUT_OF_MEMORY;
  219.         case ERROR_OUTOFMEMORY: return PHYSFS_ERR_OUT_OF_MEMORY;
  220.         case ERROR_DIR_NOT_EMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY;
  221.         default: return PHYSFS_ERR_OS_ERROR;
  222.     } /* switch */
  223. } /* errcodeFromWinApiError */
  224.  
  225. static inline PHYSFS_ErrorCode errcodeFromWinApi(void)
  226. {
  227.     return errcodeFromWinApiError(GetLastError());
  228. } /* errcodeFromWinApi */
  229.  
  230.  
  231. #if defined(PHYSFS_NO_CDROM_SUPPORT)
  232. #define detectAvailableCDs(cb, data)
  233. #define deinitCDThread()
  234. #else
  235. static HANDLE detectCDThreadHandle = NULL;
  236. static HWND detectCDHwnd = NULL;
  237. static volatile DWORD drivesWithMediaBitmap = 0;
  238.  
  239. typedef BOOL (WINAPI *fnSTEM)(DWORD, LPDWORD b);
  240.  
  241. static DWORD pollDiscDrives(void)
  242. {
  243.     /* Try to use SetThreadErrorMode(), which showed up in Windows 7. */
  244.     HANDLE lib = LoadLibraryA("kernel32.dll");
  245.     fnSTEM stem = NULL;
  246.     char drive[4] = { 'x', ':', '\\', '\0' };
  247.     DWORD oldErrorMode = 0;
  248.     DWORD drives = 0;
  249.     DWORD i;
  250.  
  251.     if (lib)
  252.         stem = (fnSTEM) GetProcAddress(lib, "SetThreadErrorMode");
  253.  
  254.     if (stem)
  255.         stem(SEM_FAILCRITICALERRORS, &oldErrorMode);
  256.     else
  257.         oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
  258.    
  259.     /* Do detection. This may block if a disc is spinning up. */
  260.     for (i = 'A'; i <= 'Z'; i++)
  261.     {
  262.         DWORD tmp = 0;
  263.         drive[0] = (char) i;
  264.         if (GetDriveTypeA(drive) != DRIVE_CDROM)
  265.             continue;
  266.  
  267.         /* If this function succeeds, there's media in the drive */
  268.         if (GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0))
  269.             drives |= (1 << (i - 'A'));
  270.     } /* for */
  271.  
  272.     if (stem)
  273.         stem(oldErrorMode, NULL);
  274.     else
  275.         SetErrorMode(oldErrorMode);
  276.  
  277.     if (lib)
  278.         FreeLibrary(lib);
  279.  
  280.     return drives;
  281. } /* pollDiscDrives */
  282.  
  283.  
  284. static LRESULT CALLBACK detectCDWndProc(HWND hwnd, UINT msg,
  285.                                         WPARAM wp, LPARAM lparam)
  286. {
  287.     PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR) lparam;
  288.     PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME) lparam;
  289.     const int removed = (wp == DBT_DEVICEREMOVECOMPLETE);
  290.  
  291.     if (msg == WM_DESTROY)
  292.         return 0;
  293.     else if ((msg != WM_DEVICECHANGE) ||
  294.              ((wp != DBT_DEVICEARRIVAL) && (wp != DBT_DEVICEREMOVECOMPLETE)) ||
  295.              (lpdb->dbch_devicetype != DBT_DEVTYP_VOLUME) ||
  296.              ((lpdbv->dbcv_flags & DBTF_MEDIA) == 0))
  297.     {
  298.         return DefWindowProcW(hwnd, msg, wp, lparam);
  299.     } /* else if */
  300.  
  301.     if (removed)
  302.         drivesWithMediaBitmap &= ~lpdbv->dbcv_unitmask;
  303.     else
  304.         drivesWithMediaBitmap |= lpdbv->dbcv_unitmask;
  305.  
  306.     return TRUE;
  307. } /* detectCDWndProc */
  308.  
  309.  
  310. static DWORD WINAPI detectCDThread(LPVOID arg)
  311. {
  312.     HANDLE initialDiscDetectionComplete = *((HANDLE *) arg);
  313.     const char *classname = "PhysicsFSDetectCDCatcher";
  314.     const char *winname = "PhysicsFSDetectCDMsgWindow";
  315.     HINSTANCE hInstance = GetModuleHandleW(NULL);
  316.     ATOM class_atom = 0;
  317.     WNDCLASSEXA wce;
  318.     MSG msg;
  319.  
  320.     memset(&wce, '\0', sizeof (wce));
  321.     wce.cbSize = sizeof (wce);
  322.     wce.lpfnWndProc = detectCDWndProc;
  323.     wce.lpszClassName = classname;
  324.     wce.hInstance = hInstance;
  325.     class_atom = RegisterClassExA(&wce);
  326.     if (class_atom == 0)
  327.     {
  328.         SetEvent(initialDiscDetectionComplete);  /* let main thread go on. */
  329.         return 0;
  330.     } /* if */
  331.  
  332.     detectCDHwnd = CreateWindowExA(0, classname, winname, WS_OVERLAPPEDWINDOW,
  333.                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  334.                         CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
  335.  
  336.     if (detectCDHwnd == NULL)
  337.     {
  338.         SetEvent(initialDiscDetectionComplete);  /* let main thread go on. */
  339.         UnregisterClassA(classname, hInstance);
  340.         return 0;
  341.     } /* if */
  342.  
  343.     /* We'll get events when discs come and go from now on. */
  344.  
  345.     /* Do initial detection, possibly blocking awhile... */
  346.     drivesWithMediaBitmap = pollDiscDrives();
  347.  
  348.     SetEvent(initialDiscDetectionComplete);  /* let main thread go on. */
  349.  
  350.     do
  351.     {
  352.         const BOOL rc = GetMessageW(&msg, detectCDHwnd, 0, 0);
  353.         if ((rc == 0) || (rc == -1))
  354.             break;  /* don't care if WM_QUIT or error break this loop. */
  355.         TranslateMessage(&msg);
  356.         DispatchMessageW(&msg);
  357.     } while (1);
  358.  
  359.     /* we've been asked to quit. */
  360.     DestroyWindow(detectCDHwnd);
  361.     UnregisterClassA(classname, hInstance);
  362.     return 0;
  363. } /* detectCDThread */
  364.  
  365. static void detectAvailableCDs(PHYSFS_StringCallback cb, void *data)
  366. {
  367.     char drive_str[4] = { 'x', ':', '\\', '\0' };
  368.     DWORD drives = 0;
  369.     DWORD i;
  370.  
  371.     /*
  372.      * If you poll a drive while a user is inserting a disc, the OS will
  373.      *  block this thread until the drive has spun up. So we swallow the risk
  374.      *  once for initial detection, and spin a thread that will get device
  375.      *  events thereafter, for apps that use this interface to poll for
  376.      *  disc insertion.
  377.      */
  378.     if (!detectCDThreadHandle)
  379.     {
  380.         HANDLE initialDetectDone = CreateEvent(NULL, TRUE, FALSE, NULL);
  381.         if (!initialDetectDone)
  382.             return;  /* oh well. */
  383.  
  384.         detectCDThreadHandle = CreateThread(NULL, 0, detectCDThread,
  385.                                             &initialDetectDone, 0, NULL);
  386.         if (detectCDThreadHandle)
  387.             WaitForSingleObject(initialDetectDone, INFINITE);
  388.         CloseHandle(initialDetectDone);
  389.  
  390.         if (!detectCDThreadHandle)
  391.             return;  /* oh well. */
  392.     } /* if */
  393.  
  394.     drives = drivesWithMediaBitmap; /* whatever the thread has seen, we take. */
  395.     for (i = 'A'; i <= 'Z'; i++)
  396.     {
  397.         if (drives & (1 << (i - 'A')))
  398.         {
  399.             drive_str[0] = (char) i;
  400.             cb(data, drive_str);
  401.         } /* if */
  402.     } /* for */
  403. } /* detectAvailableCDs */
  404.  
  405. static void deinitCDThread(void)
  406. {
  407.     if (detectCDThreadHandle)
  408.     {
  409.         if (detectCDHwnd)
  410.             PostMessageW(detectCDHwnd, WM_QUIT, 0, 0);
  411.         CloseHandle(detectCDThreadHandle);
  412.         detectCDThreadHandle = NULL;
  413.         drivesWithMediaBitmap = 0;
  414.     } /* if */
  415. } /* deinitCDThread */
  416. #endif
  417.  
  418.  
  419. void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
  420. {
  421.     detectAvailableCDs(cb, data);
  422. } /* __PHYSFS_platformDetectAvailableCDs */
  423.  
  424. #ifdef PHYSFS_PLATFORM_WINRT
  425. static char *calcDirAppendSep(const WCHAR *wdir)
  426. {
  427.     size_t len;
  428.     void *ptr;
  429.     char *retval;
  430.     BAIL_IF(!wdir, errcodeFromWinApi(), NULL);
  431.     retval = unicodeToUtf8Heap(wdir);
  432.     BAIL_IF_ERRPASS(!retval, NULL);
  433.     len = strlen(retval);
  434.     ptr = allocator.Realloc(retval, len + 2);
  435.     if (!ptr)
  436.     {
  437.         allocator.Free(retval);
  438.         BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
  439.     } /* if */
  440.     retval = (char *) ptr;
  441.     retval[len] = '\\';
  442.     retval[len+1] = '\0';
  443.     return retval;
  444. } /* calcDirAppendSep */
  445. #endif
  446.  
  447. char *__PHYSFS_platformCalcBaseDir(const char *argv0)
  448. {
  449. #ifdef PHYSFS_PLATFORM_WINRT
  450.     return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcBaseDir());
  451. #else
  452.     char *retval = NULL;
  453.     DWORD buflen = 64;
  454.     LPWSTR modpath = NULL;
  455.  
  456.     while (1)
  457.     {
  458.         DWORD rc;
  459.         void *ptr;
  460.  
  461.         if ( (ptr = allocator.Realloc(modpath, buflen*sizeof(WCHAR))) == NULL )
  462.         {
  463.             allocator.Free(modpath);
  464.             BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
  465.         } /* if */
  466.         modpath = (LPWSTR) ptr;
  467.  
  468.         rc = GetModuleFileNameW(NULL, modpath, buflen);
  469.         if (rc == 0)
  470.         {
  471.             allocator.Free(modpath);
  472.             BAIL(errcodeFromWinApi(), NULL);
  473.         } /* if */
  474.  
  475.         if (rc < buflen)
  476.         {
  477.             buflen = rc;
  478.             break;
  479.         } /* if */
  480.  
  481.         buflen *= 2;
  482.     } /* while */
  483.  
  484.     if (buflen > 0)  /* just in case... */
  485.     {
  486.         WCHAR *ptr = (modpath + buflen) - 1;
  487.         while (ptr != modpath)
  488.         {
  489.             if (*ptr == '\\')
  490.                 break;
  491.             ptr--;
  492.         } /* while */
  493.  
  494.         if ((ptr == modpath) && (*ptr != '\\'))
  495.             PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR);  /* oh well. */
  496.         else
  497.         {
  498.             *(ptr+1) = '\0';  /* chop off filename. */
  499.             retval = unicodeToUtf8Heap(modpath);
  500.         } /* else */
  501.     } /* else */
  502.     allocator.Free(modpath);
  503.  
  504.     return retval;   /* w00t. */
  505. #endif
  506. } /* __PHYSFS_platformCalcBaseDir */
  507.  
  508.  
  509. char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
  510. {
  511. #ifdef PHYSFS_PLATFORM_WINRT
  512.     return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcPrefDir());
  513. #else
  514.     /*
  515.      * Vista and later has a new API for this, but SHGetFolderPath works there,
  516.      *  and apparently just wraps the new API. This is the new way to do it:
  517.      *
  518.      *     SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE,
  519.      *                          NULL, &wszPath);
  520.      */
  521.  
  522.     WCHAR path[MAX_PATH];
  523.     char *utf8 = NULL;
  524.     size_t len = 0;
  525.     char *retval = NULL;
  526.  
  527.     if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE,
  528.                                    NULL, 0, path)))
  529.         BAIL(PHYSFS_ERR_OS_ERROR, NULL);
  530.  
  531.     utf8 = unicodeToUtf8Heap(path);
  532.     BAIL_IF_ERRPASS(!utf8, NULL);
  533.     len = strlen(utf8) + strlen(org) + strlen(app) + 4;
  534.     retval = allocator.Malloc(len);
  535.     if (!retval)
  536.     {
  537.         allocator.Free(utf8);
  538.         BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
  539.     } /* if */
  540.  
  541.     snprintf(retval, len, "%s\\%s\\%s\\", utf8, org, app);
  542.     allocator.Free(utf8);
  543.     return retval;
  544. #endif
  545. } /* __PHYSFS_platformCalcPrefDir */
  546.  
  547.  
  548. char *__PHYSFS_platformCalcUserDir(void)
  549. {
  550. #ifdef PHYSFS_PLATFORM_WINRT
  551.     return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcPrefDir());
  552. #else
  553.     typedef BOOL (WINAPI *fnGetUserProfDirW)(HANDLE, LPWSTR, LPDWORD);
  554.     fnGetUserProfDirW pGetDir = NULL;
  555.     HANDLE lib = NULL;
  556.     HANDLE accessToken = NULL;       /* Security handle to process */
  557.     char *retval = NULL;
  558.  
  559.     lib = LoadLibraryA("userenv.dll");
  560.     BAIL_IF(!lib, errcodeFromWinApi(), NULL);
  561.     pGetDir=(fnGetUserProfDirW) GetProcAddress(lib,"GetUserProfileDirectoryW");
  562.     GOTO_IF(!pGetDir, errcodeFromWinApi(), done);
  563.  
  564.     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &accessToken))
  565.         GOTO(errcodeFromWinApi(), done);
  566.     else
  567.     {
  568.         DWORD psize = 0;
  569.         LPWSTR wstr = NULL;
  570.         BOOL rc = 0;
  571.  
  572.         /*
  573.          * Should fail. Will write the size of the profile path in
  574.          *  psize. Also note that the second parameter can't be
  575.          *  NULL or the function fails on Windows XP, but has to be NULL on
  576.          *  Windows 10 or it will fail.  :(
  577.          */
  578.         rc = pGetDir(accessToken, NULL, &psize);
  579.         GOTO_IF(rc, PHYSFS_ERR_OS_ERROR, done);  /* should have failed! */
  580.  
  581.         if (psize == 0)  /* probably on Windows XP, try a different way. */
  582.         {
  583.             WCHAR x = 0;
  584.             rc = pGetDir(accessToken, &x, &psize);
  585.             GOTO_IF(rc, PHYSFS_ERR_OS_ERROR, done);  /* should have failed! */
  586.             GOTO_IF(!psize, PHYSFS_ERR_OS_ERROR, done);  /* Uhoh... */
  587.         } /* if */
  588.  
  589.         /* Allocate memory for the profile directory */
  590.         wstr = (LPWSTR) __PHYSFS_smallAlloc((psize + 1) * sizeof (WCHAR));
  591.         if (wstr != NULL)
  592.         {
  593.             if (pGetDir(accessToken, wstr, &psize))
  594.             {
  595.                 /* Make sure it ends in a dirsep. We allocated +1 for this. */
  596.                 if (wstr[psize - 2] != '\\')
  597.                 {
  598.                     wstr[psize - 1] = '\\';
  599.                     wstr[psize - 0] = '\0';
  600.                 } /* if */
  601.                 retval = unicodeToUtf8Heap(wstr);
  602.             } /* if */
  603.             __PHYSFS_smallFree(wstr);
  604.         } /* if */
  605.     } /* if */
  606.  
  607. done:
  608.     if (accessToken)
  609.         CloseHandle(accessToken);
  610.     FreeLibrary(lib);
  611.     return retval;  /* We made it: hit the showers. */
  612. #endif
  613. } /* __PHYSFS_platformCalcUserDir */
  614.  
  615.  
  616. int __PHYSFS_platformInit(void)
  617. {
  618.     return 1;  /* It's all good */
  619. } /* __PHYSFS_platformInit */
  620.  
  621.  
  622. void __PHYSFS_platformDeinit(void)
  623. {
  624.     deinitCDThread();
  625. } /* __PHYSFS_platformDeinit */
  626.  
  627.  
  628. void *__PHYSFS_platformGetThreadID(void)
  629. {
  630.     return ( (void *) ((size_t) GetCurrentThreadId()) );
  631. } /* __PHYSFS_platformGetThreadID */
  632.  
  633.  
  634. PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
  635.                                PHYSFS_EnumerateCallback callback,
  636.                                const char *origdir, void *callbackdata)
  637. {
  638.     PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
  639.     HANDLE dir = INVALID_HANDLE_VALUE;
  640.     WIN32_FIND_DATAW entw;
  641.     size_t len = strlen(dirname);
  642.     char *searchPath = NULL;
  643.     WCHAR *wSearchPath = NULL;
  644.  
  645.     /* Allocate a new string for path, maybe '\\', "*", and NULL terminator */
  646.     searchPath = (char *) __PHYSFS_smallAlloc(len + 3);
  647.     BAIL_IF(!searchPath, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR);
  648.  
  649.     /* Copy current dirname */
  650.     strcpy(searchPath, dirname);
  651.  
  652.     /* if there's no '\\' at the end of the path, stick one in there. */
  653.     if (searchPath[len - 1] != '\\')
  654.     {
  655.         searchPath[len++] = '\\';
  656.         searchPath[len] = '\0';
  657.     } /* if */
  658.  
  659.     /* Append the "*" to the end of the string */
  660.     strcat(searchPath, "*");
  661.  
  662.     UTF8_TO_UNICODE_STACK(wSearchPath, searchPath);
  663.     __PHYSFS_smallFree(searchPath);
  664.     BAIL_IF_ERRPASS(!wSearchPath, PHYSFS_ENUM_ERROR);
  665.  
  666.     dir = winFindFirstFileW(wSearchPath, &entw);
  667.     __PHYSFS_smallFree(wSearchPath);
  668.     BAIL_IF(dir==INVALID_HANDLE_VALUE, errcodeFromWinApi(), PHYSFS_ENUM_ERROR);
  669.  
  670.     do
  671.     {
  672.         const WCHAR *fn = entw.cFileName;
  673.         char *utf8;
  674.  
  675.         if (fn[0] == '.')  /* ignore "." and ".." */
  676.         {
  677.             if ((fn[1] == '\0') || ((fn[1] == '.') && (fn[2] == '\0')))
  678.                 continue;
  679.         } /* if */
  680.  
  681.         utf8 = unicodeToUtf8Heap(fn);
  682.         if (utf8 == NULL)
  683.             retval = -1;
  684.         else
  685.         {
  686.             retval = callback(callbackdata, origdir, utf8);
  687.             allocator.Free(utf8);
  688.             if (retval == PHYSFS_ENUM_ERROR)
  689.                 PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
  690.         } /* else */
  691.     } while ((retval == PHYSFS_ENUM_OK) && (FindNextFileW(dir, &entw) != 0));
  692.  
  693.     FindClose(dir);
  694.  
  695.     return retval;
  696. } /* __PHYSFS_platformEnumerate */
  697.  
  698.  
  699. int __PHYSFS_platformMkDir(const char *path)
  700. {
  701.     WCHAR *wpath;
  702.     DWORD rc;
  703.     UTF8_TO_UNICODE_STACK(wpath, path);
  704.     rc = CreateDirectoryW(wpath, NULL);
  705.     __PHYSFS_smallFree(wpath);
  706.     BAIL_IF(rc == 0, errcodeFromWinApi(), 0);
  707.     return 1;
  708. } /* __PHYSFS_platformMkDir */
  709.  
  710.  
  711. static HANDLE doOpen(const char *fname, DWORD mode, DWORD creation)
  712. {
  713.     HANDLE fileh;
  714.     WCHAR *wfname;
  715.  
  716.     UTF8_TO_UNICODE_STACK(wfname, fname);
  717.     BAIL_IF(!wfname, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
  718.  
  719.     fileh = winCreateFileW(wfname, mode, creation);
  720.     __PHYSFS_smallFree(wfname);
  721.  
  722.     if (fileh == INVALID_HANDLE_VALUE)
  723.         BAIL(errcodeFromWinApi(), INVALID_HANDLE_VALUE);
  724.  
  725.     return fileh;
  726. } /* doOpen */
  727.  
  728.  
  729. void *__PHYSFS_platformOpenRead(const char *filename)
  730. {
  731.     HANDLE h = doOpen(filename, GENERIC_READ, OPEN_EXISTING);
  732.     return (h == INVALID_HANDLE_VALUE) ? NULL : (void *) h;
  733. } /* __PHYSFS_platformOpenRead */
  734.  
  735.  
  736. void *__PHYSFS_platformOpenWrite(const char *filename)
  737. {
  738.     HANDLE h = doOpen(filename, GENERIC_WRITE, CREATE_ALWAYS);
  739.     return (h == INVALID_HANDLE_VALUE) ? NULL : (void *) h;
  740. } /* __PHYSFS_platformOpenWrite */
  741.  
  742.  
  743. void *__PHYSFS_platformOpenAppend(const char *filename)
  744. {
  745.     HANDLE h = doOpen(filename, GENERIC_WRITE, OPEN_ALWAYS);
  746.     BAIL_IF_ERRPASS(h == INVALID_HANDLE_VALUE, NULL);
  747.  
  748.     if (!winSetFilePointer(h, 0, NULL, FILE_END))
  749.     {
  750.         const PHYSFS_ErrorCode err = errcodeFromWinApi();
  751.         CloseHandle(h);
  752.         BAIL(err, NULL);
  753.     } /* if */
  754.  
  755.     return (void *) h;
  756. } /* __PHYSFS_platformOpenAppend */
  757.  
  758.  
  759. PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len)
  760. {
  761.     HANDLE h = (HANDLE) opaque;
  762.     PHYSFS_sint64 totalRead = 0;
  763.  
  764.     if (!__PHYSFS_ui64FitsAddressSpace(len))
  765.         BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
  766.  
  767.     while (len > 0)
  768.     {
  769.         const DWORD thislen = (len > 0xFFFFFFFF) ? 0xFFFFFFFF : (DWORD) len;
  770.         DWORD numRead = 0;
  771.         if (!ReadFile(h, buf, thislen, &numRead, NULL))
  772.             BAIL(errcodeFromWinApi(), -1);
  773.         len -= (PHYSFS_uint64) numRead;
  774.         totalRead += (PHYSFS_sint64) numRead;
  775.         if (numRead != thislen)
  776.             break;
  777.     } /* while */
  778.  
  779.     return totalRead;
  780. } /* __PHYSFS_platformRead */
  781.  
  782.  
  783. PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
  784.                                      PHYSFS_uint64 len)
  785. {
  786.     HANDLE h = (HANDLE) opaque;
  787.     PHYSFS_sint64 totalWritten = 0;
  788.  
  789.     if (!__PHYSFS_ui64FitsAddressSpace(len))
  790.         BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
  791.  
  792.     while (len > 0)
  793.     {
  794.         const DWORD thislen = (len > 0xFFFFFFFF) ? 0xFFFFFFFF : (DWORD) len;
  795.         DWORD numWritten = 0;
  796.         if (!WriteFile(h, buffer, thislen, &numWritten, NULL))
  797.             BAIL(errcodeFromWinApi(), -1);
  798.         len -= (PHYSFS_uint64) numWritten;
  799.         totalWritten += (PHYSFS_sint64) numWritten;
  800.         if (numWritten != thislen)
  801.             break;
  802.     } /* while */
  803.  
  804.     return totalWritten;
  805. } /* __PHYSFS_platformWrite */
  806.  
  807.  
  808. int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
  809. {
  810.     HANDLE h = (HANDLE) opaque;
  811.     const PHYSFS_sint64 spos = (PHYSFS_sint64) pos;
  812.     BAIL_IF(!winSetFilePointer(h,spos,NULL,FILE_BEGIN), errcodeFromWinApi(), 0);
  813.     return 1;  /* No error occured */
  814. } /* __PHYSFS_platformSeek */
  815.  
  816.  
  817. PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
  818. {
  819.     HANDLE h = (HANDLE) opaque;
  820.     PHYSFS_sint64 pos = 0;
  821.     BAIL_IF(!winSetFilePointer(h,0,&pos,FILE_CURRENT), errcodeFromWinApi(), -1);
  822.     return pos;
  823. } /* __PHYSFS_platformTell */
  824.  
  825.  
  826. PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
  827. {
  828.     HANDLE h = (HANDLE) opaque;
  829.     const PHYSFS_sint64 retval = winGetFileSize(h);
  830.     BAIL_IF(retval < 0, errcodeFromWinApi(), -1);
  831.     return retval;
  832. } /* __PHYSFS_platformFileLength */
  833.  
  834.  
  835. int __PHYSFS_platformFlush(void *opaque)
  836. {
  837.     HANDLE h = (HANDLE) opaque;
  838.     BAIL_IF(!FlushFileBuffers(h), errcodeFromWinApi(), 0);
  839.     return 1;
  840. } /* __PHYSFS_platformFlush */
  841.  
  842.  
  843. void __PHYSFS_platformClose(void *opaque)
  844. {
  845.     HANDLE h = (HANDLE) opaque;
  846.     (void) CloseHandle(h); /* ignore errors. You should have flushed! */
  847. } /* __PHYSFS_platformClose */
  848.  
  849.  
  850. static int doPlatformDelete(LPWSTR wpath)
  851. {
  852.     WIN32_FILE_ATTRIBUTE_DATA info;
  853.     if (!GetFileAttributesExW(wpath, GetFileExInfoStandard, &info))
  854.         BAIL(errcodeFromWinApi(), 0);
  855.     else
  856.     {
  857.         const int isdir = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
  858.         const BOOL rc = isdir ? RemoveDirectoryW(wpath) : DeleteFileW(wpath);
  859.         BAIL_IF(!rc, errcodeFromWinApi(), 0);
  860.     } /* else */
  861.     return 1;   /* if you made it here, it worked. */
  862. } /* doPlatformDelete */
  863.  
  864.  
  865. int __PHYSFS_platformDelete(const char *path)
  866. {
  867.     int retval = 0;
  868.     LPWSTR wpath = NULL;
  869.     UTF8_TO_UNICODE_STACK(wpath, path);
  870.     BAIL_IF(!wpath, PHYSFS_ERR_OUT_OF_MEMORY, 0);
  871.     retval = doPlatformDelete(wpath);
  872.     __PHYSFS_smallFree(wpath);
  873.     return retval;
  874. } /* __PHYSFS_platformDelete */
  875.  
  876.  
  877. void *__PHYSFS_platformCreateMutex(void)
  878. {
  879.     LPCRITICAL_SECTION lpcs;
  880.     lpcs = (LPCRITICAL_SECTION) allocator.Malloc(sizeof (CRITICAL_SECTION));
  881.     BAIL_IF(!lpcs, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
  882.  
  883.     if (!winInitializeCriticalSection(lpcs))
  884.     {
  885.         allocator.Free(lpcs);
  886.         BAIL(errcodeFromWinApi(), NULL);
  887.     } /* if */
  888.  
  889.     return lpcs;
  890. } /* __PHYSFS_platformCreateMutex */
  891.  
  892.  
  893. void __PHYSFS_platformDestroyMutex(void *mutex)
  894. {
  895.     DeleteCriticalSection((LPCRITICAL_SECTION) mutex);
  896.     allocator.Free(mutex);
  897. } /* __PHYSFS_platformDestroyMutex */
  898.  
  899.  
  900. int __PHYSFS_platformGrabMutex(void *mutex)
  901. {
  902.     EnterCriticalSection((LPCRITICAL_SECTION) mutex);
  903.     return 1;
  904. } /* __PHYSFS_platformGrabMutex */
  905.  
  906.  
  907. void __PHYSFS_platformReleaseMutex(void *mutex)
  908. {
  909.     LeaveCriticalSection((LPCRITICAL_SECTION) mutex);
  910. } /* __PHYSFS_platformReleaseMutex */
  911.  
  912.  
  913. static PHYSFS_sint64 FileTimeToPhysfsTime(const FILETIME *ft)
  914. {
  915.     SYSTEMTIME st_utc;
  916.     SYSTEMTIME st_localtz;
  917.     TIME_ZONE_INFORMATION tzi;
  918.     DWORD tzid;
  919.     PHYSFS_sint64 retval;
  920.     struct tm tm;
  921.     BOOL rc;
  922.  
  923.     BAIL_IF(!FileTimeToSystemTime(ft, &st_utc), errcodeFromWinApi(), -1);
  924.     tzid = GetTimeZoneInformation(&tzi);
  925.     BAIL_IF(tzid == TIME_ZONE_ID_INVALID, errcodeFromWinApi(), -1);
  926.     rc = SystemTimeToTzSpecificLocalTime(&tzi, &st_utc, &st_localtz);
  927.     BAIL_IF(!rc, errcodeFromWinApi(), -1);
  928.  
  929.     /* Convert to a format that mktime() can grok... */
  930.     tm.tm_sec = st_localtz.wSecond;
  931.     tm.tm_min = st_localtz.wMinute;
  932.     tm.tm_hour = st_localtz.wHour;
  933.     tm.tm_mday = st_localtz.wDay;
  934.     tm.tm_mon = st_localtz.wMonth - 1;
  935.     tm.tm_year = st_localtz.wYear - 1900;
  936.     tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
  937.     tm.tm_yday = -1;
  938.     tm.tm_isdst = -1;
  939.  
  940.     /* Convert to a format PhysicsFS can grok... */
  941.     retval = (PHYSFS_sint64) mktime(&tm);
  942.     BAIL_IF(retval == -1, PHYSFS_ERR_OS_ERROR, -1);
  943.     return retval;
  944. } /* FileTimeToPhysfsTime */
  945.  
  946.  
  947. /* check for symlinks. These exist in NTFS 3.1 (WinXP), even though
  948.    they aren't really available to userspace before Vista. I wonder
  949.    what would happen if you put an NTFS disk with a symlink on it
  950.    into an XP machine, though; would this flag get set?
  951.    NTFS symlinks are a form of "reparse point" (junction, volume mount,
  952.    etc), so if the REPARSE_POINT attribute is set, check for the symlink
  953.    tag thereafter. This assumes you already read in the file attributes. */
  954. static int isSymlink(const WCHAR *wpath, const DWORD attr)
  955. {
  956.     WIN32_FIND_DATAW w32dw;
  957.     HANDLE h;
  958.  
  959.     if ((attr & PHYSFS_FILE_ATTRIBUTE_REPARSE_POINT) == 0)
  960.         return 0;  /* not a reparse point? Definitely not a symlink. */
  961.  
  962.     h = winFindFirstFileW(wpath, &w32dw);
  963.     if (h == INVALID_HANDLE_VALUE)
  964.         return 0;  /* ...maybe the file just vanished...? */
  965.  
  966.     FindClose(h);
  967.     return (w32dw.dwReserved0 == PHYSFS_IO_REPARSE_TAG_SYMLINK);
  968. } /* isSymlink */
  969.  
  970.  
  971. int __PHYSFS_platformStat(const char *filename, PHYSFS_Stat *st, const int follow)
  972. {
  973.     WIN32_FILE_ATTRIBUTE_DATA winstat;
  974.     WCHAR *wstr = NULL;
  975.     DWORD err = 0;
  976.     BOOL rc = 0;
  977.     int issymlink = 0;
  978.  
  979.     UTF8_TO_UNICODE_STACK(wstr, filename);
  980.     BAIL_IF(!wstr, PHYSFS_ERR_OUT_OF_MEMORY, 0);
  981.     rc = GetFileAttributesExW(wstr, GetFileExInfoStandard, &winstat);
  982.  
  983.     if (!rc)
  984.         err = GetLastError();
  985.     else  /* check for symlink while wstr is still available */
  986.         issymlink = !follow && isSymlink(wstr, winstat.dwFileAttributes);
  987.  
  988.     __PHYSFS_smallFree(wstr);
  989.     BAIL_IF(!rc, errcodeFromWinApiError(err), 0);
  990.  
  991.     st->modtime = FileTimeToPhysfsTime(&winstat.ftLastWriteTime);
  992.     st->accesstime = FileTimeToPhysfsTime(&winstat.ftLastAccessTime);
  993.     st->createtime = FileTimeToPhysfsTime(&winstat.ftCreationTime);
  994.  
  995.     if (issymlink)
  996.     {
  997.         st->filetype = PHYSFS_FILETYPE_SYMLINK;
  998.         st->filesize = 0;
  999.     } /* if */
  1000.  
  1001.     else if (winstat.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  1002.     {
  1003.         st->filetype = PHYSFS_FILETYPE_DIRECTORY;
  1004.         st->filesize = 0;
  1005.     } /* else if */
  1006.  
  1007.     else if (winstat.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_DEVICE))
  1008.     {
  1009.         st->filetype = PHYSFS_FILETYPE_OTHER;
  1010.         st->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
  1011.     } /* else if */
  1012.  
  1013.     else
  1014.     {
  1015.         st->filetype = PHYSFS_FILETYPE_REGULAR;
  1016.         st->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
  1017.     } /* else */
  1018.  
  1019.     st->readonly = ((winstat.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
  1020.  
  1021.     return 1;
  1022. } /* __PHYSFS_platformStat */
  1023.  
  1024. #endif  /* PHYSFS_PLATFORM_WINDOWS */
  1025.  
  1026. /* end of physfs_platform_windows.c ... */
  1027.  
  1028.  
  1029.