OpenTTD
win32.cpp
Go to the documentation of this file.
1 /* $Id: win32.cpp 27481 2015-12-28 13:16:41Z michi_cc $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
12 #include "../../stdafx.h"
13 #include "../../debug.h"
14 #include "../../gfx_func.h"
15 #include "../../textbuf_gui.h"
16 #include "../../fileio_func.h"
17 #include <windows.h>
18 #include <fcntl.h>
19 #include <regstr.h>
20 #include <shlobj.h> /* SHGetFolderPath */
21 #include <shellapi.h>
22 #include "win32.h"
23 #include "../../fios.h"
24 #include "../../core/alloc_func.hpp"
25 #include "../../openttd.h"
26 #include "../../core/random_func.hpp"
27 #include "../../string_func.h"
28 #include "../../crashlog.h"
29 #include <errno.h>
30 #include <sys/stat.h>
31 
32 /* Due to TCHAR, strncat and strncpy have to remain (for a while). */
33 #include "../../safeguards.h"
34 #undef strncat
35 #undef strncpy
36 
37 static bool _has_console;
38 static bool _cursor_disable = true;
39 static bool _cursor_visible = true;
40 
41 bool MyShowCursor(bool show, bool toggle)
42 {
43  if (toggle) _cursor_disable = !_cursor_disable;
44  if (_cursor_disable) return show;
45  if (_cursor_visible == show) return show;
46 
47  _cursor_visible = show;
48  ShowCursor(show);
49 
50  return !show;
51 }
52 
58 bool LoadLibraryList(Function proc[], const char *dll)
59 {
60  while (*dll != '\0') {
61  HMODULE lib;
62  lib = LoadLibrary(MB_TO_WIDE(dll));
63 
64  if (lib == NULL) return false;
65  for (;;) {
66  FARPROC p;
67 
68  while (*dll++ != '\0') { /* Nothing */ }
69  if (*dll == '\0') break;
70 #if defined(WINCE)
71  p = GetProcAddress(lib, MB_TO_WIDE(dll));
72 #else
73  p = GetProcAddress(lib, dll);
74 #endif
75  if (p == NULL) return false;
76  *proc++ = (Function)p;
77  }
78  dll++;
79  }
80  return true;
81 }
82 
83 void ShowOSErrorBox(const char *buf, bool system)
84 {
85  MyShowCursor(true);
86  MessageBox(GetActiveWindow(), OTTD2FS(buf), _T("Error!"), MB_ICONSTOP);
87 }
88 
89 void OSOpenBrowser(const char *url)
90 {
91  ShellExecute(GetActiveWindow(), _T("open"), OTTD2FS(url), NULL, NULL, SW_SHOWNORMAL);
92 }
93 
94 /* Code below for windows version of opendir/readdir/closedir copied and
95  * modified from Jan Wassenberg's GPL implementation posted over at
96  * http://www.gamedev.net/community/forums/topic.asp?topic_id=364584&whichpage=1&#2398903 */
97 
98 struct DIR {
99  HANDLE hFind;
100  /* the dirent returned by readdir.
101  * note: having only one global instance is not possible because
102  * multiple independent opendir/readdir sequences must be supported. */
103  dirent ent;
104  WIN32_FIND_DATA fd;
105  /* since opendir calls FindFirstFile, we need a means of telling the
106  * first call to readdir that we already have a file.
107  * that's the case iff this is true */
108  bool at_first_entry;
109 };
110 
111 /* suballocator - satisfies most requests with a reusable static instance.
112  * this avoids hundreds of alloc/free which would fragment the heap.
113  * To guarantee concurrency, we fall back to malloc if the instance is
114  * already in use (it's important to avoid surprises since this is such a
115  * low-level routine). */
116 static DIR _global_dir;
117 static LONG _global_dir_is_in_use = false;
118 
119 static inline DIR *dir_calloc()
120 {
121  DIR *d;
122 
123  if (InterlockedExchange(&_global_dir_is_in_use, true) == (LONG)true) {
124  d = CallocT<DIR>(1);
125  } else {
126  d = &_global_dir;
127  memset(d, 0, sizeof(*d));
128  }
129  return d;
130 }
131 
132 static inline void dir_free(DIR *d)
133 {
134  if (d == &_global_dir) {
135  _global_dir_is_in_use = (LONG)false;
136  } else {
137  free(d);
138  }
139 }
140 
141 DIR *opendir(const TCHAR *path)
142 {
143  DIR *d;
144  UINT sem = SetErrorMode(SEM_FAILCRITICALERRORS); // disable 'no-disk' message box
145  DWORD fa = GetFileAttributes(path);
146 
147  if ((fa != INVALID_FILE_ATTRIBUTES) && (fa & FILE_ATTRIBUTE_DIRECTORY)) {
148  d = dir_calloc();
149  if (d != NULL) {
150  TCHAR search_path[MAX_PATH];
151  bool slash = path[_tcslen(path) - 1] == '\\';
152 
153  /* build search path for FindFirstFile, try not to append additional slashes
154  * as it throws Win9x off its groove for root directories */
155  _sntprintf(search_path, lengthof(search_path), _T("%s%s*"), path, slash ? _T("") : _T("\\"));
156  *lastof(search_path) = '\0';
157  d->hFind = FindFirstFile(search_path, &d->fd);
158 
159  if (d->hFind != INVALID_HANDLE_VALUE ||
160  GetLastError() == ERROR_NO_MORE_FILES) { // the directory is empty
161  d->ent.dir = d;
162  d->at_first_entry = true;
163  } else {
164  dir_free(d);
165  d = NULL;
166  }
167  } else {
168  errno = ENOMEM;
169  }
170  } else {
171  /* path not found or not a directory */
172  d = NULL;
173  errno = ENOENT;
174  }
175 
176  SetErrorMode(sem); // restore previous setting
177  return d;
178 }
179 
180 struct dirent *readdir(DIR *d)
181 {
182  DWORD prev_err = GetLastError(); // avoid polluting last error
183 
184  if (d->at_first_entry) {
185  /* the directory was empty when opened */
186  if (d->hFind == INVALID_HANDLE_VALUE) return NULL;
187  d->at_first_entry = false;
188  } else if (!FindNextFile(d->hFind, &d->fd)) { // determine cause and bail
189  if (GetLastError() == ERROR_NO_MORE_FILES) SetLastError(prev_err);
190  return NULL;
191  }
192 
193  /* This entry has passed all checks; return information about it.
194  * (note: d_name is a pointer; see struct dirent definition) */
195  d->ent.d_name = d->fd.cFileName;
196  return &d->ent;
197 }
198 
199 int closedir(DIR *d)
200 {
201  FindClose(d->hFind);
202  dir_free(d);
203  return 0;
204 }
205 
206 bool FiosIsRoot(const char *file)
207 {
208  return file[3] == '\0'; // C:\...
209 }
210 
211 void FiosGetDrives()
212 {
213 #if defined(WINCE)
214  /* WinCE only knows one drive: / */
215  FiosItem *fios = _fios_items.Append();
216  fios->type = FIOS_TYPE_DRIVE;
217  fios->mtime = 0;
218  seprintf(fios->name, lastof(fios->name), PATHSEP "");
219  strecpy(fios->title, fios->name, lastof(fios->title));
220 #else
221  TCHAR drives[256];
222  const TCHAR *s;
223 
224  GetLogicalDriveStrings(lengthof(drives), drives);
225  for (s = drives; *s != '\0';) {
226  FiosItem *fios = _fios_items.Append();
227  fios->type = FIOS_TYPE_DRIVE;
228  fios->mtime = 0;
229  seprintf(fios->name, lastof(fios->name), "%c:", s[0] & 0xFF);
230  strecpy(fios->title, fios->name, lastof(fios->title));
231  while (*s++ != '\0') { /* Nothing */ }
232  }
233 #endif
234 }
235 
236 bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb)
237 {
238  /* hectonanoseconds between Windows and POSIX epoch */
239  static const int64 posix_epoch_hns = 0x019DB1DED53E8000LL;
240  const WIN32_FIND_DATA *fd = &ent->dir->fd;
241 
242  sb->st_size = ((uint64) fd->nFileSizeHigh << 32) + fd->nFileSizeLow;
243  /* UTC FILETIME to seconds-since-1970 UTC
244  * we just have to subtract POSIX epoch and scale down to units of seconds.
245  * http://www.gamedev.net/community/forums/topic.asp?topic_id=294070&whichpage=1&#1860504
246  * XXX - not entirely correct, since filetimes on FAT aren't UTC but local,
247  * this won't entirely be correct, but we use the time only for comparison. */
248  sb->st_mtime = (time_t)((*(const uint64*)&fd->ftLastWriteTime - posix_epoch_hns) / 1E7);
249  sb->st_mode = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG;
250 
251  return true;
252 }
253 
254 bool FiosIsHiddenFile(const struct dirent *ent)
255 {
256  return (ent->dir->fd.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0;
257 }
258 
259 bool FiosGetDiskFreeSpace(const char *path, uint64 *tot)
260 {
261  UINT sem = SetErrorMode(SEM_FAILCRITICALERRORS); // disable 'no-disk' message box
262  bool retval = false;
263  TCHAR root[4];
264  DWORD spc, bps, nfc, tnc;
265 
266  _sntprintf(root, lengthof(root), _T("%c:") _T(PATHSEP), path[0]);
267  if (tot != NULL && GetDiskFreeSpace(root, &spc, &bps, &nfc, &tnc)) {
268  *tot = ((spc * bps) * (uint64)nfc);
269  retval = true;
270  }
271 
272  SetErrorMode(sem); // reset previous setting
273  return retval;
274 }
275 
276 static int ParseCommandLine(char *line, char **argv, int max_argc)
277 {
278  int n = 0;
279 
280  do {
281  /* skip whitespace */
282  while (*line == ' ' || *line == '\t') line++;
283 
284  /* end? */
285  if (*line == '\0') break;
286 
287  /* special handling when quoted */
288  if (*line == '"') {
289  argv[n++] = ++line;
290  while (*line != '"') {
291  if (*line == '\0') return n;
292  line++;
293  }
294  } else {
295  argv[n++] = line;
296  while (*line != ' ' && *line != '\t') {
297  if (*line == '\0') return n;
298  line++;
299  }
300  }
301  *line++ = '\0';
302  } while (n != max_argc);
303 
304  return n;
305 }
306 
307 void CreateConsole()
308 {
309 #if defined(WINCE)
310  /* WinCE doesn't support console stuff */
311 #else
312  HANDLE hand;
313  CONSOLE_SCREEN_BUFFER_INFO coninfo;
314 
315  if (_has_console) return;
316  _has_console = true;
317 
318  AllocConsole();
319 
320  hand = GetStdHandle(STD_OUTPUT_HANDLE);
321  GetConsoleScreenBufferInfo(hand, &coninfo);
322  coninfo.dwSize.Y = 500;
323  SetConsoleScreenBufferSize(hand, coninfo.dwSize);
324 
325  /* redirect unbuffered STDIN, STDOUT, STDERR to the console */
326 #if !defined(__CYGWIN__)
327 
328  /* Check if we can open a handle to STDOUT. */
329  int fd = _open_osfhandle((intptr_t)hand, _O_TEXT);
330  if (fd == -1) {
331  /* Free everything related to the console. */
332  FreeConsole();
333  _has_console = false;
334  _close(fd);
335  CloseHandle(hand);
336 
337  ShowInfo("Unable to open an output handle to the console. Check known-bugs.txt for details.");
338  return;
339  }
340 
341 #if defined(_MSC_VER) && _MSC_VER >= 1900
342  freopen("CONOUT$", "a", stdout);
343  freopen("CONIN$", "r", stdin);
344  freopen("CONOUT$", "a", stderr);
345 #else
346  *stdout = *_fdopen(fd, "w");
347  *stdin = *_fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT), "r" );
348  *stderr = *_fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT), "w" );
349 #endif
350 
351 #else
352  /* open_osfhandle is not in cygwin */
353  *stdout = *fdopen(1, "w" );
354  *stdin = *fdopen(0, "r" );
355  *stderr = *fdopen(2, "w" );
356 #endif
357 
358  setvbuf(stdin, NULL, _IONBF, 0);
359  setvbuf(stdout, NULL, _IONBF, 0);
360  setvbuf(stderr, NULL, _IONBF, 0);
361 #endif
362 }
363 
365 static const char *_help_msg;
366 
368 static INT_PTR CALLBACK HelpDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
369 {
370  switch (msg) {
371  case WM_INITDIALOG: {
372  char help_msg[8192];
373  const char *p = _help_msg;
374  char *q = help_msg;
375  while (q != lastof(help_msg) && *p != '\0') {
376  if (*p == '\n') {
377  *q++ = '\r';
378  if (q == lastof(help_msg)) {
379  q[-1] = '\0';
380  break;
381  }
382  }
383  *q++ = *p++;
384  }
385  *q = '\0';
386  /* We need to put the text in a separate buffer because the default
387  * buffer in OTTD2FS might not be large enough (512 chars). */
388  TCHAR help_msg_buf[8192];
389  SetDlgItemText(wnd, 11, convert_to_fs(help_msg, help_msg_buf, lengthof(help_msg_buf)));
390  SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
391  } return TRUE;
392 
393  case WM_COMMAND:
394  if (wParam == 12) ExitProcess(0);
395  return TRUE;
396  case WM_CLOSE:
397  ExitProcess(0);
398  }
399 
400  return FALSE;
401 }
402 
403 void ShowInfo(const char *str)
404 {
405  if (_has_console) {
406  fprintf(stderr, "%s\n", str);
407  } else {
408  bool old;
409  ReleaseCapture();
411 
412  old = MyShowCursor(true);
413  if (strlen(str) > 2048) {
414  /* The minimum length of the help message is 2048. Other messages sent via
415  * ShowInfo are much shorter, or so long they need this way of displaying
416  * them anyway. */
417  _help_msg = str;
418  DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(101), NULL, HelpDialogFunc);
419  } else {
420  /* We need to put the text in a separate buffer because the default
421  * buffer in OTTD2FS might not be large enough (512 chars). */
422  TCHAR help_msg_buf[8192];
423  MessageBox(GetActiveWindow(), convert_to_fs(str, help_msg_buf, lengthof(help_msg_buf)), _T("OpenTTD"), MB_ICONINFORMATION | MB_OK);
424  }
425  MyShowCursor(old);
426  }
427 }
428 
429 #if defined(WINCE)
430 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
431 #else
432 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
433 #endif
434 {
435  int argc;
436  char *argv[64]; // max 64 command line arguments
437 
439 
440 #if defined(UNICODE) && !defined(WINCE)
441  /* Check if a win9x user started the win32 version */
442  if (HasBit(GetVersion(), 31)) usererror("This version of OpenTTD doesn't run on windows 95/98/ME.\nPlease download the win9x binary and try again.");
443 #endif
444 
445  /* Convert the command line to UTF-8. We need a dedicated buffer
446  * for this because argv[] points into this buffer and this needs to
447  * be available between subsequent calls to FS2OTTD(). */
448  char *cmdline = stredup(FS2OTTD(GetCommandLine()));
449 
450 #if defined(_DEBUG)
451  CreateConsole();
452 #endif
453 
454 #if !defined(WINCE)
455  _set_error_mode(_OUT_TO_MSGBOX); // force assertion output to messagebox
456 #endif
457 
458  /* setup random seed to something quite random */
459  SetRandomSeed(GetTickCount());
460 
461  argc = ParseCommandLine(cmdline, argv, lengthof(argv));
462 
463  /* Make sure our arguments contain only valid UTF-8 characters. */
464  for (int i = 0; i < argc; i++) ValidateString(argv[i]);
465 
466  openttd_main(argc, argv);
467  free(cmdline);
468  return 0;
469 }
470 
471 #if defined(WINCE)
472 void GetCurrentDirectoryW(int length, wchar_t *path)
473 {
474  /* Get the name of this module */
475  GetModuleFileName(NULL, path, length);
476 
477  /* Remove the executable name, this we call CurrentDir */
478  wchar_t *pDest = wcsrchr(path, '\\');
479  if (pDest != NULL) {
480  int result = pDest - path + 1;
481  path[result] = '\0';
482  }
483 }
484 #endif
485 
486 char *getcwd(char *buf, size_t size)
487 {
488 #if defined(WINCE)
489  TCHAR path[MAX_PATH];
490  GetModuleFileName(NULL, path, MAX_PATH);
491  convert_from_fs(path, buf, size);
492  /* GetModuleFileName returns dir with file, so remove everything behind latest '\\' */
493  char *p = strrchr(buf, '\\');
494  if (p != NULL) *p = '\0';
495 #else
496  TCHAR path[MAX_PATH];
497  GetCurrentDirectory(MAX_PATH - 1, path);
498  convert_from_fs(path, buf, size);
499 #endif
500  return buf;
501 }
502 
503 
504 void DetermineBasePaths(const char *exe)
505 {
506  char tmp[MAX_PATH];
507  TCHAR path[MAX_PATH];
508 #ifdef WITH_PERSONAL_DIR
509  if (SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path))) {
510  strecpy(tmp, FS2OTTD(path), lastof(tmp));
511  AppendPathSeparator(tmp, lastof(tmp));
512  strecat(tmp, PERSONAL_DIR, lastof(tmp));
513  AppendPathSeparator(tmp, lastof(tmp));
515  } else {
517  }
518 
519  if (SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path))) {
520  strecpy(tmp, FS2OTTD(path), lastof(tmp));
521  AppendPathSeparator(tmp, lastof(tmp));
522  strecat(tmp, PERSONAL_DIR, lastof(tmp));
523  AppendPathSeparator(tmp, lastof(tmp));
525  } else {
526  _searchpaths[SP_SHARED_DIR] = NULL;
527  }
528 #else
530  _searchpaths[SP_SHARED_DIR] = NULL;
531 #endif
532 
533  /* Get the path to working directory of OpenTTD */
534  getcwd(tmp, lengthof(tmp));
535  AppendPathSeparator(tmp, lastof(tmp));
537 
538  if (!GetModuleFileName(NULL, path, lengthof(path))) {
539  DEBUG(misc, 0, "GetModuleFileName failed (%lu)\n", GetLastError());
540  _searchpaths[SP_BINARY_DIR] = NULL;
541  } else {
542  TCHAR exec_dir[MAX_PATH];
543  _tcsncpy(path, convert_to_fs(exe, path, lengthof(path)), lengthof(path));
544  if (!GetFullPathName(path, lengthof(exec_dir), exec_dir, NULL)) {
545  DEBUG(misc, 0, "GetFullPathName failed (%lu)\n", GetLastError());
546  _searchpaths[SP_BINARY_DIR] = NULL;
547  } else {
548  strecpy(tmp, convert_from_fs(exec_dir, tmp, lengthof(tmp)), lastof(tmp));
549  char *s = strrchr(tmp, PATHSEPCHAR);
550  *(s + 1) = '\0';
552  }
553  }
554 
557 }
558 
559 
560 bool GetClipboardContents(char *buffer, const char *last)
561 {
562  HGLOBAL cbuf;
563  const char *ptr;
564 
565  if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
566  OpenClipboard(NULL);
567  cbuf = GetClipboardData(CF_UNICODETEXT);
568 
569  ptr = (const char*)GlobalLock(cbuf);
570  int out_len = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)ptr, -1, buffer, (last - buffer) + 1, NULL, NULL);
571  GlobalUnlock(cbuf);
572  CloseClipboard();
573 
574  if (out_len == 0) return false;
575 #if !defined(UNICODE)
576  } else if (IsClipboardFormatAvailable(CF_TEXT)) {
577  OpenClipboard(NULL);
578  cbuf = GetClipboardData(CF_TEXT);
579 
580  ptr = (const char*)GlobalLock(cbuf);
581  strecpy(buffer, FS2OTTD(ptr), last);
582 
583  GlobalUnlock(cbuf);
584  CloseClipboard();
585 #endif /* UNICODE */
586  } else {
587  return false;
588  }
589 
590  return true;
591 }
592 
593 
594 void CSleep(int milliseconds)
595 {
596  Sleep(milliseconds);
597 }
598 
599 
613 const char *FS2OTTD(const TCHAR *name)
614 {
615  static char utf8_buf[512];
616  return convert_from_fs(name, utf8_buf, lengthof(utf8_buf));
617 }
618 
631 const TCHAR *OTTD2FS(const char *name, bool console_cp)
632 {
633  static TCHAR system_buf[512];
634  return convert_to_fs(name, system_buf, lengthof(system_buf), console_cp);
635 }
636 
637 
646 char *convert_from_fs(const TCHAR *name, char *utf8_buf, size_t buflen)
647 {
648 #if defined(UNICODE)
649  const WCHAR *wide_buf = name;
650 #else
651  /* Convert string from the local codepage to UTF-16. */
652  int wide_len = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0);
653  if (wide_len == 0) {
654  utf8_buf[0] = '\0';
655  return utf8_buf;
656  }
657 
658  WCHAR *wide_buf = AllocaM(WCHAR, wide_len);
659  MultiByteToWideChar(CP_ACP, 0, name, -1, wide_buf, wide_len);
660 #endif
661 
662  /* Convert UTF-16 string to UTF-8. */
663  int len = WideCharToMultiByte(CP_UTF8, 0, wide_buf, -1, utf8_buf, (int)buflen, NULL, NULL);
664  if (len == 0) utf8_buf[0] = '\0';
665 
666  return utf8_buf;
667 }
668 
669 
680 TCHAR *convert_to_fs(const char *name, TCHAR *system_buf, size_t buflen, bool console_cp)
681 {
682 #if defined(UNICODE)
683  int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, system_buf, (int)buflen);
684  if (len == 0) system_buf[0] = '\0';
685 #else
686  int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
687  if (len == 0) {
688  system_buf[0] = '\0';
689  return system_buf;
690  }
691 
692  WCHAR *wide_buf = AllocaM(WCHAR, len);
693  MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_buf, len);
694 
695  len = WideCharToMultiByte(console_cp ? CP_OEMCP : CP_ACP, 0, wide_buf, len, system_buf, (int)buflen, NULL, NULL);
696  if (len == 0) system_buf[0] = '\0';
697 #endif
698 
699  return system_buf;
700 }
701 
708 HRESULT OTTDSHGetFolderPath(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)
709 {
710  static HRESULT (WINAPI *SHGetFolderPath)(HWND, int, HANDLE, DWORD, LPTSTR) = NULL;
711  static bool first_time = true;
712 
713  /* We only try to load the library one time; if it fails, it fails */
714  if (first_time) {
715 #if defined(UNICODE)
716 # define W(x) x "W"
717 #else
718 # define W(x) x "A"
719 #endif
720  /* The function lives in shell32.dll for all current Windows versions, but it first started to appear in SHFolder.dll. */
721  if (!LoadLibraryList((Function*)&SHGetFolderPath, "shell32.dll\0" W("SHGetFolderPath") "\0\0")) {
722  if (!LoadLibraryList((Function*)&SHGetFolderPath, "SHFolder.dll\0" W("SHGetFolderPath") "\0\0")) {
723  DEBUG(misc, 0, "Unable to load " W("SHGetFolderPath") "from either shell32.dll or SHFolder.dll");
724  }
725  }
726 #undef W
727  first_time = false;
728  }
729 
730  if (SHGetFolderPath != NULL) return SHGetFolderPath(hwnd, csidl, hToken, dwFlags, pszPath);
731 
732  /* SHGetFolderPath doesn't exist, try a more conservative approach,
733  * eg environment variables. This is only included for legacy modes
734  * MSDN says: that 'pszPath' is a "Pointer to a null-terminated string of
735  * length MAX_PATH which will receive the path" so let's assume that
736  * Windows 95 with Internet Explorer 5.0, Windows 98 with Internet Explorer 5.0,
737  * Windows 98 Second Edition (SE), Windows NT 4.0 with Internet Explorer 5.0,
738  * Windows NT 4.0 with Service Pack 4 (SP4) */
739  {
740  DWORD ret;
741  switch (csidl) {
742  case CSIDL_FONTS: // Get the system font path, eg %WINDIR%\Fonts
743  ret = GetEnvironmentVariable(_T("WINDIR"), pszPath, MAX_PATH);
744  if (ret == 0) break;
745  _tcsncat(pszPath, _T("\\Fonts"), MAX_PATH);
746 
747  return (HRESULT)0;
748 
749  case CSIDL_PERSONAL:
750  case CSIDL_COMMON_DOCUMENTS: {
751  HKEY key;
752  if (RegOpenKeyEx(csidl == CSIDL_PERSONAL ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, REGSTR_PATH_SPECIAL_FOLDERS, 0, KEY_READ, &key) != ERROR_SUCCESS) break;
753  DWORD len = MAX_PATH;
754  ret = RegQueryValueEx(key, csidl == CSIDL_PERSONAL ? _T("Personal") : _T("Common Documents"), NULL, NULL, (LPBYTE)pszPath, &len);
755  RegCloseKey(key);
756  if (ret == ERROR_SUCCESS) return (HRESULT)0;
757  break;
758  }
759 
760  /* XXX - other types to go here when needed... */
761  }
762  }
763 
764  return E_INVALIDARG;
765 }
766 
768 const char *GetCurrentLocale(const char *)
769 {
770  char lang[9], country[9];
771  if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lang, lengthof(lang)) == 0 ||
772  GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, country, lengthof(country)) == 0) {
773  /* Unable to retrieve the locale. */
774  return NULL;
775  }
776  /* Format it as 'en_us'. */
777  static char retbuf[6] = {lang[0], lang[1], '_', country[0], country[1], 0};
778  return retbuf;
779 }
780 
782 {
783  SYSTEM_INFO info;
784 
785  GetSystemInfo(&info);
786  return info.dwNumberOfProcessors;
787 }