c9f9ee894eb8cce322507075c5218642a2366006
[supertux.git] / src / unison / physfs-1.1.1 / platform / windows.c
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, since we manage this ourselves. */
15 #ifdef UNICODE
16 #undef UNICODE
17 #endif
18
19 #include <windows.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <time.h>
26
27 #include "physfs_internal.h"
28
29 #define LOWORDER_UINT64(pos) (PHYSFS_uint32) \
30     (pos & 0x00000000FFFFFFFF)
31 #define HIGHORDER_UINT64(pos) (PHYSFS_uint32) \
32     (((pos & 0xFFFFFFFF00000000) >> 32) & 0x00000000FFFFFFFF)
33
34 /*
35  * Users without the platform SDK don't have this defined.  The original docs
36  *  for SetFilePointer() just said to compare with 0xFFFFFFFF, so this should
37  *  work as desired.
38  */
39 #define PHYSFS_INVALID_SET_FILE_POINTER  0xFFFFFFFF
40
41 /* just in case... */
42 #define PHYSFS_INVALID_FILE_ATTRIBUTES   0xFFFFFFFF
43
44 /* Not defined before the Vista SDK. */
45 #define PHYSFS_IO_REPARSE_TAG_SYMLINK    0xA000000C
46
47
48 #define UTF8_TO_UNICODE_STACK_MACRO(w_assignto, str) { \
49     if (str == NULL) \
50         w_assignto = NULL; \
51     else { \
52         const PHYSFS_uint64 len = (PHYSFS_uint64) ((strlen(str) * 4) + 1); \
53         w_assignto = (WCHAR *) __PHYSFS_smallAlloc(len); \
54         if (w_assignto != NULL) \
55             PHYSFS_utf8ToUcs2(str, (PHYSFS_uint16 *) w_assignto, len); \
56     } \
57 } \
58
59 static PHYSFS_uint64 wStrLen(const WCHAR *wstr)
60 {
61     PHYSFS_uint64 len = 0;
62     while (*(wstr++))
63         len++;
64     return(len);
65 } /* wStrLen */
66
67 static char *unicodeToUtf8Heap(const WCHAR *w_str)
68 {
69     char *retval = NULL;
70     if (w_str != NULL)
71     {
72         void *ptr = NULL;
73         const PHYSFS_uint64 len = (wStrLen(w_str) * 4) + 1;
74         retval = allocator.Malloc(len);
75         BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
76         PHYSFS_utf8FromUcs2((const PHYSFS_uint16 *) w_str, retval, len);
77         ptr = allocator.Realloc(retval, strlen(retval) + 1); /* shrink. */
78         if (ptr != NULL)
79             retval = (char *) ptr;
80     } /* if */
81     return(retval);
82 } /* unicodeToUtf8Heap */
83
84
85 static char *codepageToUtf8Heap(const char *cpstr)
86 {
87     char *retval = NULL;
88     if (cpstr != NULL)
89     {
90         const int len = (int) (strlen(cpstr) + 1);
91         WCHAR *wbuf = (WCHAR *) __PHYSFS_smallAlloc(len * sizeof (WCHAR));
92         BAIL_IF_MACRO(wbuf == NULL, ERR_OUT_OF_MEMORY, NULL);
93         MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, cpstr, len, wbuf, len);
94         retval = (char *) allocator.Malloc(len * 4);
95         if (retval == NULL)
96             __PHYSFS_setError(ERR_OUT_OF_MEMORY);
97         else
98             PHYSFS_utf8FromUcs2(wbuf, retval, len * 4);
99         __PHYSFS_smallFree(wbuf);
100     } /* if */
101     return(retval);
102 } /* codepageToUtf8Heap */
103
104
105 typedef struct
106 {
107     HANDLE handle;
108     int readonly;
109 } WinApiFile;
110
111
112 static char *userDir = NULL;
113 static int osHasUnicode = 0;
114
115
116 /* pointers for APIs that may not exist on some Windows versions... */
117 static HANDLE libKernel32 = NULL;
118 static HANDLE libUserEnv = NULL;
119 static HANDLE libAdvApi32 = NULL;
120 static DWORD (WINAPI *pGetModuleFileNameW)(HMODULE, LPWCH, DWORD);
121 static BOOL (WINAPI *pGetUserProfileDirectoryW)(HANDLE, LPWSTR, LPDWORD);
122 static BOOL (WINAPI *pGetUserNameW)(LPWSTR, LPDWORD);
123 static DWORD (WINAPI *pGetFileAttributesW)(LPCWSTR);
124 static HANDLE (WINAPI *pFindFirstFileW)(LPCWSTR, LPWIN32_FIND_DATAW);
125 static BOOL (WINAPI *pFindNextFileW)(HANDLE, LPWIN32_FIND_DATAW);
126 static DWORD (WINAPI *pGetCurrentDirectoryW)(DWORD, LPWSTR);
127 static BOOL (WINAPI *pDeleteFileW)(LPCWSTR);
128 static BOOL (WINAPI *pRemoveDirectoryW)(LPCWSTR);
129 static BOOL (WINAPI *pCreateDirectoryW)(LPCWSTR, LPSECURITY_ATTRIBUTES);
130 static BOOL (WINAPI *pGetFileAttributesExA)
131     (LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID);
132 static BOOL (WINAPI *pGetFileAttributesExW)
133     (LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID);
134 static DWORD (WINAPI *pFormatMessageW)
135     (DWORD, LPCVOID, DWORD, DWORD, LPWSTR, DWORD, va_list *);
136 static HANDLE (WINAPI *pCreateFileW)
137     (LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
138
139
140 /*
141  * Fallbacks for missing Unicode functions on Win95/98/ME. These are filled
142  *  into the function pointers if looking up the real Unicode entry points
143  *  in the system DLLs fails, so they're never used on WinNT/XP/Vista/etc.
144  * They make an earnest effort to convert to/from UTF-8 and UCS-2 to 
145  *  the user's current codepage.
146  */
147
148 static BOOL WINAPI fallbackGetUserNameW(LPWSTR buf, LPDWORD len)
149 {
150     const DWORD cplen = *len;
151     char *cpstr = __PHYSFS_smallAlloc(cplen);
152     BOOL retval = GetUserNameA(cpstr, len);
153     if (buf != NULL)
154         MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, cpstr, cplen, buf, *len);
155     __PHYSFS_smallFree(cpstr);
156     return(retval);
157 } /* fallbackGetUserNameW */
158
159 static DWORD WINAPI fallbackFormatMessageW(DWORD dwFlags, LPCVOID lpSource,
160                                            DWORD dwMessageId, DWORD dwLangId,
161                                            LPWSTR lpBuf, DWORD nSize,
162                                            va_list *Arguments)
163 {
164     char *cpbuf = (char *) __PHYSFS_smallAlloc(nSize);
165     DWORD retval = FormatMessageA(dwFlags, lpSource, dwMessageId, dwLangId,
166                                   cpbuf, nSize, Arguments);
167     if (retval > 0)
168         MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,cpbuf,retval,lpBuf,nSize);
169     __PHYSFS_smallFree(cpbuf);
170     return(retval);
171 } /* fallbackFormatMessageW */
172
173 static DWORD WINAPI fallbackGetModuleFileNameW(HMODULE hMod, LPWCH lpBuf,
174                                                DWORD nSize)
175 {
176     char *cpbuf = (char *) __PHYSFS_smallAlloc(nSize);
177     DWORD retval = GetModuleFileNameA(hMod, cpbuf, nSize);
178     if (retval > 0)
179         MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,cpbuf,retval,lpBuf,nSize);
180     __PHYSFS_smallFree(cpbuf);
181     return(retval);
182 } /* fallbackGetModuleFileNameW */
183
184 static DWORD WINAPI fallbackGetFileAttributesW(LPCWSTR fname)
185 {
186     DWORD retval = 0;
187     const int buflen = (int) (wStrLen(fname) + 1);
188     char *cpstr = (char *) __PHYSFS_smallAlloc(buflen);
189     WideCharToMultiByte(CP_ACP, 0, fname, buflen, cpstr, buflen, NULL, NULL);
190     retval = GetFileAttributesA(cpstr);
191     __PHYSFS_smallFree(cpstr);
192     return(retval);
193 } /* fallbackGetFileAttributesW */
194
195 static DWORD WINAPI fallbackGetCurrentDirectoryW(DWORD buflen, LPWSTR buf)
196 {
197     DWORD retval = 0;
198     char *cpbuf = NULL;
199     if (buf != NULL)
200         cpbuf = (char *) __PHYSFS_smallAlloc(buflen);
201     retval = GetCurrentDirectoryA(buflen, cpbuf);
202     if (cpbuf != NULL)
203     {
204         MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,cpbuf,retval,buf,buflen);
205         __PHYSFS_smallFree(cpbuf);
206     } /* if */
207     return(retval);
208 } /* fallbackGetCurrentDirectoryW */
209
210 static BOOL WINAPI fallbackRemoveDirectoryW(LPCWSTR dname)
211 {
212     BOOL retval = 0;
213     const int buflen = (int) (wStrLen(dname) + 1);
214     char *cpstr = (char *) __PHYSFS_smallAlloc(buflen);
215     WideCharToMultiByte(CP_ACP, 0, dname, buflen, cpstr, buflen, NULL, NULL);
216     retval = RemoveDirectoryA(cpstr);
217     __PHYSFS_smallFree(cpstr);
218     return(retval);
219 } /* fallbackRemoveDirectoryW */
220
221 static BOOL WINAPI fallbackCreateDirectoryW(LPCWSTR dname, 
222                                             LPSECURITY_ATTRIBUTES attr)
223 {
224     BOOL retval = 0;
225     const int buflen = (int) (wStrLen(dname) + 1);
226     char *cpstr = (char *) __PHYSFS_smallAlloc(buflen);
227     WideCharToMultiByte(CP_ACP, 0, dname, buflen, cpstr, buflen, NULL, NULL);
228     retval = CreateDirectoryA(cpstr, attr);
229     __PHYSFS_smallFree(cpstr);
230     return(retval);
231 } /* fallbackCreateDirectoryW */
232
233 static BOOL WINAPI fallbackDeleteFileW(LPCWSTR fname)
234 {
235     BOOL retval = 0;
236     const int buflen = (int) (wStrLen(fname) + 1);
237     char *cpstr = (char *) __PHYSFS_smallAlloc(buflen);
238     WideCharToMultiByte(CP_ACP, 0, fname, buflen, cpstr, buflen, NULL, NULL);
239     retval = DeleteFileA(cpstr);
240     __PHYSFS_smallFree(cpstr);
241     return(retval);
242 } /* fallbackDeleteFileW */
243
244 static HANDLE WINAPI fallbackCreateFileW(LPCWSTR fname, 
245                 DWORD dwDesiredAccess, DWORD dwShareMode,
246                 LPSECURITY_ATTRIBUTES lpSecurityAttrs,
247                 DWORD dwCreationDisposition,
248                 DWORD dwFlagsAndAttrs, HANDLE hTemplFile)
249 {
250     HANDLE retval;
251     const int buflen = (int) (wStrLen(fname) + 1);
252     char *cpstr = (char *) __PHYSFS_smallAlloc(buflen);
253     WideCharToMultiByte(CP_ACP, 0, fname, buflen, cpstr, buflen, NULL, NULL);
254     retval = CreateFileA(cpstr, dwDesiredAccess, dwShareMode, lpSecurityAttrs,
255                          dwCreationDisposition, dwFlagsAndAttrs, hTemplFile);
256     __PHYSFS_smallFree(cpstr);
257     return(retval);
258 } /* fallbackCreateFileW */
259
260
261 /* A blatant abuse of pointer casting... */
262 static int symLookup(HMODULE dll, void **addr, const char *sym)
263 {
264     return( (*addr = GetProcAddress(dll, sym)) != NULL );
265 } /* symLookup */
266
267
268 static int findApiSymbols(void)
269 {
270     HMODULE dll = NULL;
271
272     #define LOOKUP_NOFALLBACK(x, reallyLook) { \
273         if (reallyLook) \
274             symLookup(dll, (void **) &p##x, #x); \
275         else \
276             p##x = NULL; \
277     }
278
279     #define LOOKUP(x, reallyLook) { \
280         if ((!reallyLook) || (!symLookup(dll, (void **) &p##x, #x))) \
281             p##x = fallback##x; \
282     }
283
284     /* Apparently Win9x HAS the Unicode entry points, they just don't WORK. */
285     /*  ...so don't look them up unless we're on NT+. (see osHasUnicode.) */
286
287     dll = libUserEnv = LoadLibraryA("userenv.dll");
288     if (dll != NULL)
289         LOOKUP_NOFALLBACK(GetUserProfileDirectoryW, osHasUnicode);
290
291     /* !!! FIXME: what do they call advapi32.dll on Win64? */
292     dll = libAdvApi32 = LoadLibraryA("advapi32.dll");
293     if (dll != NULL)
294         LOOKUP(GetUserNameW, osHasUnicode);
295
296     /* !!! FIXME: what do they call kernel32.dll on Win64? */
297     dll = libKernel32 = LoadLibraryA("kernel32.dll");
298     if (dll != NULL)
299     {
300         LOOKUP_NOFALLBACK(GetFileAttributesExA, 1);
301         LOOKUP_NOFALLBACK(GetFileAttributesExW, osHasUnicode);
302         LOOKUP_NOFALLBACK(FindFirstFileW, osHasUnicode);
303         LOOKUP_NOFALLBACK(FindNextFileW, osHasUnicode);
304         LOOKUP(GetModuleFileNameW, osHasUnicode);
305         LOOKUP(FormatMessageW, osHasUnicode);
306         LOOKUP(GetFileAttributesW, osHasUnicode);
307         LOOKUP(GetCurrentDirectoryW, osHasUnicode);
308         LOOKUP(CreateDirectoryW, osHasUnicode);
309         LOOKUP(RemoveDirectoryW, osHasUnicode);
310         LOOKUP(CreateFileW, osHasUnicode);
311         LOOKUP(DeleteFileW, osHasUnicode);
312     } /* if */
313
314     #undef LOOKUP_NOFALLBACK
315     #undef LOOKUP
316
317     return(1);
318 } /* findApiSymbols */
319
320
321 const char *__PHYSFS_platformDirSeparator = "\\";
322
323
324 /*
325  * Figure out what the last failing Windows API call was, and
326  *  generate a human-readable string for the error message.
327  *
328  * The return value is a static buffer that is overwritten with
329  *  each call to this function.
330  */
331 static const char *winApiStrError(void)
332 {
333     static char utf8buf[255];
334     WCHAR msgbuf[255];
335     WCHAR *ptr;
336     DWORD rc = pFormatMessageW(
337                     FORMAT_MESSAGE_FROM_SYSTEM |
338                     FORMAT_MESSAGE_IGNORE_INSERTS,
339                     NULL, GetLastError(),
340                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
341                     msgbuf, __PHYSFS_ARRAYLEN(msgbuf),
342                     NULL);
343
344     /* chop off newlines. */
345     for (ptr = msgbuf; *ptr; ptr++)
346     {
347         if ((*ptr == '\n') || (*ptr == '\r'))
348         {
349             *ptr = '\0';
350             break;
351         } /* if */
352     } /* for */
353
354     /* may truncate, but oh well. */
355     PHYSFS_utf8FromUcs2((PHYSFS_uint16 *) msgbuf, utf8buf, sizeof (utf8buf));
356     return((const char *) utf8buf);
357 } /* winApiStrError */
358
359
360 static char *getExePath(void)
361 {
362     DWORD buflen = 64;
363     int success = 0;
364     LPWSTR modpath = NULL;
365     char *retval = NULL;
366
367     while (1)
368     {
369         DWORD rc;
370         void *ptr;
371
372         if ( !(ptr = allocator.Realloc(modpath, buflen*sizeof(WCHAR))) )
373         {
374             allocator.Free(modpath);
375             BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL);
376         } /* if */
377         modpath = (LPWSTR) ptr;
378
379         rc = pGetModuleFileNameW(NULL, modpath, buflen);
380         if (rc == 0)
381         {
382             allocator.Free(modpath);
383             BAIL_MACRO(winApiStrError(), NULL);
384         } /* if */
385
386         if (rc < buflen)
387         {
388             buflen = rc;
389             break;
390         } /* if */
391
392         buflen *= 2;
393     } /* while */
394
395     if (buflen > 0)  /* just in case... */
396     {
397         WCHAR *ptr = (modpath + buflen) - 1;
398         while (ptr != modpath)
399         {
400             if (*ptr == '\\')
401                 break;
402             ptr--;
403         } /* while */
404
405         if ((ptr == modpath) && (*ptr != '\\'))
406             __PHYSFS_setError(ERR_GETMODFN_NO_DIR);
407         else
408         {
409             *(ptr + 1) = '\0';  /* chop off filename. */
410             retval = unicodeToUtf8Heap(modpath);
411         } /* else */
412     } /* else */
413     allocator.Free(modpath);
414
415     return(retval);   /* w00t. */
416 } /* getExePath */
417
418
419 /*
420  * Try to make use of GetUserProfileDirectoryW(), which isn't available on
421  *  some common variants of Win32. If we can't use this, we just punt and
422  *  use the physfs base dir for the user dir, too.
423  *
424  * On success, module-scope variable (userDir) will have a pointer to
425  *  a malloc()'d string of the user's profile dir, and a non-zero value is
426  *  returned. If we can't determine the profile dir, (userDir) will
427  *  be NULL, and zero is returned.
428  */
429 static int determineUserDir(void)
430 {
431     if (userDir != NULL)
432         return(1);  /* already good to go. */
433
434     /*
435      * GetUserProfileDirectoryW() is only available on NT 4.0 and later.
436      *  This means Win95/98/ME (and CE?) users have to do without, so for
437      *  them, we'll default to the base directory when we can't get the
438      *  function pointer. Since this is originally an NT API, we don't
439          *  offer a non-Unicode fallback.
440      */
441     if (pGetUserProfileDirectoryW != NULL)
442     {
443         HANDLE accessToken = NULL;       /* Security handle to process */
444         HANDLE processHandle = GetCurrentProcess();
445         if (OpenProcessToken(processHandle, TOKEN_QUERY, &accessToken))
446         {
447             DWORD psize = 0;
448             WCHAR dummy = 0;
449             LPWSTR wstr = NULL;
450             BOOL rc = 0;
451
452             /*
453              * Should fail. Will write the size of the profile path in
454              *  psize. Also note that the second parameter can't be
455              *  NULL or the function fails.
456              */ 
457                 rc = pGetUserProfileDirectoryW(accessToken, &dummy, &psize);
458             assert(!rc);  /* !!! FIXME: handle this gracefully. */
459
460             /* Allocate memory for the profile directory */
461             wstr = (LPWSTR) __PHYSFS_smallAlloc(psize * sizeof (WCHAR));
462             if (wstr != NULL)
463             {
464                 if (pGetUserProfileDirectoryW(accessToken, wstr, &psize))
465                     userDir = unicodeToUtf8Heap(wstr);
466                 __PHYSFS_smallFree(wstr);
467             } /* else */
468         } /* if */
469
470         CloseHandle(accessToken);
471     } /* if */
472
473     if (userDir == NULL)  /* couldn't get profile for some reason. */
474     {
475         /* Might just be a non-NT system; resort to the basedir. */
476         userDir = getExePath();
477         BAIL_IF_MACRO(userDir == NULL, NULL, 0); /* STILL failed?! */
478     } /* if */
479
480     return(1);  /* We made it: hit the showers. */
481 } /* determineUserDir */
482
483
484 static BOOL mediaInDrive(const char *drive)
485 {
486     UINT oldErrorMode;
487     DWORD tmp;
488     BOOL retval;
489
490     /* Prevent windows warning message appearing when checking media size */
491     oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
492     
493     /* If this function succeeds, there's media in the drive */
494     retval = GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0);
495
496     /* Revert back to old windows error handler */
497     SetErrorMode(oldErrorMode);
498
499     return(retval);
500 } /* mediaInDrive */
501
502
503 void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
504 {
505     /* !!! FIXME: Can CD drives be non-drive letter paths? */
506     /* !!! FIXME:  (so can they be Unicode paths?) */
507     char drive_str[4] = "x:\\";
508     char ch;
509     for (ch = 'A'; ch <= 'Z'; ch++)
510     {
511         drive_str[0] = ch;
512         if (GetDriveType(drive_str) == DRIVE_CDROM && mediaInDrive(drive_str))
513             cb(data, drive_str);
514     } /* for */
515 } /* __PHYSFS_platformDetectAvailableCDs */
516
517
518 char *__PHYSFS_platformCalcBaseDir(const char *argv0)
519 {
520     if ((argv0 != NULL) && (strchr(argv0, '\\') != NULL))
521         return(NULL); /* default behaviour can handle this. */
522
523     return(getExePath());
524 } /* __PHYSFS_platformCalcBaseDir */
525
526
527 char *__PHYSFS_platformGetUserName(void)
528 {
529     DWORD bufsize = 0;
530     char *retval = NULL;
531     
532     if (pGetUserNameW(NULL, &bufsize) == 0)  /* This SHOULD fail. */
533     {
534         LPWSTR wbuf = (LPWSTR) __PHYSFS_smallAlloc(bufsize * sizeof (WCHAR));
535         BAIL_IF_MACRO(wbuf == NULL, ERR_OUT_OF_MEMORY, NULL);
536         if (pGetUserNameW(wbuf, &bufsize) == 0)  /* ?! */
537             __PHYSFS_setError(winApiStrError());
538         else
539             retval = unicodeToUtf8Heap(wbuf);
540         __PHYSFS_smallFree(wbuf);
541     } /* if */
542
543     return(retval);
544 } /* __PHYSFS_platformGetUserName */
545
546
547 char *__PHYSFS_platformGetUserDir(void)
548 {
549     char *retval = (char *) allocator.Malloc(strlen(userDir) + 1);
550     BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
551     strcpy(retval, userDir); /* calculated at init time. */
552     return(retval);
553 } /* __PHYSFS_platformGetUserDir */
554
555
556 PHYSFS_uint64 __PHYSFS_platformGetThreadID(void)
557 {
558     return((PHYSFS_uint64) GetCurrentThreadId());
559 } /* __PHYSFS_platformGetThreadID */
560
561
562 static int doPlatformExists(LPWSTR wpath)
563 {
564     BAIL_IF_MACRO
565     (
566         pGetFileAttributesW(wpath) == PHYSFS_INVALID_FILE_ATTRIBUTES,
567         winApiStrError(), 0
568     );
569     return(1);
570 } /* doPlatformExists */
571
572
573 int __PHYSFS_platformExists(const char *fname)
574 {
575     int retval = 0;
576     LPWSTR wpath;
577     UTF8_TO_UNICODE_STACK_MACRO(wpath, fname);
578     BAIL_IF_MACRO(wpath == NULL, ERR_OUT_OF_MEMORY, 0);
579     retval = doPlatformExists(wpath);
580     __PHYSFS_smallFree(wpath);
581     return(retval);
582 } /* __PHYSFS_platformExists */
583
584
585 static int isSymlinkAttrs(const DWORD attr, const DWORD tag)
586 {
587     return ( (attr & FILE_ATTRIBUTE_REPARSE_POINT) && 
588              (tag == PHYSFS_IO_REPARSE_TAG_SYMLINK) );
589 } /* isSymlinkAttrs */
590
591
592 int __PHYSFS_platformIsSymLink(const char *fname)
593 {
594     /* !!! FIXME:
595      * Windows Vista can have NTFS symlinks. Can older Windows releases have
596      *  them when talking to a network file server? What happens when you
597      *  mount a NTFS partition on XP that was plugged into a Vista install
598      *  that made a symlink?
599      */
600
601     int retval = 0;
602     LPWSTR wpath;
603     HANDLE dir;
604     WIN32_FIND_DATAW entw;
605
606     /* no unicode entry points? Probably no symlinks. */
607     BAIL_IF_MACRO(pFindFirstFileW == NULL, NULL, 0);
608
609     UTF8_TO_UNICODE_STACK_MACRO(wpath, fname);
610     BAIL_IF_MACRO(wpath == NULL, ERR_OUT_OF_MEMORY, 0);
611
612     /* !!! FIXME: filter wildcard chars? */
613     dir = pFindFirstFileW(wpath, &entw);
614     if (dir != INVALID_HANDLE_VALUE)
615     {
616         retval = isSymlinkAttrs(entw.dwFileAttributes, entw.dwReserved0);
617         FindClose(dir);
618     } /* if */
619
620     __PHYSFS_smallFree(wpath);
621     return(retval);
622 } /* __PHYSFS_platformIsSymlink */
623
624
625 int __PHYSFS_platformIsDirectory(const char *fname)
626 {
627     int retval = 0;
628     LPWSTR wpath;
629     UTF8_TO_UNICODE_STACK_MACRO(wpath, fname);
630     BAIL_IF_MACRO(wpath == NULL, ERR_OUT_OF_MEMORY, 0);
631     retval = ((pGetFileAttributesW(wpath) & FILE_ATTRIBUTE_DIRECTORY) != 0);
632     __PHYSFS_smallFree(wpath);
633     return(retval);
634 } /* __PHYSFS_platformIsDirectory */
635
636
637 char *__PHYSFS_platformCvtToDependent(const char *prepend,
638                                       const char *dirName,
639                                       const char *append)
640 {
641     int len = ((prepend) ? strlen(prepend) : 0) +
642               ((append) ? strlen(append) : 0) +
643               strlen(dirName) + 1;
644     char *retval = (char *) allocator.Malloc(len);
645     char *p;
646
647     BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
648
649     if (prepend)
650         strcpy(retval, prepend);
651     else
652         retval[0] = '\0';
653
654     strcat(retval, dirName);
655
656     if (append)
657         strcat(retval, append);
658
659     for (p = strchr(retval, '/'); p != NULL; p = strchr(p + 1, '/'))
660         *p = '\\';
661
662     return(retval);
663 } /* __PHYSFS_platformCvtToDependent */
664
665
666 void __PHYSFS_platformEnumerateFiles(const char *dirname,
667                                      int omitSymLinks,
668                                      PHYSFS_EnumFilesCallback callback,
669                                      const char *origdir,
670                                      void *callbackdata)
671 {
672     const int unicode = (pFindFirstFileW != NULL) && (pFindNextFileW != NULL);
673     HANDLE dir = INVALID_HANDLE_VALUE;
674     WIN32_FIND_DATA ent;
675     WIN32_FIND_DATAW entw;
676     size_t len = strlen(dirname);
677     char *searchPath = NULL;
678     WCHAR *wSearchPath = NULL;
679     char *utf8 = NULL;
680
681     /* Allocate a new string for path, maybe '\\', "*", and NULL terminator */
682     searchPath = (char *) __PHYSFS_smallAlloc(len + 3);
683     if (searchPath == NULL)
684         return;
685
686     /* Copy current dirname */
687     strcpy(searchPath, dirname);
688
689     /* if there's no '\\' at the end of the path, stick one in there. */
690     if (searchPath[len - 1] != '\\')
691     {
692         searchPath[len++] = '\\';
693         searchPath[len] = '\0';
694     } /* if */
695
696     /* Append the "*" to the end of the string */
697     strcat(searchPath, "*");
698
699     UTF8_TO_UNICODE_STACK_MACRO(wSearchPath, searchPath);
700     if (wSearchPath == NULL)
701         return;  /* oh well. */
702
703     if (unicode)
704         dir = pFindFirstFileW(wSearchPath, &entw);
705     else
706     {
707         const int len = (int) (wStrLen(wSearchPath) + 1);
708         char *cp = (char *) __PHYSFS_smallAlloc(len);
709         if (cp != NULL)
710         {
711             WideCharToMultiByte(CP_ACP, 0, wSearchPath, len, cp, len, 0, 0);
712             dir = FindFirstFileA(cp, &ent);
713             __PHYSFS_smallFree(cp);
714         } /* if */
715     } /* else */
716
717     __PHYSFS_smallFree(wSearchPath);
718     __PHYSFS_smallFree(searchPath);
719     if (dir == INVALID_HANDLE_VALUE)
720         return;
721
722     if (unicode)
723     {
724         do
725         {
726             const DWORD attr = entw.dwFileAttributes;
727             const DWORD tag = entw.dwReserved0;
728             const WCHAR *fn = entw.cFileName;
729             if ((fn[0] == '.') && (fn[1] == '\0'))
730                 continue;
731             if ((fn[0] == '.') && (fn[1] == '.') && (fn[2] == '\0'))
732                 continue;
733             if ((omitSymLinks) && (isSymlinkAttrs(attr, tag)))
734                 continue;
735
736             utf8 = unicodeToUtf8Heap(fn);
737             if (utf8 != NULL)
738             {
739                 callback(callbackdata, origdir, utf8);
740                 allocator.Free(utf8);
741             } /* if */
742         } while (pFindNextFileW(dir, &entw) != 0);
743     } /* if */
744
745     else  /* ANSI fallback. */
746     {
747         do
748         {
749             const DWORD attr = ent.dwFileAttributes;
750             const DWORD tag = ent.dwReserved0;
751             const char *fn = ent.cFileName;
752             if ((fn[0] == '.') && (fn[1] == '\0'))
753                 continue;
754             if ((fn[0] == '.') && (fn[1] == '.') && (fn[2] == '\0'))
755                 continue;
756             if ((omitSymLinks) && (isSymlinkAttrs(attr, tag)))
757                 continue;
758
759             utf8 = codepageToUtf8Heap(fn);
760             if (utf8 != NULL)
761             {
762                 callback(callbackdata, origdir, utf8);
763                 allocator.Free(utf8);
764             } /* if */
765         } while (FindNextFileA(dir, &ent) != 0);
766     } /* else */
767
768     FindClose(dir);
769 } /* __PHYSFS_platformEnumerateFiles */
770
771
772 char *__PHYSFS_platformCurrentDir(void)
773 {
774     char *retval = NULL;
775     WCHAR *wbuf = NULL;
776     DWORD buflen = 0;
777
778     buflen = pGetCurrentDirectoryW(buflen, NULL);
779     wbuf = (WCHAR *) __PHYSFS_smallAlloc((buflen + 2) * sizeof (WCHAR));
780     BAIL_IF_MACRO(wbuf == NULL, ERR_OUT_OF_MEMORY, NULL);
781     pGetCurrentDirectoryW(buflen, wbuf);
782
783     if (wbuf[buflen - 2] == '\\')
784         wbuf[buflen-1] = '\0';  /* just in case... */
785     else
786     {
787         wbuf[buflen - 1] = '\\'; 
788         wbuf[buflen] = '\0'; 
789     } /* else */
790
791     retval = unicodeToUtf8Heap(wbuf);
792     __PHYSFS_smallFree(wbuf);
793     return(retval);
794 } /* __PHYSFS_platformCurrentDir */
795
796
797 /* this could probably use a cleanup. */
798 char *__PHYSFS_platformRealPath(const char *path)
799 {
800     /* !!! FIXME: try GetFullPathName() instead? */
801     /* this function should be UTF-8 clean. */
802     char *retval = NULL;
803     char *p = NULL;
804
805     BAIL_IF_MACRO(path == NULL, ERR_INVALID_ARGUMENT, NULL);
806     BAIL_IF_MACRO(*path == '\0', ERR_INVALID_ARGUMENT, NULL);
807
808     retval = (char *) allocator.Malloc(MAX_PATH);
809     BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
810
811         /*
812          * If in \\server\path format, it's already an absolute path.
813          *  We'll need to check for "." and ".." dirs, though, just in case.
814          */
815     if ((path[0] == '\\') && (path[1] == '\\'))
816         strcpy(retval, path);
817
818     else
819     {
820         char *currentDir = __PHYSFS_platformCurrentDir();
821         if (currentDir == NULL)
822         {
823             allocator.Free(retval);
824             BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL);
825         } /* if */
826
827         if (path[1] == ':')   /* drive letter specified? */
828         {
829             /*
830              * Apparently, "D:mypath" is the same as "D:\\mypath" if
831              *  D: is not the current drive. However, if D: is the
832              *  current drive, then "D:mypath" is a relative path. Ugh.
833              */
834             if (path[2] == '\\')  /* maybe an absolute path? */
835                 strcpy(retval, path);
836             else  /* definitely an absolute path. */
837             {
838                 if (path[0] == currentDir[0]) /* current drive; relative. */
839                 {
840                     strcpy(retval, currentDir);
841                     strcat(retval, path + 2);
842                 } /* if */
843
844                 else  /* not current drive; absolute. */
845                 {
846                     retval[0] = path[0];
847                     retval[1] = ':';
848                     retval[2] = '\\';
849                     strcpy(retval + 3, path + 2);
850                 } /* else */
851             } /* else */
852         } /* if */
853
854         else  /* no drive letter specified. */
855         {
856             if (path[0] == '\\')  /* absolute path. */
857             {
858                 retval[0] = currentDir[0];
859                 retval[1] = ':';
860                 strcpy(retval + 2, path);
861             } /* if */
862             else
863             {
864                 strcpy(retval, currentDir);
865                 strcat(retval, path);
866             } /* else */
867         } /* else */
868
869         allocator.Free(currentDir);
870     } /* else */
871
872     /* (whew.) Ok, now take out "." and ".." path entries... */
873
874     p = retval;
875     while ( (p = strstr(p, "\\.")) != NULL)
876     {
877         /* it's a "." entry that doesn't end the string. */
878         if (p[2] == '\\')
879             memmove(p + 1, p + 3, strlen(p + 3) + 1);
880
881         /* it's a "." entry that ends the string. */
882         else if (p[2] == '\0')
883             p[0] = '\0';
884
885         /* it's a ".." entry. */
886         else if (p[2] == '.')
887         {
888             char *prevEntry = p - 1;
889             while ((prevEntry != retval) && (*prevEntry != '\\'))
890                 prevEntry--;
891
892             if (prevEntry == retval)  /* make it look like a "." entry. */
893                 memmove(p + 1, p + 2, strlen(p + 2) + 1);
894             else
895             {
896                 if (p[3] != '\0') /* doesn't end string. */
897                     *prevEntry = '\0';
898                 else /* ends string. */
899                     memmove(prevEntry + 1, p + 4, strlen(p + 4) + 1);
900
901                 p = prevEntry;
902             } /* else */
903         } /* else if */
904
905         else
906         {
907             p++;  /* look past current char. */
908         } /* else */
909     } /* while */
910
911     /* shrink the retval's memory block if possible... */
912     p = (char *) allocator.Realloc(retval, strlen(retval) + 1);
913     if (p != NULL)
914         retval = p;
915
916     return(retval);
917 } /* __PHYSFS_platformRealPath */
918
919
920 int __PHYSFS_platformMkDir(const char *path)
921 {
922     WCHAR *wpath;
923     DWORD rc;
924     UTF8_TO_UNICODE_STACK_MACRO(wpath, path);
925     rc = pCreateDirectoryW(wpath, NULL);
926     __PHYSFS_smallFree(wpath);
927     BAIL_IF_MACRO(rc == 0, winApiStrError(), 0);
928     return(1);
929 } /* __PHYSFS_platformMkDir */
930
931
932  /*
933   * Get OS info and save the important parts.
934   *
935   * Returns non-zero if successful, otherwise it returns zero on failure.
936   */
937  static int getOSInfo(void)
938  {
939      OSVERSIONINFO osVerInfo;     /* Information about the OS */
940      osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
941      BAIL_IF_MACRO(!GetVersionEx(&osVerInfo), winApiStrError(), 0);
942      osHasUnicode = (osVerInfo.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS);
943      return(1);
944  } /* getOSInfo */
945
946
947 int __PHYSFS_platformInit(void)
948 {
949     BAIL_IF_MACRO(!getOSInfo(), NULL, 0);
950     BAIL_IF_MACRO(!findApiSymbols(), NULL, 0);
951     BAIL_IF_MACRO(!determineUserDir(), NULL, 0);
952     return(1);  /* It's all good */
953 } /* __PHYSFS_platformInit */
954
955
956 int __PHYSFS_platformDeinit(void)
957 {
958     HANDLE *libs[] = { &libKernel32, &libUserEnv, &libAdvApi32, NULL };
959     int i;
960
961     allocator.Free(userDir);
962     userDir = NULL;
963
964     for (i = 0; libs[i] != NULL; i++)
965     {
966         const HANDLE lib = *(libs[i]);
967         if (lib)
968             FreeLibrary(lib);
969         *(libs[i]) = NULL;
970     } /* for */
971
972     return(1); /* It's all good */
973 } /* __PHYSFS_platformDeinit */
974
975
976 static void *doOpen(const char *fname, DWORD mode, DWORD creation, int rdonly)
977 {
978     HANDLE fileHandle;
979     WinApiFile *retval;
980     WCHAR *wfname;
981
982     UTF8_TO_UNICODE_STACK_MACRO(wfname, fname);
983     BAIL_IF_MACRO(wfname == NULL, ERR_OUT_OF_MEMORY, NULL);
984     fileHandle = pCreateFileW(wfname, mode, FILE_SHARE_READ, NULL,
985                               creation, FILE_ATTRIBUTE_NORMAL, NULL);
986     __PHYSFS_smallFree(wfname);
987
988     BAIL_IF_MACRO
989     (
990         fileHandle == INVALID_HANDLE_VALUE,
991         winApiStrError(), NULL
992     );
993
994     retval = (WinApiFile *) allocator.Malloc(sizeof (WinApiFile));
995     if (retval == NULL)
996     {
997         CloseHandle(fileHandle);
998         BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL);
999     } /* if */
1000
1001     retval->readonly = rdonly;
1002     retval->handle = fileHandle;
1003     return(retval);
1004 } /* doOpen */
1005
1006
1007 void *__PHYSFS_platformOpenRead(const char *filename)
1008 {
1009     return(doOpen(filename, GENERIC_READ, OPEN_EXISTING, 1));
1010 } /* __PHYSFS_platformOpenRead */
1011
1012
1013 void *__PHYSFS_platformOpenWrite(const char *filename)
1014 {
1015     return(doOpen(filename, GENERIC_WRITE, CREATE_ALWAYS, 0));
1016 } /* __PHYSFS_platformOpenWrite */
1017
1018
1019 void *__PHYSFS_platformOpenAppend(const char *filename)
1020 {
1021     void *retval = doOpen(filename, GENERIC_WRITE, OPEN_ALWAYS, 0);
1022     if (retval != NULL)
1023     {
1024         HANDLE h = ((WinApiFile *) retval)->handle;
1025         DWORD rc = SetFilePointer(h, 0, NULL, FILE_END);
1026         if (rc == PHYSFS_INVALID_SET_FILE_POINTER)
1027         {
1028             const char *err = winApiStrError();
1029             CloseHandle(h);
1030             allocator.Free(retval);
1031             BAIL_MACRO(err, NULL);
1032         } /* if */
1033     } /* if */
1034
1035     return(retval);
1036 } /* __PHYSFS_platformOpenAppend */
1037
1038
1039 PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer,
1040                                     PHYSFS_uint32 size, PHYSFS_uint32 count)
1041 {
1042     HANDLE Handle = ((WinApiFile *) opaque)->handle;
1043     DWORD CountOfBytesRead;
1044     PHYSFS_sint64 retval;
1045
1046     /* Read data from the file */
1047     /* !!! FIXME: uint32 might be a greater # than DWORD */
1048     if(!ReadFile(Handle, buffer, count * size, &CountOfBytesRead, NULL))
1049     {
1050         BAIL_MACRO(winApiStrError(), -1);
1051     } /* if */
1052     else
1053     {
1054         /* Return the number of "objects" read. */
1055         /* !!! FIXME: What if not the right amount of bytes was read to make an object? */
1056         retval = CountOfBytesRead / size;
1057     } /* else */
1058
1059     return(retval);
1060 } /* __PHYSFS_platformRead */
1061
1062
1063 PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
1064                                      PHYSFS_uint32 size, PHYSFS_uint32 count)
1065 {
1066     HANDLE Handle = ((WinApiFile *) opaque)->handle;
1067     DWORD CountOfBytesWritten;
1068     PHYSFS_sint64 retval;
1069
1070     /* Read data from the file */
1071     /* !!! FIXME: uint32 might be a greater # than DWORD */
1072     if(!WriteFile(Handle, buffer, count * size, &CountOfBytesWritten, NULL))
1073     {
1074         BAIL_MACRO(winApiStrError(), -1);
1075     } /* if */
1076     else
1077     {
1078         /* Return the number of "objects" read. */
1079         /* !!! FIXME: What if not the right number of bytes was written? */
1080         retval = CountOfBytesWritten / size;
1081     } /* else */
1082
1083     return(retval);
1084 } /* __PHYSFS_platformWrite */
1085
1086
1087 int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
1088 {
1089     HANDLE Handle = ((WinApiFile *) opaque)->handle;
1090     DWORD HighOrderPos;
1091     DWORD *pHighOrderPos;
1092     DWORD rc;
1093
1094     /* Get the high order 32-bits of the position */
1095     HighOrderPos = HIGHORDER_UINT64(pos);
1096
1097     /*
1098      * MSDN: "If you do not need the high-order 32 bits, this
1099      *         pointer must be set to NULL."
1100      */
1101     pHighOrderPos = (HighOrderPos) ? &HighOrderPos : NULL;
1102
1103     /*
1104      * !!! FIXME: MSDN: "Windows Me/98/95:  If the pointer
1105      * !!! FIXME:  lpDistanceToMoveHigh is not NULL, then it must
1106      * !!! FIXME:  point to either 0, INVALID_SET_FILE_POINTER, or
1107      * !!! FIXME:  the sign extension of the value of lDistanceToMove.
1108      * !!! FIXME:  Any other value will be rejected."
1109      */
1110
1111     /* Move pointer "pos" count from start of file */
1112     rc = SetFilePointer(Handle, LOWORDER_UINT64(pos),
1113                         pHighOrderPos, FILE_BEGIN);
1114
1115     if ( (rc == PHYSFS_INVALID_SET_FILE_POINTER) &&
1116          (GetLastError() != NO_ERROR) )
1117     {
1118         BAIL_MACRO(winApiStrError(), 0);
1119     } /* if */
1120     
1121     return(1);  /* No error occured */
1122 } /* __PHYSFS_platformSeek */
1123
1124
1125 PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
1126 {
1127     HANDLE Handle = ((WinApiFile *) opaque)->handle;
1128     DWORD HighPos = 0;
1129     DWORD LowPos;
1130     PHYSFS_sint64 retval;
1131
1132     /* Get current position */
1133     LowPos = SetFilePointer(Handle, 0, &HighPos, FILE_CURRENT);
1134     if ( (LowPos == PHYSFS_INVALID_SET_FILE_POINTER) &&
1135          (GetLastError() != NO_ERROR) )
1136     {
1137         BAIL_MACRO(winApiStrError(), 0);
1138     } /* if */
1139     else
1140     {
1141         /* Combine the high/low order to create the 64-bit position value */
1142         retval = (((PHYSFS_uint64) HighPos) << 32) | LowPos;
1143         assert(retval >= 0);
1144     } /* else */
1145
1146     return(retval);
1147 } /* __PHYSFS_platformTell */
1148
1149
1150 PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
1151 {
1152     HANDLE Handle = ((WinApiFile *) opaque)->handle;
1153     DWORD SizeHigh;
1154     DWORD SizeLow;
1155     PHYSFS_sint64 retval;
1156
1157     SizeLow = GetFileSize(Handle, &SizeHigh);
1158     if ( (SizeLow == PHYSFS_INVALID_SET_FILE_POINTER) &&
1159          (GetLastError() != NO_ERROR) )
1160     {
1161         BAIL_MACRO(winApiStrError(), -1);
1162     } /* if */
1163     else
1164     {
1165         /* Combine the high/low order to create the 64-bit position value */
1166         retval = (((PHYSFS_uint64) SizeHigh) << 32) | SizeLow;
1167         assert(retval >= 0);
1168     } /* else */
1169
1170     return(retval);
1171 } /* __PHYSFS_platformFileLength */
1172
1173
1174 int __PHYSFS_platformEOF(void *opaque)
1175 {
1176     PHYSFS_sint64 FilePosition;
1177     int retval = 0;
1178
1179     /* Get the current position in the file */
1180     if ((FilePosition = __PHYSFS_platformTell(opaque)) != 0)
1181     {
1182         /* Non-zero if EOF is equal to the file length */
1183         retval = FilePosition == __PHYSFS_platformFileLength(opaque);
1184     } /* if */
1185
1186     return(retval);
1187 } /* __PHYSFS_platformEOF */
1188
1189
1190 int __PHYSFS_platformFlush(void *opaque)
1191 {
1192     WinApiFile *fh = ((WinApiFile *) opaque);
1193     if (!fh->readonly)
1194         BAIL_IF_MACRO(!FlushFileBuffers(fh->handle), winApiStrError(), 0);
1195
1196     return(1);
1197 } /* __PHYSFS_platformFlush */
1198
1199
1200 int __PHYSFS_platformClose(void *opaque)
1201 {
1202     HANDLE Handle = ((WinApiFile *) opaque)->handle;
1203     BAIL_IF_MACRO(!CloseHandle(Handle), winApiStrError(), 0);
1204     allocator.Free(opaque);
1205     return(1);
1206 } /* __PHYSFS_platformClose */
1207
1208
1209 static int doPlatformDelete(LPWSTR wpath)
1210 {
1211     /* If filename is a folder */
1212     if (pGetFileAttributesW(wpath) == FILE_ATTRIBUTE_DIRECTORY)
1213     {
1214         BAIL_IF_MACRO(!pRemoveDirectoryW(wpath), winApiStrError(), 0);
1215     } /* if */
1216     else
1217     {
1218         BAIL_IF_MACRO(!pDeleteFileW(wpath), winApiStrError(), 0);
1219     } /* else */
1220
1221     return(1);   /* if you made it here, it worked. */
1222 } /* doPlatformDelete */
1223
1224
1225 int __PHYSFS_platformDelete(const char *path)
1226 {
1227     int retval = 0;
1228     LPWSTR wpath;
1229     UTF8_TO_UNICODE_STACK_MACRO(wpath, path);
1230     BAIL_IF_MACRO(wpath == NULL, ERR_OUT_OF_MEMORY, 0);
1231     retval = doPlatformDelete(wpath);
1232     __PHYSFS_smallFree(wpath);
1233     return(retval);
1234 } /* __PHYSFS_platformDelete */
1235
1236
1237 /*
1238  * !!! FIXME: why aren't we using Critical Sections instead of Mutexes?
1239  * !!! FIXME:  mutexes on Windows are for cross-process sync. CritSects are
1240  * !!! FIXME:  mutexes for threads in a single process and are faster.
1241  */
1242 void *__PHYSFS_platformCreateMutex(void)
1243 {
1244     return((void *) CreateMutex(NULL, FALSE, NULL));
1245 } /* __PHYSFS_platformCreateMutex */
1246
1247
1248 void __PHYSFS_platformDestroyMutex(void *mutex)
1249 {
1250     CloseHandle((HANDLE) mutex);
1251 } /* __PHYSFS_platformDestroyMutex */
1252
1253
1254 int __PHYSFS_platformGrabMutex(void *mutex)
1255 {
1256     return(WaitForSingleObject((HANDLE) mutex, INFINITE) != WAIT_FAILED);
1257 } /* __PHYSFS_platformGrabMutex */
1258
1259
1260 void __PHYSFS_platformReleaseMutex(void *mutex)
1261 {
1262     ReleaseMutex((HANDLE) mutex);
1263 } /* __PHYSFS_platformReleaseMutex */
1264
1265
1266 static PHYSFS_sint64 FileTimeToPhysfsTime(const FILETIME *ft)
1267 {
1268     SYSTEMTIME st_utc;
1269     SYSTEMTIME st_localtz;
1270     TIME_ZONE_INFORMATION tzi;
1271     DWORD tzid;
1272     PHYSFS_sint64 retval;
1273     struct tm tm;
1274
1275     BAIL_IF_MACRO(!FileTimeToSystemTime(ft, &st_utc), winApiStrError(), -1);
1276     tzid = GetTimeZoneInformation(&tzi);
1277     BAIL_IF_MACRO(tzid == TIME_ZONE_ID_INVALID, winApiStrError(), -1);
1278
1279     /* (This API is unsupported and fails on non-NT systems. */
1280     if (!SystemTimeToTzSpecificLocalTime(&tzi, &st_utc, &st_localtz))
1281     {
1282         /* do it by hand. Grumble... */
1283         ULARGE_INTEGER ui64;
1284         FILETIME new_ft;
1285         ui64.LowPart = ft->dwLowDateTime;
1286         ui64.HighPart = ft->dwHighDateTime;
1287
1288         if (tzid == TIME_ZONE_ID_STANDARD)
1289             tzi.Bias += tzi.StandardBias;
1290         else if (tzid == TIME_ZONE_ID_DAYLIGHT)
1291             tzi.Bias += tzi.DaylightBias;
1292
1293         /* convert from minutes to 100-nanosecond increments... */
1294         ui64.QuadPart -= (((LONGLONG) tzi.Bias) * (600000000));
1295
1296         /* Move it back into a FILETIME structure... */
1297         new_ft.dwLowDateTime = ui64.LowPart;
1298         new_ft.dwHighDateTime = ui64.HighPart;
1299
1300         /* Convert to something human-readable... */
1301         if (!FileTimeToSystemTime(&new_ft, &st_localtz))
1302             BAIL_MACRO(winApiStrError(), -1);
1303     } /* if */
1304
1305     /* Convert to a format that mktime() can grok... */
1306     tm.tm_sec = st_localtz.wSecond;
1307     tm.tm_min = st_localtz.wMinute;
1308     tm.tm_hour = st_localtz.wHour;
1309     tm.tm_mday = st_localtz.wDay;
1310     tm.tm_mon = st_localtz.wMonth - 1;
1311     tm.tm_year = st_localtz.wYear - 1900;
1312     tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
1313     tm.tm_yday = -1;
1314     tm.tm_isdst = -1;
1315
1316     /* Convert to a format PhysicsFS can grok... */
1317     retval = (PHYSFS_sint64) mktime(&tm);
1318     BAIL_IF_MACRO(retval == -1, strerror(errno), -1);
1319     return(retval);
1320 } /* FileTimeToPhysfsTime */
1321
1322
1323 PHYSFS_sint64 __PHYSFS_platformGetLastModTime(const char *fname)
1324 {
1325     PHYSFS_sint64 retval = -1;
1326     WIN32_FILE_ATTRIBUTE_DATA attr;
1327     int rc = 0;
1328
1329     memset(&attr, '\0', sizeof (attr));
1330
1331     /* GetFileAttributesEx didn't show up until Win98 and NT4. */
1332     if ((pGetFileAttributesExW != NULL) || (pGetFileAttributesExA != NULL))
1333     {
1334         WCHAR *wstr;
1335         UTF8_TO_UNICODE_STACK_MACRO(wstr, fname);
1336         if (wstr != NULL) /* if NULL, maybe the fallback will work. */
1337         {
1338             if (pGetFileAttributesExW != NULL)  /* NT/XP/Vista/etc system. */
1339                 rc = pGetFileAttributesExW(wstr, GetFileExInfoStandard, &attr);
1340             else  /* Win98/ME system */
1341             {
1342                 const int len = (int) (wStrLen(wstr) + 1);
1343                 char *cp = (char *) __PHYSFS_smallAlloc(len);
1344                 if (cp != NULL)
1345                 {
1346                     WideCharToMultiByte(CP_ACP, 0, wstr, len, cp, len, 0, 0);
1347                     rc = pGetFileAttributesExA(cp, GetFileExInfoStandard, &attr);
1348                     __PHYSFS_smallFree(cp);
1349                 } /* if */
1350             } /* else */
1351             __PHYSFS_smallFree(wstr);
1352         } /* if */
1353     } /* if */
1354
1355     if (rc)  /* had API entry point and it worked. */
1356     {
1357         /* 0 return value indicates an error or not supported */
1358         if ( (attr.ftLastWriteTime.dwHighDateTime != 0) ||
1359              (attr.ftLastWriteTime.dwLowDateTime != 0) )
1360         {
1361             retval = FileTimeToPhysfsTime(&attr.ftLastWriteTime);
1362         } /* if */
1363     } /* if */
1364
1365     /* GetFileTime() has been in the Win32 API since the start. */
1366     if (retval == -1)  /* try a fallback... */
1367     {
1368         FILETIME ft;
1369         BOOL rc;
1370         const char *err;
1371         WinApiFile *f = (WinApiFile *) __PHYSFS_platformOpenRead(fname);
1372         BAIL_IF_MACRO(f == NULL, NULL, -1)
1373         rc = GetFileTime(f->handle, NULL, NULL, &ft);
1374         err = winApiStrError();
1375         CloseHandle(f->handle);
1376         allocator.Free(f);
1377         BAIL_IF_MACRO(!rc, err, -1);
1378         retval = FileTimeToPhysfsTime(&ft);
1379     } /* if */
1380
1381     return(retval);
1382 } /* __PHYSFS_platformGetLastModTime */
1383
1384
1385 /* !!! FIXME: Don't use C runtime for allocators? */
1386 int __PHYSFS_platformSetDefaultAllocator(PHYSFS_Allocator *a)
1387 {
1388     return(0);  /* just use malloc() and friends. */
1389 } /* __PHYSFS_platformSetDefaultAllocator */
1390
1391 #endif  /* PHYSFS_PLATFORM_WINDOWS */
1392
1393 /* end of windows.c ... */
1394
1395