| 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 | |