00001
00002
00005 #include "stdafx.h"
00006 #include "openttd.h"
00007 #include "debug.h"
00008 #include "saveload/saveload.h"
00009 #include "gfx_func.h"
00010 #include "textbuf_gui.h"
00011 #include "fileio_func.h"
00012 #include "fios.h"
00013 #include "rev.h"
00014 #include <windows.h>
00015 #include <winnt.h>
00016 #include <wininet.h>
00017 #include <fcntl.h>
00018 #include <shlobj.h>
00019 #include "variables.h"
00020 #include "win32.h"
00021 #include "core/alloc_func.hpp"
00022 #include "functions.h"
00023 #include "core/random_func.hpp"
00024 #include "core/bitmath_func.hpp"
00025 #include "string_func.h"
00026 #include "gamelog.h"
00027 #include <ctype.h>
00028 #include <errno.h>
00029 #include <sys/types.h>
00030 #include <sys/stat.h>
00031 #if defined(_MSC_VER) && !defined(WINCE)
00032 #include <dbghelp.h>
00033 #include "strings_func.h"
00034 #endif
00035
00036 static bool _has_console;
00037
00038 static bool cursor_visible = true;
00039
00040 bool MyShowCursor(bool show)
00041 {
00042 if (cursor_visible == show) return show;
00043
00044 cursor_visible = show;
00045 ShowCursor(show);
00046
00047 return !show;
00048 }
00049
00053 bool LoadLibraryList(Function proc[], const char *dll)
00054 {
00055 while (*dll != '\0') {
00056 HMODULE lib;
00057 lib = LoadLibrary(MB_TO_WIDE(dll));
00058
00059 if (lib == NULL) return false;
00060 for (;;) {
00061 FARPROC p;
00062
00063 while (*dll++ != '\0') { }
00064 if (*dll == '\0') break;
00065 #if defined(WINCE)
00066 p = GetProcAddress(lib, MB_TO_WIDE(dll));
00067 #else
00068 p = GetProcAddress(lib, dll);
00069 #endif
00070 if (p == NULL) return false;
00071 *proc++ = (Function)p;
00072 }
00073 dll++;
00074 }
00075 return true;
00076 }
00077
00078 #ifdef _MSC_VER
00079 static const char *_exception_string = NULL;
00080 void SetExceptionString(const char *s, ...)
00081 {
00082 va_list va;
00083 char buf[512];
00084
00085 va_start(va, s);
00086 vsnprintf(buf, lengthof(buf), s, va);
00087 va_end(va);
00088
00089 _exception_string = strdup(buf);
00090 }
00091 #endif
00092
00093 void ShowOSErrorBox(const char *buf, bool system)
00094 {
00095 MyShowCursor(true);
00096 MessageBox(GetActiveWindow(), MB_TO_WIDE(buf), _T("Error!"), MB_ICONSTOP);
00097
00098
00099 #if defined(WIN32_EXCEPTION_TRACKER) && !defined(_DEBUG)
00100 if (system) {
00101 _exception_string = buf;
00102 *(byte*)0 = 0;
00103 }
00104 #endif
00105 }
00106
00107 #if defined(_MSC_VER) && !defined(WINCE)
00108
00109 static void *_safe_esp;
00110 static char *_crash_msg;
00111 static bool _expanded;
00112 static bool _did_emerg_save;
00113 static int _ident;
00114
00115 struct DebugFileInfo {
00116 uint32 size;
00117 uint32 crc32;
00118 SYSTEMTIME file_time;
00119 };
00120
00121 static uint32 *_crc_table;
00122
00123 static void MakeCRCTable(uint32 *table)
00124 {
00125 uint32 crc, poly = 0xEDB88320L;
00126 int i;
00127 int j;
00128
00129 _crc_table = table;
00130
00131 for (i = 0; i != 256; i++) {
00132 crc = i;
00133 for (j = 8; j != 0; j--) {
00134 crc = (crc & 1 ? (crc >> 1) ^ poly : crc >> 1);
00135 }
00136 table[i] = crc;
00137 }
00138 }
00139
00140 static uint32 CalcCRC(byte *data, uint size, uint32 crc)
00141 {
00142 for (; size > 0; size--) {
00143 crc = ((crc >> 8) & 0x00FFFFFF) ^ _crc_table[(crc ^ *data++) & 0xFF];
00144 }
00145 return crc;
00146 }
00147
00148 static void GetFileInfo(DebugFileInfo *dfi, const TCHAR *filename)
00149 {
00150 HANDLE file;
00151 memset(dfi, 0, sizeof(*dfi));
00152
00153 file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
00154 if (file != INVALID_HANDLE_VALUE) {
00155 byte buffer[1024];
00156 DWORD numread;
00157 uint32 filesize = 0;
00158 FILETIME write_time;
00159 uint32 crc = (uint32)-1;
00160
00161 for (;;) {
00162 if (ReadFile(file, buffer, sizeof(buffer), &numread, NULL) == 0 || numread == 0)
00163 break;
00164 filesize += numread;
00165 crc = CalcCRC(buffer, numread, crc);
00166 }
00167 dfi->size = filesize;
00168 dfi->crc32 = crc ^ (uint32)-1;
00169
00170 if (GetFileTime(file, NULL, NULL, &write_time)) {
00171 FileTimeToSystemTime(&write_time, &dfi->file_time);
00172 }
00173 CloseHandle(file);
00174 }
00175 }
00176
00177
00178 static char *PrintModuleInfo(char *output, const char *last, HMODULE mod)
00179 {
00180 TCHAR buffer[MAX_PATH];
00181 DebugFileInfo dfi;
00182
00183 GetModuleFileName(mod, buffer, MAX_PATH);
00184 GetFileInfo(&dfi, buffer);
00185 output += seprintf(output, last, " %-20s handle: %p size: %d crc: %.8X date: %d-%.2d-%.2d %.2d:%.2d:%.2d\r\n",
00186 WIDE_TO_MB(buffer),
00187 mod,
00188 dfi.size,
00189 dfi.crc32,
00190 dfi.file_time.wYear,
00191 dfi.file_time.wMonth,
00192 dfi.file_time.wDay,
00193 dfi.file_time.wHour,
00194 dfi.file_time.wMinute,
00195 dfi.file_time.wSecond
00196 );
00197 return output;
00198 }
00199
00200 static char *PrintModuleList(char *output, const char *last)
00201 {
00202 BOOL (WINAPI *EnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD);
00203
00204 if (LoadLibraryList((Function*)&EnumProcessModules, "psapi.dll\0EnumProcessModules\0\0")) {
00205 HMODULE modules[100];
00206 DWORD needed;
00207 BOOL res;
00208
00209 HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
00210 if (proc != NULL) {
00211 res = EnumProcessModules(proc, modules, sizeof(modules), &needed);
00212 CloseHandle(proc);
00213 if (res) {
00214 size_t count = min(needed / sizeof(HMODULE), lengthof(modules));
00215
00216 for (size_t i = 0; i != count; i++) output = PrintModuleInfo(output, last, modules[i]);
00217 return output;
00218 }
00219 }
00220 }
00221 output = PrintModuleInfo(output, last, NULL);
00222 return output;
00223 }
00224
00225 static const TCHAR _crash_desc[] =
00226 _T("A serious fault condition occured in the game. The game will shut down.\n")
00227 _T("Please send the crash information and the crash.dmp file (if any) to the developers.\n")
00228 _T("This will greatly help debugging. The correct place to do this is http:
00229 _T("The information contained in the report is displayed below.\n")
00230 _T("Press \"Emergency save\" to attempt saving the game.");
00231
00232 static const TCHAR _save_succeeded[] =
00233 _T("Emergency save succeeded.\n")
00234 _T("Be aware that critical parts of the internal game state may have become ")
00235 _T("corrupted. The saved game is not guaranteed to work.");
00236
00237 static bool EmergencySave()
00238 {
00239 SaveOrLoad("crash.sav", SL_SAVE, BASE_DIR);
00240 return true;
00241 }
00242
00243
00244 #if 0
00245
00246 struct WinInetProcs {
00247 HINTERNET (WINAPI *InternetOpen)(LPCTSTR, DWORD, LPCTSTR, LPCTSTR, DWORD);
00248 HINTERNET (WINAPI *InternetConnect)(HINTERNET, LPCTSTR, INTERNET_PORT, LPCTSTR, LPCTSTR, DWORD, DWORD, DWORD);
00249 HINTERNET (WINAPI *HttpOpenRequest)(HINTERNET, LPCTSTR, LPCTSTR, LPCTSTR, LPCTSTR, LPCTSTR *, DWORD, DWORD);
00250 BOOL (WINAPI *HttpSendRequest)(HINTERNET, LPCTSTR, DWORD, LPVOID, DWORD);
00251 BOOL (WINAPI *InternetCloseHandle)(HINTERNET);
00252 BOOL (WINAPI *HttpQueryInfo)(HINTERNET, DWORD, LPVOID, LPDWORD, LPDWORD);
00253 };
00254
00255 #define M(x) x "\0"
00256 #if defined(UNICODE)
00257 # define W(x) x "W"
00258 #else
00259 # define W(x) x "A"
00260 #endif
00261 static const char wininet_files[] =
00262 M("wininet.dll")
00263 M(W("InternetOpen"))
00264 M(W("InternetConnect"))
00265 M(W("HttpOpenRequest"))
00266 M(W("HttpSendRequest"))
00267 M("InternetCloseHandle")
00268 M(W("HttpQueryInfo"))
00269 M("");
00270 #undef W
00271 #undef M
00272
00273 static WinInetProcs _wininet;
00274
00275 static const TCHAR *SubmitCrashReport(HWND wnd, void *msg, size_t msglen, const TCHAR *arg)
00276 {
00277 HINTERNET inet, conn, http;
00278 const TCHAR *err = NULL;
00279 DWORD code, len;
00280 static TCHAR buf[100];
00281 TCHAR buff[100];
00282
00283 if (_wininet.InternetOpen == NULL && !LoadLibraryList((Function*)&_wininet, wininet_files)) return _T("can't load wininet.dll");
00284
00285 inet = _wininet.InternetOpen(_T("OTTD"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0 );
00286 if (inet == NULL) { err = _T("internetopen failed"); goto error1; }
00287
00288 conn = _wininet.InternetConnect(inet, _T("www.openttd.org"), INTERNET_DEFAULT_HTTP_PORT, _T(""), _T(""), INTERNET_SERVICE_HTTP, 0, 0);
00289 if (conn == NULL) { err = _T("internetconnect failed"); goto error2; }
00290
00291 _sntprintf(buff, lengthof(buff), _T("/crash.php?file=%s&ident=%d"), arg, _ident);
00292
00293 http = _wininet.HttpOpenRequest(conn, _T("POST"), buff, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE , 0);
00294 if (http == NULL) { err = _T("httpopenrequest failed"); goto error3; }
00295
00296 if (!_wininet.HttpSendRequest(http, _T("Content-type: application/binary"), -1, msg, (DWORD)msglen)) { err = _T("httpsendrequest failed"); goto error4; }
00297
00298 len = sizeof(code);
00299 if (!_wininet.HttpQueryInfo(http, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &code, &len, 0)) { err = _T("httpqueryinfo failed"); goto error4; }
00300
00301 if (code != 200) {
00302 int l = _sntprintf(buf, lengthof(buf), _T("Server said: %d "), code);
00303 len = sizeof(buf) - l;
00304 _wininet.HttpQueryInfo(http, HTTP_QUERY_STATUS_TEXT, buf + l, &len, 0);
00305 err = buf;
00306 }
00307
00308 error4:
00309 _wininet.InternetCloseHandle(http);
00310 error3:
00311 _wininet.InternetCloseHandle(conn);
00312 error2:
00313 _wininet.InternetCloseHandle(inet);
00314 error1:
00315 return err;
00316 }
00317
00318 static void SubmitFile(HWND wnd, const TCHAR *file)
00319 {
00320 HANDLE h;
00321 unsigned long size;
00322 unsigned long read;
00323 void *mem;
00324
00325 h = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
00326 if (h == NULL) return;
00327
00328 size = GetFileSize(h, NULL);
00329 if (size > 500000) goto error1;
00330
00331 mem = MallocT<byte>(size);
00332 if (mem == NULL) goto error1;
00333
00334 if (!ReadFile(h, mem, size, &read, NULL) || read != size) goto error2;
00335
00336 SubmitCrashReport(wnd, mem, size, file);
00337
00338 error2:
00339 free(mem);
00340 error1:
00341 CloseHandle(h);
00342 }
00343
00344 #endif
00345
00346 static const TCHAR * const _expand_texts[] = {_T("S&how report >>"), _T("&Hide report <<") };
00347
00348 static void SetWndSize(HWND wnd, int mode)
00349 {
00350 RECT r, r2;
00351 int offs;
00352
00353 GetWindowRect(wnd, &r);
00354
00355 SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
00356
00357 if (mode >= 0) {
00358 GetWindowRect(GetDlgItem(wnd, 11), &r2);
00359 offs = r2.bottom - r2.top + 10;
00360 if (!mode) offs = -offs;
00361 SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
00362 r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
00363 } else {
00364 SetWindowPos(wnd, HWND_TOPMOST,
00365 (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
00366 (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
00367 0, 0, SWP_NOSIZE);
00368 }
00369 }
00370
00371 static bool DoEmergencySave(HWND wnd)
00372 {
00373 bool b = false;
00374
00375 EnableWindow(GetDlgItem(wnd, 13), FALSE);
00376 _did_emerg_save = true;
00377 __try {
00378 b = EmergencySave();
00379 } __except (1) {}
00380 return b;
00381 }
00382
00383 static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
00384 {
00385 switch (msg) {
00386 case WM_INITDIALOG: {
00387 #if defined(UNICODE)
00388
00389
00390 wchar_t crash_msgW[8096];
00391 #endif
00392 SetDlgItemText(wnd, 10, _crash_desc);
00393 SetDlgItemText(wnd, 11, MB_TO_WIDE_BUFFER(_crash_msg, crash_msgW, lengthof(crash_msgW)));
00394 SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
00395 SetWndSize(wnd, -1);
00396 } return TRUE;
00397 case WM_COMMAND:
00398 switch (wParam) {
00399 case 12:
00400 ExitProcess(0);
00401 case 13:
00402 if (DoEmergencySave(wnd)) {
00403 MessageBox(wnd, _save_succeeded, _T("Save successful"), MB_ICONINFORMATION);
00404 } else {
00405 MessageBox(wnd, _T("Save failed"), _T("Save failed"), MB_ICONINFORMATION);
00406 }
00407 break;
00408
00409 #if 0
00410 case 14: {
00411 const TCHAR *s;
00412
00413 SetCursor(LoadCursor(NULL, IDC_WAIT));
00414
00415 s = SubmitCrashReport(wnd, _crash_msg, strlen(_crash_msg), _T(""));
00416 if (s != NULL) {
00417 MessageBox(wnd, s, _T("Error"), MB_ICONSTOP);
00418 break;
00419 }
00420
00421
00422 if (_did_emerg_save || DoEmergencySave(wnd)) SubmitFile(wnd, _T("crash.sav"));
00423
00424
00425 if (_opt.autosave) {
00426 TCHAR buf[40];
00427 _sntprintf(buf, lengthof(buf), _T("autosave%d.sav"), (_autosave_ctr - 1) & 3);
00428 SubmitFile(wnd, buf);
00429 }
00430 EnableWindow(GetDlgItem(wnd, 14), FALSE);
00431 SetCursor(LoadCursor(NULL, IDC_ARROW));
00432 MessageBox(wnd, _T("Crash report submitted. Thank you."), _T("Crash Report"), MB_ICONINFORMATION);
00433 } break;
00434 #endif
00435 case 15:
00436 _expanded ^= 1;
00437 SetWndSize(wnd, _expanded);
00438 break;
00439 }
00440 return TRUE;
00441 case WM_CLOSE: ExitProcess(0);
00442 }
00443
00444 return FALSE;
00445 }
00446
00447 static void Handler2()
00448 {
00449 ShowCursor(TRUE);
00450 ShowWindow(GetActiveWindow(), FALSE);
00451 DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(100), NULL, CrashDialogFunc);
00452 }
00453
00454 extern bool CloseConsoleLogIfActive();
00455
00456 static HANDLE _file_crash_log;
00457
00458 static void GamelogPrintCrashLogProc(const char *s)
00459 {
00460 DWORD num_written;
00461 WriteFile(_file_crash_log, s, (DWORD)strlen(s), &num_written, NULL);
00462 WriteFile(_file_crash_log, "\r\n", (DWORD)strlen("\r\n"), &num_written, NULL);
00463 }
00464
00466 static const int EXCEPTION_OUTPUT_SIZE = 8192;
00467
00468 static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
00469 {
00470 char *output;
00471 static bool had_exception = false;
00472
00473 if (had_exception) ExitProcess(0);
00474 had_exception = true;
00475
00476 _ident = GetTickCount();
00477
00478 MakeCRCTable(AllocaM(uint32, 256));
00479 _crash_msg = output = (char*)LocalAlloc(LMEM_FIXED, EXCEPTION_OUTPUT_SIZE);
00480 const char *last = output + EXCEPTION_OUTPUT_SIZE - 1;
00481
00482 {
00483 SYSTEMTIME time;
00484 GetLocalTime(&time);
00485 output += seprintf(output, last,
00486 "*** OpenTTD Crash Report ***\r\n"
00487 "Date: %d-%.2d-%.2d %.2d:%.2d:%.2d\r\n"
00488 "Build: %s (%d) built on " __DATE__ " " __TIME__ "\r\n",
00489 time.wYear,
00490 time.wMonth,
00491 time.wDay,
00492 time.wHour,
00493 time.wMinute,
00494 time.wSecond,
00495 _openttd_revision,
00496 _openttd_revision_modified
00497 );
00498 }
00499
00500 if (_exception_string)
00501 output += seprintf(output, last, "Reason: %s\r\n", _exception_string);
00502
00503 output += seprintf(output, last, "Language: %s\r\n", _dynlang.curr_file);
00504
00505 #ifdef _M_AMD64
00506 output += seprintf(output, last, "Exception %.8X at %.16IX\r\n"
00507 "Registers:\r\n"
00508 "RAX: %.16llX RBX: %.16llX RCX: %.16llX RDX: %.16llX\r\n"
00509 "RSI: %.16llX RDI: %.16llX RBP: %.16llX RSP: %.16llX\r\n"
00510 "R8: %.16llX R9: %.16llX R10: %.16llX R11: %.16llX\r\n"
00511 "R12: %.16llX R13: %.16llX R14: %.16llX R15: %.16llX\r\n"
00512 "RIP: %.16llX EFLAGS: %.8X\r\n"
00513 "\r\nBytes at CS:RIP:\r\n",
00514 ep->ExceptionRecord->ExceptionCode,
00515 ep->ExceptionRecord->ExceptionAddress,
00516 ep->ContextRecord->Rax,
00517 ep->ContextRecord->Rbx,
00518 ep->ContextRecord->Rcx,
00519 ep->ContextRecord->Rdx,
00520 ep->ContextRecord->Rsi,
00521 ep->ContextRecord->Rdi,
00522 ep->ContextRecord->Rbp,
00523 ep->ContextRecord->Rsp,
00524 ep->ContextRecord->R8,
00525 ep->ContextRecord->R9,
00526 ep->ContextRecord->R10,
00527 ep->ContextRecord->R11,
00528 ep->ContextRecord->R12,
00529 ep->ContextRecord->R13,
00530 ep->ContextRecord->R14,
00531 ep->ContextRecord->R15,
00532 ep->ContextRecord->Rip,
00533 ep->ContextRecord->EFlags
00534 );
00535 #else
00536 output += seprintf(output, last, "Exception %.8X at %.8p\r\n"
00537 "Registers:\r\n"
00538 " EAX: %.8X EBX: %.8X ECX: %.8X EDX: %.8X\r\n"
00539 " ESI: %.8X EDI: %.8X EBP: %.8X ESP: %.8X\r\n"
00540 " EIP: %.8X EFLAGS: %.8X\r\n"
00541 "\r\nBytes at CS:EIP:\r\n",
00542 ep->ExceptionRecord->ExceptionCode,
00543 ep->ExceptionRecord->ExceptionAddress,
00544 ep->ContextRecord->Eax,
00545 ep->ContextRecord->Ebx,
00546 ep->ContextRecord->Ecx,
00547 ep->ContextRecord->Edx,
00548 ep->ContextRecord->Esi,
00549 ep->ContextRecord->Edi,
00550 ep->ContextRecord->Ebp,
00551 ep->ContextRecord->Esp,
00552 ep->ContextRecord->Eip,
00553 ep->ContextRecord->EFlags
00554 );
00555 #endif
00556
00557 {
00558 #ifdef _M_AMD64
00559 byte *b = (byte*)ep->ContextRecord->Rip;
00560 #else
00561 byte *b = (byte*)ep->ContextRecord->Eip;
00562 #endif
00563 int i;
00564 for (i = 0; i != 24; i++) {
00565 if (IsBadReadPtr(b, 1)) {
00566 output += seprintf(output, last, " ??");
00567 } else {
00568 output += seprintf(output, last, " %.2X", *b);
00569 }
00570 b++;
00571 }
00572 output += seprintf(output, last,
00573 "\r\n"
00574 "\r\nStack trace: \r\n"
00575 );
00576 }
00577
00578 {
00579 int i, j;
00580 #ifdef _M_AMD64
00581 uint32 *b = (uint32*)ep->ContextRecord->Rsp;
00582 #else
00583 uint32 *b = (uint32*)ep->ContextRecord->Esp;
00584 #endif
00585 for (j = 0; j != 24; j++) {
00586 for (i = 0; i != 8; i++) {
00587 if (IsBadReadPtr(b, sizeof(uint32))) {
00588 output += seprintf(output, last, " ????????");
00589 } else {
00590 output += seprintf(output, last, " %.8X", *b);
00591 }
00592 b++;
00593 }
00594 output += seprintf(output, last, "\r\n");
00595 }
00596 }
00597
00598 output += seprintf(output, last, "\r\nModule information:\r\n");
00599 output = PrintModuleList(output, last);
00600
00601 {
00602 _OSVERSIONINFOA os;
00603 os.dwOSVersionInfoSize = sizeof(os);
00604 GetVersionExA(&os);
00605 output += seprintf(output, last, "\r\nSystem information:\r\n"
00606 " Windows version %d.%d %d %s\r\n\r\n",
00607 os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.szCSDVersion);
00608 }
00609
00610 _file_crash_log = CreateFile(_T("crash.log"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
00611
00612 if (_file_crash_log != INVALID_HANDLE_VALUE) {
00613 DWORD num_written;
00614 WriteFile(_file_crash_log, _crash_msg, output - _crash_msg, &num_written, NULL);
00615 }
00616
00617 #if !defined(_DEBUG)
00618 HMODULE dbghelp = LoadLibrary(_T("dbghelp.dll"));
00619 if (dbghelp != NULL) {
00620 typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE,
00621 MINIDUMP_TYPE,
00622 CONST PMINIDUMP_EXCEPTION_INFORMATION,
00623 CONST PMINIDUMP_USER_STREAM_INFORMATION,
00624 CONST PMINIDUMP_CALLBACK_INFORMATION);
00625 MiniDumpWriteDump_t funcMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(dbghelp, "MiniDumpWriteDump");
00626 if (funcMiniDumpWriteDump != NULL) {
00627 HANDLE file = CreateFile(_T("crash.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
00628 HANDLE proc = GetCurrentProcess();
00629 DWORD procid = GetCurrentProcessId();
00630 MINIDUMP_EXCEPTION_INFORMATION mdei;
00631 MINIDUMP_USER_STREAM userstream;
00632 MINIDUMP_USER_STREAM_INFORMATION musi;
00633 char msg[] = "****** Built on " __DATE__ " " __TIME__ ". ******";
00634
00635 userstream.Type = LastReservedStream + 1;
00636 userstream.Buffer = msg;
00637 userstream.BufferSize = sizeof(msg);
00638
00639 musi.UserStreamCount = 1;
00640 musi.UserStreamArray = &userstream;
00641
00642 mdei.ThreadId = GetCurrentThreadId();
00643 mdei.ExceptionPointers = ep;
00644 mdei.ClientPointers = false;
00645
00646 funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, NULL);
00647 }
00648 FreeLibrary(dbghelp);
00649 }
00650 #endif
00651
00652 if (_file_crash_log != INVALID_HANDLE_VALUE) {
00653 GamelogPrint(&GamelogPrintCrashLogProc);
00654 CloseHandle(_file_crash_log);
00655 }
00656
00657
00658 CloseConsoleLogIfActive();
00659
00660 if (_safe_esp) {
00661 #ifdef _M_AMD64
00662 ep->ContextRecord->Rip = (DWORD64)Handler2;
00663 ep->ContextRecord->Rsp = (DWORD64)_safe_esp;
00664 #else
00665 ep->ContextRecord->Eip = (DWORD)Handler2;
00666 ep->ContextRecord->Esp = (DWORD)_safe_esp;
00667 #endif
00668 return EXCEPTION_CONTINUE_EXECUTION;
00669 }
00670
00671
00672 return EXCEPTION_EXECUTE_HANDLER;
00673 }
00674
00675 #ifdef _M_AMD64
00676 extern "C" void *_get_safe_esp();
00677 #endif
00678
00679 static void Win32InitializeExceptions()
00680 {
00681 #ifdef _M_AMD64
00682 _safe_esp = _get_safe_esp();
00683 #else
00684 _asm {
00685 mov _safe_esp, esp
00686 }
00687 #endif
00688
00689 SetUnhandledExceptionFilter(ExceptionHandler);
00690 }
00691 #endif
00692
00693
00694
00695
00696
00697
00698
00699
00700
00701
00702 static DIR _global_dir;
00703 static LONG _global_dir_is_in_use = false;
00704
00705 static inline DIR *dir_calloc()
00706 {
00707 DIR *d;
00708
00709 if (InterlockedExchange(&_global_dir_is_in_use, true) == (LONG)true) {
00710 d = CallocT<DIR>(1);
00711 } else {
00712 d = &_global_dir;
00713 memset(d, 0, sizeof(*d));
00714 }
00715 return d;
00716 }
00717
00718 static inline void dir_free(DIR *d)
00719 {
00720 if (d == &_global_dir) {
00721 _global_dir_is_in_use = (LONG)false;
00722 } else {
00723 free(d);
00724 }
00725 }
00726
00727 DIR *opendir(const TCHAR *path)
00728 {
00729 DIR *d;
00730 UINT sem = SetErrorMode(SEM_FAILCRITICALERRORS);
00731 DWORD fa = GetFileAttributes(path);
00732
00733 if ((fa != INVALID_FILE_ATTRIBUTES) && (fa & FILE_ATTRIBUTE_DIRECTORY)) {
00734 d = dir_calloc();
00735 if (d != NULL) {
00736 TCHAR search_path[MAX_PATH];
00737 bool slash = path[_tcslen(path) - 1] == '\\';
00738
00739
00740
00741 _sntprintf(search_path, lengthof(search_path), _T("%s%s*"), path, slash ? _T("") : _T("\\"));
00742 *lastof(search_path) = '\0';
00743 d->hFind = FindFirstFile(search_path, &d->fd);
00744
00745 if (d->hFind != INVALID_HANDLE_VALUE ||
00746 GetLastError() == ERROR_NO_MORE_FILES) {
00747 d->ent.dir = d;
00748 d->at_first_entry = true;
00749 } else {
00750 dir_free(d);
00751 d = NULL;
00752 }
00753 } else {
00754 errno = ENOMEM;
00755 }
00756 } else {
00757
00758 d = NULL;
00759 errno = ENOENT;
00760 }
00761
00762 SetErrorMode(sem);
00763 return d;
00764 }
00765
00766 struct dirent *readdir(DIR *d)
00767 {
00768 DWORD prev_err = GetLastError();
00769
00770 if (d->at_first_entry) {
00771
00772 if (d->hFind == INVALID_HANDLE_VALUE) return NULL;
00773 d->at_first_entry = false;
00774 } else if (!FindNextFile(d->hFind, &d->fd)) {
00775 if (GetLastError() == ERROR_NO_MORE_FILES) SetLastError(prev_err);
00776 return NULL;
00777 }
00778
00779
00780
00781 d->ent.d_name = d->fd.cFileName;
00782 return &d->ent;
00783 }
00784
00785 int closedir(DIR *d)
00786 {
00787 FindClose(d->hFind);
00788 dir_free(d);
00789 return 0;
00790 }
00791
00792 bool FiosIsRoot(const char *file)
00793 {
00794 return file[3] == '\0';
00795 }
00796
00797 void FiosGetDrives()
00798 {
00799 #if defined(WINCE)
00800
00801 FiosItem *fios = _fios_items.Append();
00802 fios->type = FIOS_TYPE_DRIVE;
00803 fios->mtime = 0;
00804 snprintf(fios->name, lengthof(fios->name), PATHSEP "");
00805 strecpy(fios->title, fios->name, lastof(fios->title));
00806 #else
00807 TCHAR drives[256];
00808 const TCHAR *s;
00809
00810 GetLogicalDriveStrings(lengthof(drives), drives);
00811 for (s = drives; *s != '\0';) {
00812 FiosItem *fios = _fios_items.Append();
00813 fios->type = FIOS_TYPE_DRIVE;
00814 fios->mtime = 0;
00815 snprintf(fios->name, lengthof(fios->name), "%c:", s[0] & 0xFF);
00816 strecpy(fios->title, fios->name, lastof(fios->title));
00817 while (*s++ != '\0') { }
00818 }
00819 #endif
00820 }
00821
00822 bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb)
00823 {
00824
00825 static const int64 posix_epoch_hns = 0x019DB1DED53E8000LL;
00826 const WIN32_FIND_DATA *fd = &ent->dir->fd;
00827
00828 sb->st_size = ((uint64) fd->nFileSizeHigh << 32) + fd->nFileSizeLow;
00829
00830
00831
00832
00833
00834 sb->st_mtime = (time_t)((*(uint64*)&fd->ftLastWriteTime - posix_epoch_hns) / 1E7);
00835 sb->st_mode = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG;
00836
00837 return true;
00838 }
00839
00840 bool FiosIsHiddenFile(const struct dirent *ent)
00841 {
00842 return (ent->dir->fd.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0;
00843 }
00844
00845 bool FiosGetDiskFreeSpace(const char *path, uint64 *tot)
00846 {
00847 UINT sem = SetErrorMode(SEM_FAILCRITICALERRORS);
00848 bool retval = false;
00849 TCHAR root[4];
00850 DWORD spc, bps, nfc, tnc;
00851
00852 _sntprintf(root, lengthof(root), _T("%c:") _T(PATHSEP), path[0]);
00853 if (tot != NULL && GetDiskFreeSpace(root, &spc, &bps, &nfc, &tnc)) {
00854 *tot = ((spc * bps) * (uint64)nfc);
00855 retval = true;
00856 }
00857
00858 SetErrorMode(sem);
00859 return retval;
00860 }
00861
00862 static int ParseCommandLine(char *line, char **argv, int max_argc)
00863 {
00864 int n = 0;
00865
00866 do {
00867
00868 while (*line == ' ' || *line == '\t') line++;
00869
00870
00871 if (*line == '\0') break;
00872
00873
00874 if (*line == '"') {
00875 argv[n++] = ++line;
00876 while (*line != '"') {
00877 if (*line == '\0') return n;
00878 line++;
00879 }
00880 } else {
00881 argv[n++] = line;
00882 while (*line != ' ' && *line != '\t') {
00883 if (*line == '\0') return n;
00884 line++;
00885 }
00886 }
00887 *line++ = '\0';
00888 } while (n != max_argc);
00889
00890 return n;
00891 }
00892
00893 void CreateConsole()
00894 {
00895 #if defined(WINCE)
00896
00897 #else
00898 HANDLE hand;
00899 CONSOLE_SCREEN_BUFFER_INFO coninfo;
00900
00901 if (_has_console) return;
00902 _has_console = true;
00903
00904 AllocConsole();
00905
00906 hand = GetStdHandle(STD_OUTPUT_HANDLE);
00907 GetConsoleScreenBufferInfo(hand, &coninfo);
00908 coninfo.dwSize.Y = 500;
00909 SetConsoleScreenBufferSize(hand, coninfo.dwSize);
00910
00911
00912 #if !defined(__CYGWIN__)
00913 *stdout = *_fdopen( _open_osfhandle((intptr_t)hand, _O_TEXT), "w" );
00914 *stdin = *_fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT), "r" );
00915 *stderr = *_fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT), "w" );
00916 #else
00917
00918 *stdout = *fdopen(1, "w" );
00919 *stdin = *fdopen(0, "r" );
00920 *stderr = *fdopen(2, "w" );
00921 #endif
00922
00923 setvbuf(stdin, NULL, _IONBF, 0);
00924 setvbuf(stdout, NULL, _IONBF, 0);
00925 setvbuf(stderr, NULL, _IONBF, 0);
00926 #endif
00927 }
00928
00929 void ShowInfo(const char *str)
00930 {
00931 if (_has_console) {
00932 fprintf(stderr, "%s\n", str);
00933 } else {
00934 bool old;
00935 #if defined(UNICODE)
00936
00937
00938 wchar_t help_msgW[8192];
00939 #endif
00940 ReleaseCapture();
00941 _left_button_clicked = _left_button_down = false;
00942
00943 old = MyShowCursor(true);
00944 if (MessageBox(GetActiveWindow(), MB_TO_WIDE_BUFFER(str, help_msgW, lengthof(help_msgW)), _T("OpenTTD"), MB_ICONINFORMATION | MB_OKCANCEL) == IDCANCEL) {
00945 CreateConsole();
00946 }
00947 MyShowCursor(old);
00948 }
00949 }
00950
00951 #if defined(WINCE)
00952 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
00953 #else
00954 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
00955 #endif
00956 {
00957 int argc;
00958 char *argv[64];
00959 char *cmdline;
00960
00961 #if !defined(UNICODE)
00962 _codepage = GetACP();
00963 #endif
00964
00965 #if defined(UNICODE)
00966
00967 #if !defined(WINCE)
00968
00969 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.");
00970 #endif
00971
00972
00973
00974
00975 char cmdlinebuf[MAX_PATH];
00976 #endif
00977
00978 cmdline = WIDE_TO_MB_BUFFER(GetCommandLine(), cmdlinebuf, lengthof(cmdlinebuf));
00979
00980 #if defined(_DEBUG)
00981 CreateConsole();
00982 #endif
00983
00984 #if !defined(WINCE)
00985 _set_error_mode(_OUT_TO_MSGBOX);
00986 #endif
00987
00988
00989 SetRandomSeed(GetTickCount());
00990
00991 argc = ParseCommandLine(cmdline, argv, lengthof(argv));
00992
00993 #if defined(WIN32_EXCEPTION_TRACKER)
00994 Win32InitializeExceptions();
00995 #endif
00996
00997 #if defined(WIN32_EXCEPTION_TRACKER_DEBUG)
00998 _try {
00999 LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep);
01000 #endif
01001 ttd_main(argc, argv);
01002
01003 #if defined(WIN32_EXCEPTION_TRACKER_DEBUG)
01004 } _except (ExceptionHandler(_exception_info())) {}
01005 #endif
01006
01007 return 0;
01008 }
01009
01010 #if defined(WINCE)
01011 void GetCurrentDirectoryW(int length, wchar_t *path)
01012 {
01013
01014 GetModuleFileName(NULL, path, length);
01015
01016
01017 wchar_t *pDest = wcsrchr(path, '\\');
01018 if (pDest != NULL) {
01019 int result = pDest - path + 1;
01020 path[result] = '\0';
01021 }
01022 }
01023 #endif
01024
01025 char *getcwd(char *buf, size_t size)
01026 {
01027 #if defined(WINCE)
01028 TCHAR path[MAX_PATH];
01029 GetModuleFileName(NULL, path, MAX_PATH);
01030 convert_from_fs(path, buf, size);
01031
01032 char *p = strrchr(buf, '\\');
01033 if (p != NULL) *p = '\0';
01034 #elif defined(UNICODE)
01035 TCHAR path[MAX_PATH];
01036 GetCurrentDirectory(MAX_PATH - 1, path);
01037 convert_from_fs(path, buf, size);
01038 #else
01039 GetCurrentDirectory(size, buf);
01040 #endif
01041 return buf;
01042 }
01043
01044
01045 void DetermineBasePaths(const char *exe)
01046 {
01047 char tmp[MAX_PATH];
01048 TCHAR path[MAX_PATH];
01049 #ifdef WITH_PERSONAL_DIR
01050 SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path);
01051 strecpy(tmp, WIDE_TO_MB_BUFFER(path, tmp, lengthof(tmp)), lastof(tmp));
01052 AppendPathSeparator(tmp, MAX_PATH);
01053 ttd_strlcat(tmp, PERSONAL_DIR, MAX_PATH);
01054 AppendPathSeparator(tmp, MAX_PATH);
01055 _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
01056
01057 SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
01058 strecpy(tmp, WIDE_TO_MB_BUFFER(path, tmp, lengthof(tmp)), lastof(tmp));
01059 AppendPathSeparator(tmp, MAX_PATH);
01060 ttd_strlcat(tmp, PERSONAL_DIR, MAX_PATH);
01061 AppendPathSeparator(tmp, MAX_PATH);
01062 _searchpaths[SP_SHARED_DIR] = strdup(tmp);
01063 #else
01064 _searchpaths[SP_PERSONAL_DIR] = NULL;
01065 _searchpaths[SP_SHARED_DIR] = NULL;
01066 #endif
01067
01068
01069 getcwd(tmp, lengthof(tmp));
01070 AppendPathSeparator(tmp, MAX_PATH);
01071 _searchpaths[SP_WORKING_DIR] = strdup(tmp);
01072
01073 if (!GetModuleFileName(NULL, path, lengthof(path))) {
01074 DEBUG(misc, 0, "GetModuleFileName failed (%d)\n", GetLastError());
01075 _searchpaths[SP_BINARY_DIR] = NULL;
01076 } else {
01077 TCHAR exec_dir[MAX_PATH];
01078 _tcsncpy(path, MB_TO_WIDE_BUFFER(exe, path, lengthof(path)), lengthof(path));
01079 if (!GetFullPathName(path, lengthof(exec_dir), exec_dir, NULL)) {
01080 DEBUG(misc, 0, "GetFullPathName failed (%d)\n", GetLastError());
01081 _searchpaths[SP_BINARY_DIR] = NULL;
01082 } else {
01083 strecpy(tmp, WIDE_TO_MB_BUFFER(exec_dir, tmp, lengthof(tmp)), lastof(tmp));
01084 char *s = strrchr(tmp, PATHSEPCHAR);
01085 *(s + 1) = '\0';
01086 _searchpaths[SP_BINARY_DIR] = strdup(tmp);
01087 }
01088 }
01089
01090 _searchpaths[SP_INSTALLATION_DIR] = NULL;
01091 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
01092 }
01093
01101 bool InsertTextBufferClipboard(Textbuf *tb)
01102 {
01103 HGLOBAL cbuf;
01104 char utf8_buf[512];
01105 const char *ptr;
01106
01107 WChar c;
01108 uint16 width, length;
01109
01110 if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
01111 OpenClipboard(NULL);
01112 cbuf = GetClipboardData(CF_UNICODETEXT);
01113
01114 ptr = (const char*)GlobalLock(cbuf);
01115 const char *ret = convert_from_fs((wchar_t*)ptr, utf8_buf, lengthof(utf8_buf));
01116 GlobalUnlock(cbuf);
01117 CloseClipboard();
01118
01119 if (*ret == '\0') return false;
01120 #if !defined(UNICODE)
01121 } else if (IsClipboardFormatAvailable(CF_TEXT)) {
01122 OpenClipboard(NULL);
01123 cbuf = GetClipboardData(CF_TEXT);
01124
01125 ptr = (const char*)GlobalLock(cbuf);
01126 strecpy(utf8_buf, FS2OTTD(ptr), lastof(utf8_buf));
01127
01128 GlobalUnlock(cbuf);
01129 CloseClipboard();
01130 #endif
01131 } else {
01132 return false;
01133 }
01134
01135 width = length = 0;
01136
01137 for (ptr = utf8_buf; (c = Utf8Consume(&ptr)) != '\0';) {
01138 if (!IsPrintable(c)) break;
01139
01140 byte len = Utf8CharLen(c);
01141 if (tb->size + length + len > tb->maxsize) break;
01142
01143 byte charwidth = GetCharacterWidth(FS_NORMAL, c);
01144 if (tb->maxwidth != 0 && width + tb->width + charwidth > tb->maxwidth) break;
01145
01146 width += charwidth;
01147 length += len;
01148 }
01149
01150 if (length == 0) return false;
01151
01152 memmove(tb->buf + tb->caretpos + length, tb->buf + tb->caretpos, tb->size - tb->caretpos);
01153 memcpy(tb->buf + tb->caretpos, utf8_buf, length);
01154 tb->width += width;
01155 tb->caretxoffs += width;
01156
01157 tb->size += length;
01158 tb->caretpos += length;
01159 assert(tb->size <= tb->maxsize);
01160 tb->buf[tb->size - 1] = '\0';
01161
01162 return true;
01163 }
01164
01165
01166 void CSleep(int milliseconds)
01167 {
01168 Sleep(milliseconds);
01169 }
01170
01171
01174 int64 GetTS()
01175 {
01176 static double freq;
01177 __int64 value;
01178 if (!freq) {
01179 QueryPerformanceFrequency((LARGE_INTEGER*)&value);
01180 freq = (double)1000000 / value;
01181 }
01182 QueryPerformanceCounter((LARGE_INTEGER*)&value);
01183 return (__int64)(value * freq);
01184 }
01185
01186
01199 const char *FS2OTTD(const TCHAR *name)
01200 {
01201 static char utf8_buf[512];
01202 #if defined(UNICODE)
01203 return convert_from_fs(name, utf8_buf, lengthof(utf8_buf));
01204 #else
01205 char *s = utf8_buf;
01206
01207 for (; *name != '\0'; name++) {
01208 wchar_t w;
01209 int len = MultiByteToWideChar(_codepage, 0, name, 1, &w, 1);
01210 if (len != 1) {
01211 DEBUG(misc, 0, "[utf8] M2W error converting '%c'. Errno %d", *name, GetLastError());
01212 continue;
01213 }
01214
01215 if (s + Utf8CharLen(w) >= lastof(utf8_buf)) break;
01216 s += Utf8Encode(s, w);
01217 }
01218
01219 *s = '\0';
01220 return utf8_buf;
01221 #endif
01222 }
01223
01236 const TCHAR *OTTD2FS(const char *name)
01237 {
01238 static TCHAR system_buf[512];
01239 #if defined(UNICODE)
01240 return convert_to_fs(name, system_buf, lengthof(system_buf));
01241 #else
01242 char *s = system_buf;
01243
01244 for (WChar c; (c = Utf8Consume(&name)) != '\0';) {
01245 if (s >= lastof(system_buf)) break;
01246
01247 char mb;
01248 int len = WideCharToMultiByte(_codepage, 0, (wchar_t*)&c, 1, &mb, 1, NULL, NULL);
01249 if (len != 1) {
01250 DEBUG(misc, 0, "[utf8] W2M error converting '0x%X'. Errno %d", c, GetLastError());
01251 continue;
01252 }
01253
01254 *s++ = mb;
01255 }
01256
01257 *s = '\0';
01258 return system_buf;
01259 #endif
01260 }
01261
01262
01269 char *convert_from_fs(const wchar_t *name, char *utf8_buf, size_t buflen)
01270 {
01271 int len = WideCharToMultiByte(CP_UTF8, 0, name, -1, utf8_buf, (int)buflen, NULL, NULL);
01272 if (len == 0) {
01273 DEBUG(misc, 0, "[utf8] W2M error converting wide-string. Errno %d", GetLastError());
01274 utf8_buf[0] = '\0';
01275 }
01276
01277 return utf8_buf;
01278 }
01279
01280
01288 wchar_t *convert_to_fs(const char *name, wchar_t *utf16_buf, size_t buflen)
01289 {
01290 int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, utf16_buf, (int)buflen);
01291 if (len == 0) {
01292 DEBUG(misc, 0, "[utf8] M2W error converting '%s'. Errno %d", name, GetLastError());
01293 utf16_buf[0] = '\0';
01294 }
01295
01296 return utf16_buf;
01297 }
01298
01303 HRESULT OTTDSHGetFolderPath(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)
01304 {
01305 static HRESULT (WINAPI *SHGetFolderPath)(HWND, int, HANDLE, DWORD, LPTSTR) = NULL;
01306 static bool first_time = true;
01307
01308
01309 if (first_time) {
01310 #if defined(UNICODE)
01311 # define W(x) x "W"
01312 #else
01313 # define W(x) x "A"
01314 #endif
01315 if (!LoadLibraryList((Function*)&SHGetFolderPath, "SHFolder.dll\0" W("SHGetFolderPath") "\0\0")) {
01316 DEBUG(misc, 0, "Unable to load " W("SHGetFolderPath") "from SHFolder.dll");
01317 }
01318 #undef W
01319 first_time = false;
01320 }
01321
01322 if (SHGetFolderPath != NULL) return SHGetFolderPath(hwnd, csidl, hToken, dwFlags, pszPath);
01323
01324
01325
01326
01327
01328
01329
01330
01331 {
01332 DWORD ret;
01333 switch (csidl) {
01334 case CSIDL_FONTS:
01335 ret = GetEnvironmentVariable(_T("WINDIR"), pszPath, MAX_PATH);
01336 if (ret == 0) break;
01337 _tcsncat(pszPath, _T("\\Fonts"), MAX_PATH);
01338
01339 return (HRESULT)0;
01340 break;
01341
01342 }
01343 }
01344
01345 return E_INVALIDARG;
01346 }
01347
01349 const char *GetCurrentLocale(const char *)
01350 {
01351 char lang[9], country[9];
01352 if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lang, lengthof(lang)) == 0 ||
01353 GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, country, lengthof(country)) == 0) {
01354
01355 return NULL;
01356 }
01357
01358 static char retbuf[6] = {lang[0], lang[1], '_', country[0], country[1], 0};
01359 return retbuf;
01360 }