/* NetHack 3.7 windsys.c $NHDT-Date: 1710949760 2024/03/20 15:49:20 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.95 $ */ /* Copyright (c) NetHack PC Development Team 1993, 1994 */ /* NetHack may be freely redistributed. See license for details. */ /* * WIN32 system functions. * * Included in both console-based and window-based clients on the windows platform. * * Initial Creation: Michael Allison - January 31/93 * */ #include "win10.h" #include "winos.h" #include #define NEED_VARARGS #include "hack.h" #include #ifndef __BORLANDC__ #include #endif #ifdef TTY_GRAPHICS #include "wintty.h" #endif #include #include #ifdef WIN32 #include #include /* * The following WIN32 API routines are used in this file. * * GetDiskFreeSpace * GetVolumeInformation * GetUserName * FindFirstFile * FindNextFile * FindClose * */ static char portable_device_path[MAX_PATH]; static boolean path_buffer_set = FALSE; static char path_buffer[MAX_PATH]; #ifndef SFCTOOL /* runtime cursor display control switch */ boolean win32_cursorblink; /* globals required within here */ HANDLE ffhandle = (HANDLE) 0; WIN32_FIND_DATA ffd; extern int GUILaunched; extern boolean getreturn_enabled; int redirect_stdout; static char *get_executable_path(void); #ifdef WIN32CON typedef HWND(WINAPI *GETCONSOLEWINDOW)(void); #if 0 static HWND GetConsoleHandle(void); static HWND GetConsoleHwnd(void); #endif /* 0 */ #endif /* WIN32CON */ #if !defined(TTY_GRAPHICS) extern void backsp(void); #endif int windows_console_custom_nhgetch(void); extern void safe_routines(void); int windows_early_options(const char *window_opt); unsigned long sys_random_seed(void); #if 0 static int max_filename(void); #endif int get_known_folder_path(const KNOWNFOLDERID *folder_id, char *path, size_t path_size); void create_directory(const char *path); int build_known_folder_path(const KNOWNFOLDERID *folder_id, char *path, size_t path_size, boolean versioned); void build_environment_path(const char *env_str, const char *folder, char *path, size_t path_size); boolean folder_file_exists(const char *folder, const char *file_name); boolean test_portable_config(const char *executable_path, char *portable_device_path, size_t portable_device_path_size); /* The function pointer nt_kbhit contains a kbhit() equivalent * which varies depending on which window port is active. * For the tty port it is tty_kbhit() [from consoletty.c] * For the win32 port it is win32_kbhit() [from winmain.c] * It is initialized to point to def_kbhit [in here] for safety. */ int def_kbhit(void); int (*nt_kbhit)(void) = def_kbhit; #ifndef WIN32CON /* this is used as a printf() replacement when the window * system isn't initialized yet */ void msmsg VA_DECL(const char *, fmt) { VA_START(fmt); VA_INIT(fmt, const char *); VA_END(); return; } #endif /* WIN32CON */ char switchar(void) { /* Could not locate a WIN32 API call for this- MJA */ return '-'; } long freediskspace(char *path) { char tmppath[4]; DWORD SectorsPerCluster = 0; DWORD BytesPerSector = 0; DWORD FreeClusters = 0; DWORD TotalClusters = 0; tmppath[0] = *path; tmppath[1] = ':'; tmppath[2] = '\\'; tmppath[3] = '\0'; GetDiskFreeSpace(tmppath, &SectorsPerCluster, &BytesPerSector, &FreeClusters, &TotalClusters); return (long) (SectorsPerCluster * BytesPerSector * FreeClusters); } /* * Functions to get filenames using wildcards */ int findfirst(char *path) { if (ffhandle) { FindClose(ffhandle); ffhandle = (HANDLE) 0; } ffhandle = FindFirstFile(path, &ffd); return (ffhandle == INVALID_HANDLE_VALUE) ? 0 : 1; } int findnext(void) { return FindNextFile(ffhandle, &ffd) ? 1 : 0; } char * foundfile_buffer(void) { return &ffd.cFileName[0]; } long filesize(char *file) { if (findfirst(file)) { return ((long) ffd.nFileSizeLow); } else return -1L; } /* * Chdrive() changes the default drive. */ void chdrive(char *str) { char *ptr; char drive; if ((ptr = strchr(str, ':')) != (char *) 0) { drive = toupper((uchar) *(ptr - 1)); (void) _chdrive((drive - 'A') + 1); } } #if 0 static int max_filename(void) { DWORD maxflen; int status = 0; status = GetVolumeInformation((LPTSTR) 0, (LPTSTR) 0, 0, (LPDWORD) 0, &maxflen, (LPDWORD) 0, (LPTSTR) 0, 0); if (status) return maxflen; else return 0; } #endif int def_kbhit(void) { return 0; } /* * Strip out troublesome file system characters. */ void nt_regularize(char* s) /* normalize file name */ { unsigned char *lp; for (lp = (unsigned char *) s; *lp; lp++) if (*lp == '?' || *lp == '"' || *lp == '\\' || *lp == '/' || *lp == '>' || *lp == '<' || *lp == '*' || *lp == '|' || *lp == ':' || (*lp > 127)) *lp = '_'; } #if 0 char *getxxx(void) { char szFullPath[MAX_PATH] = ""; HMODULE hInst = NULL; /* NULL gets the filename of this module */ GetModuleFileName(hInst, szFullPath, sizeof(szFullPath)); return &szFullPath[0]; } #endif #ifdef MSWIN_GRAPHICS extern void mswin_raw_print_flush(void); extern void mswin_raw_print(const char *); #endif DISABLE_WARNING_FORMAT_NONLITERAL /* fatal error */ /*VARARGS1*/ void error VA_DECL(const char *, s) { char buf[BUFSZ]; VA_START(s); VA_INIT(s, const char *); /* error() may get called before tty is initialized */ #ifdef TTY_GRAPHICS if (iflags.window_inited) term_end_screen(); #endif if (WINDOWPORT(tty)) { buf[0] = '\n'; (void) vsnprintf(&buf[1], sizeof buf - (1 + sizeof "\n"), s, VA_ARGS); Strcat(buf, "\n"); msmsg("%s", buf); } else { (void) vsnprintf(buf, sizeof buf - sizeof "\n", s, VA_ARGS); Strcat(buf, "\n"); raw_printf("%s",buf); } #ifdef MSWIN_GRAPHICS if (windowprocs.win_raw_print == mswin_raw_print) mswin_raw_print_flush(); #endif VA_END(); exit(EXIT_FAILURE); } RESTORE_WARNING_FORMAT_NONLITERAL void Delay(int ms) { (void) Sleep(ms); } void win32_abort(void) { int c; if (WINDOWPORT(mswin) || WINDOWPORT(tty)) { if (iflags.window_inited) exit_nhwindows((char *) 0); iflags.window_inited = FALSE; } #ifdef WIN32CON if (!WINDOWPORT(mswin) && !WINDOWPORT(safestartup)) safe_routines(); #endif if (wizard) { raw_print("Execute debug breakpoint wizard?"); if ((c = nhgetch()) == 'y' || c == 'Y') DebugBreak(); } abort(); } static char interjection_buf[INTERJECTION_TYPES][1024]; static int interjection[INTERJECTION_TYPES]; void interject_assistance(int num, int interjection_type, genericptr_t ptr1, genericptr_t ptr2) { switch (num) { case 1: { char *panicmsg = (char *) ptr1; char *datadir = (char *) ptr2; char *tempdir = nh_getenv("TEMP"); interjection_type = INTERJECT_PANIC; interjection[INTERJECT_PANIC] = 1; /* * ptr1 = the panic message about to be delivered. * ptr2 = the directory prefix of the dungeon file * that failed to open. * Check to see if datadir matches tempdir or a * common windows temp location. If it does, inform * the user that they are probably trying to run the * game from within their unzip utility, so the required * files really don't exist at the location. Instruct * them to unpack them first. */ if (panicmsg && datadir) { if (!strncmpi(datadir, "C:\\WINDOWS\\TEMP", 15) || strstri(datadir, "TEMP") || (tempdir && strstri(datadir, tempdir))) { (void) strncpy( interjection_buf[INTERJECT_PANIC], "\nOne common cause of this error is attempting to " "execute\n" "the game by double-clicking on it while it is " "displayed\n" "inside an unzip utility.\n\n" "You have to unzip the contents of the zip file into a\n" "folder on your system, and then run \"NetHack.exe\" or " "\n" "\"NetHackW.exe\" from there.\n\n" "If that is not the situation, you are encouraged to\n" "report the error as shown above.\n\n", 1023); } } } break; } nhUse(interjection_type); } void interject(int interjection_type) { if (interjection_type >= 0 && interjection_type < INTERJECTION_TYPES) msmsg("%s", interjection_buf[interjection_type]); } #ifdef RUNTIME_PASTEBUF_SUPPORT void port_insert_pastebuf(char *buf) { /* This implementation will utilize the windows clipboard * to accomplish this. */ HGLOBAL hglbCopy; WCHAR *w, w2[2]; /* int cc; */ int rc, abytes; LPWSTR lpwstrCopy; HANDLE hresult; if (!buf) return; /* cc = strlen(buf); */ /* last arg=0 means "tell me the size of the buffer that I need" */ rc = MultiByteToWideChar(GetConsoleOutputCP(), 0, buf, -1, w2, 0); if (!rc) return; abytes = rc * sizeof(WCHAR); w = (WCHAR *)alloc(abytes); /* Housekeeping need: +free(w) */ rc = MultiByteToWideChar(GetConsoleOutputCP(), 0, buf, -1, w, rc); if (!rc) { free(w); return; } if (!OpenClipboard(NULL)) { free(w); return; } /* Housekeeping need: +CloseClipboard(), free(w) */ EmptyClipboard(); /* allocate global mem obj to hold the text */ hglbCopy = GlobalAlloc(GMEM_MOVEABLE, abytes); if (hglbCopy == NULL) { CloseClipboard(); free(w); return; } /* Housekeeping need: +GlobalFree(hglbCopy), CloseClipboard(), free(w) */ lpwstrCopy = (LPWSTR)GlobalLock(hglbCopy); /* Housekeeping need: +GlobalUnlock(hglbCopy), GlobalFree(hglbCopy), CloseClipboard(), free(w) */ if (lpwstrCopy) { memcpy(lpwstrCopy, w, abytes); GlobalUnlock(hglbCopy); /* Housekeeping need: GlobalFree(hglbCopy), CloseClipboard(), free(w) */ /* put it on the clipboard */ hresult = SetClipboardData(CF_UNICODETEXT, hglbCopy); if (!hresult) { raw_printf("Error copying to clipboard.\n"); GlobalFree( hglbCopy); /* only needed if clipboard didn't accept data */ } /* Housekeeping need: CloseClipboard(), free(w) */ } CloseClipboard(); free(w); return; } #ifdef WIN32CON #if 0 static HWND GetConsoleHandle(void) { HMODULE hMod = GetModuleHandle("kernel32.dll"); if (hMod) { GETCONSOLEWINDOW pfnGetConsoleWindow = (GETCONSOLEWINDOW) GetProcAddress(hMod, "GetConsoleWindow"); if (pfnGetConsoleWindow) return pfnGetConsoleWindow(); } return GetConsoleHwnd(); } static HWND GetConsoleHwnd(void) { int iterations = 0; HWND hwndFound = 0; char OldTitle[1024], NewTitle[1024], TestTitle[1024]; /* Get current window title */ GetConsoleTitle(OldTitle, sizeof OldTitle); (void) sprintf(NewTitle, "NETHACK%lld/%ld", GetTickCount64(), GetCurrentProcessId()); SetConsoleTitle(NewTitle); GetConsoleTitle(TestTitle, sizeof TestTitle); while (strcmp(TestTitle, NewTitle) != 0) { iterations++; /* sleep(0); */ GetConsoleTitle(TestTitle, sizeof TestTitle); } hwndFound = FindWindow(NULL, NewTitle); SetConsoleTitle(OldTitle); /* printf("%d iterations\n", iterations); */ return hwndFound; } #endif /* 0 */ #endif /* WIN32CON */ #endif /* RUNTIME_PASTEBUF_SUPPORT */ #ifdef RUNTIME_PORT_ID /* * _M_IX86 is Defined for x86 processors. This is not defined for x64 * processors. * _M_X64 is Defined for x64 processors. * _M_IA64 is Defined for Itanium Processor Family 64-bit processors. * _WIN64 is Defined for applications for Win64. */ #ifndef _M_IX86 #ifdef _M_X64 #define TARGET_PORT "x64" #endif #ifdef _M_IA64 #define TARGET_PORT "IA64" #endif #endif #ifndef TARGET_PORT #define TARGET_PORT "x86" #endif char * get_port_id(char *buf) { Strcpy(buf, TARGET_PORT); return buf; } #endif /* RUNTIME_PORT_ID */ #ifdef MSWIN_GRAPHICS extern void free_winmain_stuff(void); #endif void nethack_exit(int code) { /* Only if we started from the GUI, not the command prompt, * we need to get one last return, so the score board does * not vanish instantly after being created. * GUILaunched is defined and set in consoletty.c. */ #ifdef WIN32CON if (!GUILaunched) { windowprocs = *get_safe_procs(1); /* use our custom version which works a little cleaner than the stdio one */ windowprocs.win_nhgetch = windows_console_custom_nhgetch; } else #endif if (getreturn_enabled) { raw_print("\n"); if (iflags.window_inited) wait_synch(); } /* frees some status tracking data */ genl_status_finish(); #ifdef MSWIN_GRAPHICS free_winmain_stuff(); #endif exit(code); } #ifdef WIN32CON #undef kbhit #include int windows_console_custom_nhgetch(void) { return _getch(); } extern int windows_console_custom_nhgetch(void); void getreturn(const char *str) { static boolean in_getreturn = FALSE; char buf[BUFSZ]; if (in_getreturn || !getreturn_enabled) return; in_getreturn = TRUE; Sprintf(buf,"Hit %s.", str); raw_print(buf); if (WINDOWPORT(tty)) windows_console_custom_nhgetch(); else wait_synch(); in_getreturn = FALSE; return; } #endif /* nethack_enter_windows() is called from main immediately after initializing the window port */ void nethack_enter_windows(void) { #ifdef WIN32CON if (WINDOWPORT(tty)) nethack_enter_consoletty(); #endif } /* CP437 to Unicode mapping according to the Unicode Consortium */ const WCHAR cp437[256] = { 0x0020, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 }; WCHAR * winos_ascii_to_wide_str(const unsigned char * src, WCHAR * dst, size_t dstLength) { size_t i = 0; while(i < dstLength - 1 && src[i] != 0) { dst[i] = cp437[src[i]]; i++; } dst[i] = 0; return dst; } void winos_ascii_to_wide(WCHAR dst[3], UINT32 c) { #ifdef ENHANCED_SYMBOLS if (SYMHANDLING(H_UTF8)) { if (c <= 0xFFFF) { dst[0] = (WCHAR) c; dst[1] = L'\0'; } else { /* UTF-16 surrogate pair */ dst[0] = (WCHAR) ((c >> 10) + 0xD7C0); dst[1] = (WCHAR) ((c & 0x3FF) + 0xDC00); dst[2] = L'\0'; } } else #endif { dst[0] = cp437[c]; dst[1] = L'\0'; } } BOOL winos_font_support_cp437(HFONT hFont) { BOOL allFound = FALSE; HDC hdc = GetDC(NULL); HFONT oldFont = SelectObject(hdc, hFont); DWORD size = (size_t) GetFontUnicodeRanges(hdc, NULL); if (size) { GLYPHSET *glyphSet = (GLYPHSET *) malloc((size_t) size); if (glyphSet != NULL) { #ifdef _MSC_VER #pragma warning( push ) #pragma warning( disable : 6386 ) #endif size = GetFontUnicodeRanges(hdc, glyphSet); #ifdef _MSC_VER #pragma warning( pop ) #endif allFound = TRUE; for (int i = 0; i < 256 && allFound; i++) { WCHAR wc = cp437[i]; BOOL found = FALSE; for (DWORD j = 0; j < glyphSet->cRanges && !found; j++) { WCHAR first = glyphSet->ranges[j].wcLow; WCHAR last = first + glyphSet->ranges[j].cGlyphs - 1; if (wc >= first && wc <= last) found = TRUE; } if (!found) allFound = FALSE; } free(glyphSet); } } SelectObject(hdc, oldFont); ReleaseDC(NULL, hdc); return allFound; } int windows_early_options(const char *window_opt) { /* * If you return 2, the game will exit before it begins. * Return 1, to say the option parsed okay. * Return 0, to say the option was bad. */ if (match_optname(window_opt, "cursorblink", 5, FALSE)) { win32_cursorblink = TRUE; return 1; } else { raw_printf( "-%s windows:cursorblink is the only supported option.\n", window_opt); } return 0; } #include /* Windows Crypto Next Gen (CNG) */ #ifndef STATUS_SUCCESS #define STATUS_SUCCESS 0 #endif #ifndef STATUS_NOT_FOUND #define STATUS_NOT_FOUND 0xC0000225 #endif #ifndef STATUS_UNSUCCESSFUL #define STATUS_UNSUCCESSFUL 0xC0000001 #endif unsigned long sys_random_seed(void) { unsigned long ourseed = 0UL; BCRYPT_ALG_HANDLE hRa = (BCRYPT_ALG_HANDLE) 0; NTSTATUS status = STATUS_UNSUCCESSFUL; boolean Plan_B = TRUE; status = BCryptOpenAlgorithmProvider(&hRa, BCRYPT_RNG_ALGORITHM, (LPCWSTR) 0, 0); if (hRa && status == (NTSTATUS) STATUS_SUCCESS) { status = BCryptGenRandom(hRa, (PUCHAR) &ourseed, (ULONG) sizeof ourseed, 0); if (status == (NTSTATUS) STATUS_SUCCESS) { BCryptCloseAlgorithmProvider(hRa,0); has_strong_rngseed = TRUE; Plan_B = FALSE; } } if (Plan_B) { time_t datetime = 0; const char *emsg; if (status == (NTSTATUS) STATUS_NOT_FOUND) emsg = "BCRYPT_RNG_ALGORITHM not avail, falling back"; else emsg = "Other failure than algorithm not avail"; paniclog("sys_random_seed", emsg); /* leaves clue, doesn't exit */ (void) time(&datetime); ourseed = (unsigned long) datetime; } return ourseed; } /* nt_assert_failed is called when an nhassert's condition is false */ void nt_assert_failed(const char *expression, const char *filepath, int line) { const char * filename; filename = strrchr(filepath, '\\'); filename = (filename == NULL ? filepath : filename + 1); if (IsDebuggerPresent()) { char message[BUFSIZ]; snprintf(message, sizeof(message), "nhassert(%s) failed in file '%s' at line %d", expression, filename, line); OutputDebugStringA(message); DebugBreak(); } /* get file name from path */ impossible("nhassert(%s) failed in file '%s' at line %d", expression, filename, line); } #endif /* SFCTOOL */ /* used by util/sfctool.c as well as files.c */ boolean get_user_home_folder(char *homebuf, size_t sz) { static char szHomeDirBuf[MAX_PATH] = { 0 }; // We need a process with query permission set HANDLE hToken = 0; DWORD result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken); DWORD BufSize = MAX_PATH; result = GetUserProfileDirectoryA(hToken, szHomeDirBuf, &BufSize); // Close handle opened via OpenProcessToken CloseHandle(hToken); if (result != 0) { Snprintf(homebuf, sz, "%s", szHomeDirBuf); } return (result != 0); } char * get_executable_path(void) { #ifdef UNICODE { TCHAR wbuf[BUFSZ]; GetModuleFileName((HANDLE) 0, wbuf, BUFSZ); WideCharToMultiByte(CP_ACP, 0, wbuf, -1, path_buffer, sizeof(path_buffer), NULL, NULL); } #else DWORD length = GetModuleFileName((HANDLE) 0, path_buffer, MAX_PATH); if (length == ERROR_INSUFFICIENT_BUFFER) error("Unable to get module name"); path_buffer[length] = '\0'; #endif char *separator = strrchr(path_buffer, PATH_SEPARATOR); if (separator) *separator = '\0'; path_buffer_set = TRUE; return path_buffer; } char * windows_exepath(void) { char *p = (char *) 0; if (path_buffer_set) p = path_buffer; return p; } char * translate_path_variables(const char *str, char *buf) { const char *src; char evar[BUFSZ], *dest, *envp, *eptr = (char *) 0; boolean in_evar; size_t ccount, ecount, destcount, slen = str ? strlen(str) : 0; if (!slen || !buf) { if (buf) *buf = '\0'; return buf; } dest = buf; src = str; in_evar = FALSE; destcount = ecount = 0; for (ccount = 0; ccount < slen && destcount < (BUFSZ - 1) && ecount < (BUFSZ - 1); ++ccount, ++src) { if (*src == '%') { if (in_evar) { *eptr = '\0'; envp = nh_getenv(evar); if (envp) { size_t elen = strlen(envp); if ((elen + destcount) < (size_t) (BUFSZ - 1)) { Strcpy(dest, envp); dest += elen; destcount += elen; } } } else { eptr = evar; ecount = 0; } in_evar = !in_evar; continue; } if (in_evar) { *eptr++ = *src; ecount++; } else { *dest++ = *src; destcount++; } } *dest = '\0'; return buf; } DISABLE_WARNING_UNREACHABLE_CODE int get_known_folder_path(const KNOWNFOLDERID *folder_id, char *path, size_t path_size) { PWSTR wide_path; if (FAILED(SHGetKnownFolderPath(folder_id, 0, NULL, &wide_path))) { error("Unable to get known folder path"); return FALSE; } size_t converted; errno_t err; err = wcstombs_s(&converted, path, path_size, wide_path, _TRUNCATE); CoTaskMemFree(wide_path); if (err == STRUNCATE || err == EILSEQ) { // silently handle this problem return FALSE; } else if (err != 0) { error( "Failed folder (%lu) path string conversion, unexpected err = %d", folder_id->Data1, err); return FALSE; } return TRUE; } void create_directory(const char *path) { BOOL dres = CreateDirectoryA(path, NULL); if (!dres) { DWORD dw = GetLastError(); if (dw != ERROR_ALREADY_EXISTS) error("Unable to create directory '%s'", path); } } RESTORE_WARNING_UNREACHABLE_CODE int build_known_folder_path(const KNOWNFOLDERID *folder_id, char *path, size_t path_size, boolean versioned) { if (!get_known_folder_path(folder_id, path, path_size)) return FALSE; strcat(path, "\\NetHack\\"); create_directory(path); if (versioned) { Sprintf(eos(path), "%d.%d\\", VERSION_MAJOR, VERSION_MINOR); create_directory(path); } return TRUE; } void build_environment_path(const char *env_str, const char *folder, char *path, size_t path_size) { path[0] = '\0'; const char *root_path = nh_getenv(env_str); if (root_path == NULL) return; strcpy_s(path, path_size, root_path); char *colon = strchr(path, ';'); if (colon != NULL) path[0] = '\0'; if (strlen(path) == 0) return; append_slash(path); if (folder != NULL) { strcat_s(path, path_size, folder); strcat_s(path, path_size, "\\"); } } boolean folder_file_exists(const char *folder, const char *file_name) { char path[MAX_PATH]; if (folder[0] == '\0') return FALSE; strcpy(path, folder); strcat(path, file_name); return file_exists(path); } boolean file_exists(const char *path) { struct stat sb; /* Just see if it's there */ if (stat(path, &sb)) { return FALSE; } return TRUE; } void set_default_prefix_locations(const char *programPath UNUSED) { static char executable_path[MAX_PATH]; static char profile_path[MAX_PATH]; static char versioned_profile_path[MAX_PATH]; static char versioned_user_data_path[MAX_PATH]; static char versioned_global_data_path[MAX_PATH]; /* static char versioninfo[20] UNUSED; */ strcpy(executable_path, get_executable_path()); append_slash(executable_path); #ifndef SFCTOOL if (test_portable_config(executable_path, portable_device_path, sizeof portable_device_path)) { gf.fqn_prefix[SYSCONFPREFIX] = executable_path; gf.fqn_prefix[CONFIGPREFIX] = portable_device_path; gf.fqn_prefix[HACKPREFIX] = portable_device_path; gf.fqn_prefix[SAVEPREFIX] = portable_device_path; gf.fqn_prefix[LEVELPREFIX] = portable_device_path; gf.fqn_prefix[BONESPREFIX] = portable_device_path; gf.fqn_prefix[SCOREPREFIX] = portable_device_path; gf.fqn_prefix[LOCKPREFIX] = portable_device_path; gf.fqn_prefix[TROUBLEPREFIX] = portable_device_path; gf.fqn_prefix[DATAPREFIX] = executable_path; } else { #endif /* SFCTOOL */ if (!build_known_folder_path(&FOLDERID_Profile, profile_path, sizeof(profile_path), FALSE)) strcpy(profile_path, executable_path); if (!build_known_folder_path(&FOLDERID_Profile, versioned_profile_path, sizeof(profile_path), TRUE)) strcpy(versioned_profile_path, executable_path); if (!build_known_folder_path(&FOLDERID_LocalAppData, versioned_user_data_path, sizeof(versioned_user_data_path), TRUE)) strcpy(versioned_user_data_path, executable_path); if (!build_known_folder_path( &FOLDERID_ProgramData, versioned_global_data_path, sizeof(versioned_global_data_path), TRUE)) strcpy(versioned_global_data_path, executable_path); gf.fqn_prefix[SYSCONFPREFIX] = versioned_global_data_path; gf.fqn_prefix[CONFIGPREFIX] = profile_path; gf.fqn_prefix[HACKPREFIX] = versioned_profile_path; gf.fqn_prefix[SAVEPREFIX] = versioned_user_data_path; gf.fqn_prefix[LEVELPREFIX] = versioned_user_data_path; gf.fqn_prefix[BONESPREFIX] = versioned_global_data_path; gf.fqn_prefix[SCOREPREFIX] = versioned_global_data_path; gf.fqn_prefix[LOCKPREFIX] = versioned_global_data_path; gf.fqn_prefix[TROUBLEPREFIX] = versioned_profile_path; gf.fqn_prefix[DATAPREFIX] = executable_path; #ifndef SFCTOOL } #endif /* SFCTOOL */ } /* * Add a backslash to any name not ending in /, \ or : There must * be room for the \ */ void append_slash(char *name) { char *ptr; if (!*name) return; ptr = name + (strlen(name) - 1); if (*ptr != '\\' && *ptr != '/' && *ptr != ':') { *++ptr = '\\'; *++ptr = '\0'; } return; } void set_default_prefix_locations(const char *programPath); boolean test_portable_config(const char *executable_path, char *portable_device_path, size_t portable_device_path_size) { int lth = 0; const char *sysconf = "sysconf"; char tmppath[MAX_PATH]; boolean retval = FALSE, save_initoptions_noterminate = iflags.initoptions_noterminate; if (portable_device_path && folder_file_exists(executable_path, "sysconf")) { /* There is a sysconf file (not just sysconf.template) present in the exe path, which is not the way NetHack is initially distributed, so assume it means that the admin/installer wants to override something, perhaps set up for a fully-portable configuration that leaves no traces behind elsewhere on this computer's hard drive - delve into that... */ *portable_device_path = '\0'; lth = sizeof tmppath - strlen(sysconf); (void) strncpy(tmppath, executable_path, lth - 1); tmppath[lth - 1] = '\0'; (void) strcat(tmppath, sysconf); iflags.initoptions_noterminate = 1; /* assure_syscf_file(); */ config_error_init(TRUE, tmppath, FALSE); /* ... and _must_ parse correctly. */ if (read_config_file(tmppath, set_in_sysconf) && sysopt.portable_device_paths) retval = TRUE; (void) config_error_done(); iflags.initoptions_noterminate = save_initoptions_noterminate; sysopt_release(); /* the real sysconf processing comes later */ } if (retval) { lth = strlen(executable_path); if (lth <= (int) portable_device_path_size - 1) Strcpy(portable_device_path, executable_path); else retval = FALSE; } return retval; } const char * get_portable_device(void) { return (const char *) portable_device_path; } #ifndef SFCTOOL /* Windows helpers for CRASHREPORT etc */ #ifdef CRASHREPORT struct CRctxt { BCRYPT_ALG_HANDLE bah; BCRYPT_HASH_HANDLE bhh; PBYTE pbhashobj; DWORD cbhashobj; /* temp */ DWORD cbhash; /* hash length */ DWORD cbdata; /* temp */ PBYTE pbhash; /* binary hash */ NTSTATUS st; } ctxp_ = { NULL, NULL, NULL, 0, 0, 0, NULL, 0 }; struct CRctxt *ctxp = &ctxp_; // XXX should this now be in gc.* ? #define win32err(fn) errname = (char *) fn; goto error int win32_cr_helper(char cmd, struct CRctxt *contextp, void *p, int d){ char *errname = (char *) "unknown"; switch (cmd) { default: /* Not panic - we don't want to upgrade an impossible to a * panic due to a bug in the CRASHREPORT code. */ impossible("win_cr_helper bad cmd %d", cmd); return 1; case 'D': { char *bidstr = (char *) p; wchar_t lbidstr[40]; // sizeof(bid), but const swprintf_s(lbidstr, 40, L"%S", bidstr); // XXX TODO: need something that will allow copy of just the bid return MessageBoxW(NULL, lbidstr, L"bidshow", MB_SETFOREGROUND); } break; case 'i': /* HASH_INIT(contextp) */ if (!IsWindowsVistaOrGreater()) return 1; // CNG not available. contextp->bah = NULL; contextp->bhh = NULL; contextp->pbhashobj = NULL; contextp->cbhashobj = 0; contextp->cbhash = 0; contextp->cbdata = 0; contextp->pbhash = NULL; contextp->st = 0; // win32err("test"); // TESTING - FAKE AN ERROR if (0 > (contextp->st = BCryptOpenAlgorithmProvider( &contextp->bah, BCRYPT_MD4_ALGORITHM, NULL, 0))) { win32err("BCryptOpenAlgorithmProvider"); }; if (0 > (contextp->st = BCryptGetProperty(contextp->bah, BCRYPT_OBJECT_LENGTH, (unsigned char *) &contextp->cbhashobj, sizeof(DWORD), &contextp->cbdata, 0))) { win32err("BCryptGetProperty1"); }; if (0 == (contextp->pbhashobj = HeapAlloc(GetProcessHeap(), 0, contextp->cbhashobj))) { win32err("HeapAlloc1"); }; if (0 > (contextp->st = BCryptGetProperty( contextp->bah, BCRYPT_HASH_LENGTH, (PBYTE) &contextp->cbhash, sizeof(DWORD), &contextp->cbdata, 0))) { win32err("BCryptGetProperty2"); } if (0 == (contextp->pbhash = HeapAlloc(GetProcessHeap(), 0, contextp->cbhash))) { win32err("HeapAlloc2\n"); } if (0 > BCryptCreateHash(contextp->bah, &contextp->bhh, contextp->pbhashobj, contextp->cbhashobj, NULL, 0, 0)) { win32err("BCryptCreateHash"); } break; case 'u': /* HASH_UPDATE(contextp, ptr, len) */ if (0 > (contextp->st = BCryptHashData(contextp->bhh, p, d, 0))) { win32err("BCryptHashData"); } break; case 'f': /* HASH_FINISH(contextp) */ if (0 > BCryptFinishHash(contextp->bhh, contextp->pbhash, contextp->cbhash, 0)) { win32err("BCryptFinishHash"); } break; case 'c': /* HASH_CLEANUP(contextp) */ if (contextp->bah) { BCryptCloseAlgorithmProvider(contextp->bah, 0); } if (contextp->bhh) { BCryptDestroyHash(contextp->bhh); } if (contextp->pbhashobj) { HeapFree(GetProcessHeap(), 0, contextp->pbhashobj); } if (contextp->pbhash) { HeapFree(GetProcessHeap(), 0, contextp->pbhash); } break; case 's': /* HASH_RESULT_SIZE(contextp) */ return contextp->cbhash; case 'r': /* HASH_RESULT(contextp, resp) */ *(unsigned char **)p = contextp->pbhash; break; case 'b': /* HASH_BINFILE(NULL,&binfile,0) */ // XXX This buffer should be allocated, not static (and freed in // HASH_CLEANUP). // NB: assumes !longPathAware in manifest (Win10+) { static char binfile[MAX_PATH]; DWORD rv = GetModuleFileNameA(NULL, binfile, sizeof(binfile)); if (rv == 0 || rv == sizeof(binfile)) return 1; #if (NH_DEVEL_STATUS == NH_STATUS_BETA) printf("FILE '%s'\n", binfile); #endif *(unsigned char **) p = (unsigned char *) binfile; return 0; } } return 0; /* ok */ error: raw_printf("WIN32 function %s failed: status=%" PRIx64 "\n", errname, (uint64)contextp->st); return 1; /* fail */ } #undef win32err #include #define MAX_SYM_SIZE 100 #ifdef __GNUC__ // gcc can't generate .pdb files. llvm can almost do it. // For these platforms, use github/ianlancetaylor/libbacktrace. // XXX this doesn't work yet - we get correct addresses but no symbol info // XXX so still needs cleanup // XXX no mark (overflow held to last valid segment) handling yet // XXX libbacktrace isn't available by default, so don't try until it works //#define USE_BACKTRACE #ifdef USE_BACKTRACE #include struct userstate { int error_count; int good_count; char *out; int outsize; int maxframes; } userstate; //backtrace_full_callback static int btfcb_fn(void *us0, uintptr_t pc, const char *filename, int lineno, const char *fnname){ struct userstate *us = us0; //XXX generate a stack frame line printf("C: pc=%llx f=%s line=%d fn=%s\n",pc,filename,lineno,fnname); us->good_count++; return 0; } //backtrace_error_callback static void btecb_fn(void *us0, const char *msg, int errnum){ struct userstate *us = us0; us->error_count++; if(errnum < 0){ printf("E1: M=%s e=%d\n",msg,errnum); // XXX save error message } else { printf("E2: M=%s e=%d\n",msg,errnum); // errnum is an errno //XXX save error message with strerror } } #endif #ifdef USE_BACKTRACE #define USED_IF_BACKTRACE #else #define USED_IF_BACKTRACE UNUSED #endif int win32_cr_gettrace(int maxframes USED_IF_BACKTRACE, char *out USED_IF_BACKTRACE, int outsize USED_IF_BACKTRACE) { #ifdef USE_BACKTRACE userstate.error_count = 0; userstate.good_count = 0; userstate.out = out; userstate.outsize = outsize; userstate.maxframes = maxframes; static char binfile[MAX_PATH];// assumes !longPathAware in manifest (Win10+) DWORD rv = GetModuleFileNameA(NULL, binfile, sizeof(binfile)); struct backtrace_state *state = backtrace_create_state(binfile, 0, btecb_fn, &userstate); if(!state) return userstate.error_count + userstate.good_count; rv=backtrace_full(state, 0, btfcb_fn, btecb_fn, &userstate); printf("rv=%d\n",rv); // XXX rv not checked // XXX this API leaks its memory; there is no free function return userstate.error_count + userstate.good_count; #else return 0; #endif } #else // Use win32 API with Visual Studio (and probably MSVC). #include int win32_cr_gettrace(int maxframes, char *out, int outsize){ char *mark = out; void *frames[200]; // XXX why does VS fail var array? wrong C std? int x; int tmp; #define BSIZE (MAX_SYM_SIZE+50) char buf[BSIZE]; HANDLE me = GetCurrentProcess(); SetLastError(0); // XXX may need to pass the binary's dir //XXX check for different flags if(!SymInitialize(me, NULL, TRUE)){ snprintf(buf, BSIZE, "no stack trace: SymInitialize: %d\n", (int) GetLastError()); return 1; } int fcount = CaptureStackBackTrace(0, maxframes,frames,NULL); if(!fcount)goto finish; char symbol_info_space[sizeof(SYMBOL_INFO)+MAX_SYM_SIZE]; SYMBOL_INFO *si = (SYMBOL_INFO *)symbol_info_space; si->MaxNameLen = MAX_SYM_SIZE; si->SizeOfStruct = sizeof(SYMBOL_INFO); for(x=0;xName[0], (long long int)disp64); if(swr_add_uricoded(buf, &out, &outsize, mark)) goto finish; #if 1 // XXX does this block do anything useful? DWORD disp = (DWORD) disp64; IMAGEHLP_LINE ihl; ihl.SizeOfStruct = sizeof(IMAGEHLP_LINE); if (SymGetLineFromAddr(me, adr, &disp, &ihl)) { printf("L=%d\n", (int) ihl.LineNumber); } else { // 7e/1e7 - no info. May need to call SymLoadModule if we need those addrs // BUT probably system code, so we don't care - experiment // printf("SGLFA failed: $%08x\n", GetLastError()); } //XXXnow format the line #endif } else { // Error 487 (invalid address) seems to mean // "I can't find any info for this address". tmp = snprintf(buf, BSIZE, "%d %p (error %d)\n", x, frames[x], (int) GetLastError()); if(swr_add_uricoded(buf, &out, &outsize, mark)) goto finish; } if(tmp < 0 || tmp >= outsize){ // XXX is test now wrong? //printf("FAIL tmp=%d\n",tmp); fcount = x+1; goto finish; } mark = out; } finish: SymCleanup(me); return fcount; // XXX if output truncated, fcount could be wrong } #endif int * win32_cr_shellexecute(const char *url){ //XXX Docs say to do this, but has so many caveats I'm going to try skipping it. //CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); int *rv = (int*)ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); return rv; } #endif /* CRASHREPORT */ #endif /* SFCTOOL */ #endif /* WIN32 */ /*windsys.c*/