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! */
72static PHYSFS_uint64 wStrLen(const WCHAR *wstr)
73{
74 PHYSFS_uint64 len = 0;
75 while (*(wstr++))
76 len++;
77 return len;
78} /* wStrLen */
79
80static 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
102static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d)
103{
104 #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0501) // Windows XP+
105 return FindFirstFileExW(path, FindExInfoStandard, d,
106 FindExSearchNameMatch, NULL, 0);
107 #else
108 return FindFirstFileW(path, d);
109 #endif
110} /* winFindFirstFileW */
111
112static inline BOOL winInitializeCriticalSection(LPCRITICAL_SECTION lpcs)
113{
114 #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
115 return InitializeCriticalSectionEx(lpcs, 2000, 0);
116 #else
117 InitializeCriticalSection(lpcs);
118 return TRUE;
119 #endif
120} /* winInitializeCriticalSection */
121
122static 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 #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0602) // Windows 8+
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
134static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
135 PHYSFS_sint64 *_newpos, const DWORD whence)
136{
137 #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0501) // Windows XP+
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
159static PHYSFS_sint64 winGetFileSize(HANDLE h)
160{
161 #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
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
176static 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
225static 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
235static HANDLE detectCDThreadHandle = NULL;
236static HWND detectCDHwnd = NULL;
237static volatile DWORD drivesWithMediaBitmap = 0;
238
239typedef BOOL (WINAPI *fnSTEM)(DWORD, LPDWORD b);
240
241static 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
284static 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
310static 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
365static 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
405static 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
419void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
420{
421 detectAvailableCDs(cb, data);
422} /* __PHYSFS_platformDetectAvailableCDs */
423
424#ifdef PHYSFS_PLATFORM_WINRT
425static 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
447char *__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
509char *__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
548char *__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
607done:
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
616int __PHYSFS_platformInit(void)
617{
618 return 1; /* It's all good */
619} /* __PHYSFS_platformInit */
620
621
622void __PHYSFS_platformDeinit(void)
623{
624 deinitCDThread();
625} /* __PHYSFS_platformDeinit */
626
627
628void *__PHYSFS_platformGetThreadID(void)
629{
630 return ( (void *) ((size_t) GetCurrentThreadId()) );
631} /* __PHYSFS_platformGetThreadID */
632
633
634PHYSFS_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
699int __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
711static 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
729void *__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
736void *__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
743void *__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
759PHYSFS_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
783PHYSFS_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
808int __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
817PHYSFS_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
826PHYSFS_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
835int __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
843void __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
850static 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
865int __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
877void *__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
893void __PHYSFS_platformDestroyMutex(void *mutex)
894{
895 DeleteCriticalSection((LPCRITICAL_SECTION) mutex);
896 allocator.Free(mutex);
897} /* __PHYSFS_platformDestroyMutex */
898
899
900int __PHYSFS_platformGrabMutex(void *mutex)
901{
902 EnterCriticalSection((LPCRITICAL_SECTION) mutex);
903 return 1;
904} /* __PHYSFS_platformGrabMutex */
905
906
907void __PHYSFS_platformReleaseMutex(void *mutex)
908{
909 LeaveCriticalSection((LPCRITICAL_SECTION) mutex);
910} /* __PHYSFS_platformReleaseMutex */
911
912
913static 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. */
954static 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
971int __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