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