Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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