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 | #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 | |
112 | static 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 | |
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 | #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 | |
134 | static 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 | |
159 | static 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 | |
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 | |