gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:
The range and shape of "falls through" comments accepted are
contingent upon the level of the warning. (The default level is =3.)
-Wimplicit-fallthrough=0 disables the warning altogether.
-Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
-Wimplicit-fallthrough=2 essentially accepts any comment that contains something
that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
-Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
expressions, listed in the GCC manual. E.g., all of these are accepted:
/* Falls through. */
/* fall-thru */
/* Else falls through. */
/* FALLTHRU */
/* ... falls through ... */
etc.
-Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
expressions but is much more strict than level =3.
-Wimplicit-fallthrough=5 doesn't recognize any comments.
Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.
The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.
Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:
C compiler whitelist approach
--------------------------- -------------------------------------
C23 conforming compilers [[fallthrough]]
clang versions supporting
standards prior to
C23 __attribute__((__fallthrough__))
Microsoft Visual Studio
since VS 2022 17.4.
The warning C5262 controls
whether the implict
fallthrough is detected and
warned about with
/std:clatest. [[fallthrough]]
This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.
The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.
The gcc comment approach has also been left in place at this time.
1619 lines
45 KiB
C
1619 lines
45 KiB
C
/* NetHack 3.7 windmain.c $NHDT-Date: 1693359653 2023/08/30 01:40:53 $ $NHDT-Branch: keni-crashweb2 $:$NHDT-Revision: 1.189 $ */
|
|
/* Copyright (c) Derek S. Ray, 2015. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/* main.c - Windows */
|
|
|
|
#include "win32api.h" /* for GetModuleFileName */
|
|
|
|
#include "hack.h"
|
|
#ifdef DLB
|
|
#include "dlb.h"
|
|
#endif
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <sys\stat.h>
|
|
#include <errno.h>
|
|
#include <ShlObj.h>
|
|
|
|
#if !defined(SAFEPROCS)
|
|
#error You must #define SAFEPROCS to build windmain.c
|
|
#endif
|
|
|
|
static void process_options(int argc, char **argv);
|
|
static void nhusage(void);
|
|
static char *get_executable_path(void);
|
|
char *translate_path_variables(const char *, char *);
|
|
char *exename(void);
|
|
boolean fakeconsole(void);
|
|
void freefakeconsole(void);
|
|
ATTRNORETURN extern void nethack_exit(int) NORETURN;
|
|
#if defined(MSWIN_GRAPHICS)
|
|
extern void mswin_destroy_reg(void);
|
|
#endif
|
|
#ifdef TTY_GRAPHICS
|
|
#ifdef WIN32CON
|
|
extern void backsp(void);
|
|
#endif
|
|
#endif
|
|
extern void term_clear_screen(void);
|
|
|
|
#ifdef update_file
|
|
#undef update_file
|
|
#endif
|
|
#if defined(TERMLIB) || defined(CURSES_GRAPHICS)
|
|
extern void (*decgraphics_mode_callback)(void);
|
|
#endif
|
|
#ifdef CURSES_GRAPHICS
|
|
extern void (*cursesgraphics_mode_callback)(void);
|
|
#endif
|
|
#ifdef ENHANCED_SYMBOLS
|
|
extern void (*utf8graphics_mode_callback)(void);
|
|
#endif
|
|
|
|
#ifdef WIN32CON
|
|
#ifdef _MSC_VER
|
|
#ifdef kbhit
|
|
#undef kbhit
|
|
#endif
|
|
#include <conio.h.>
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef PC_LOCKING
|
|
static int eraseoldlocks(void);
|
|
#endif
|
|
|
|
int windows_nhgetch(void);
|
|
void windows_nhbell(void);
|
|
int windows_nh_poskey(int *, int *, int *);
|
|
void windows_raw_print(const char *);
|
|
char windows_yn_function(const char *, const char *, char);
|
|
static void windows_getlin(const char *, char *);
|
|
|
|
#ifdef WIN32CON
|
|
extern int windows_console_custom_nhgetch(void);
|
|
void safe_routines(void);
|
|
int tty_self_recover_prompt(void);
|
|
#endif
|
|
|
|
int other_self_recover_prompt(void);
|
|
|
|
char orgdir[PATHLEN];
|
|
boolean getreturn_enabled;
|
|
int windows_startup_state = 0; /* we flag whether to continue with this */
|
|
/* 0 = keep starting up, everything is good */
|
|
|
|
extern int redirect_stdout; /* from sys/share/pcsys.c */
|
|
extern int GUILaunched;
|
|
HANDLE hStdOut;
|
|
char default_window_sys[7] =
|
|
#if defined(MSWIN_GRAPHICS)
|
|
"mswin";
|
|
#elif defined(TTY_GRAPHICS)
|
|
"tty";
|
|
#endif
|
|
#ifdef WANT_GETHDATE
|
|
static struct stat hbuf;
|
|
#endif
|
|
#include <sys/stat.h>
|
|
|
|
|
|
extern char orgdir[];
|
|
|
|
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);
|
|
void set_default_prefix_locations(const char *programPath);
|
|
void copy_sysconf_content(void);
|
|
void copy_config_content(void);
|
|
void copy_hack_content(void);
|
|
void copy_symbols_content(void);
|
|
|
|
#ifdef PORT_HELP
|
|
void port_help(void);
|
|
#endif
|
|
void windows_raw_print(const char *str);
|
|
|
|
extern const char *known_handling[]; /* symbols.c */
|
|
extern const char *known_restrictions[]; /* symbols.c */
|
|
|
|
/* --------------------------------------------------------------------------- */
|
|
|
|
DISABLE_WARNING_UNREACHABLE_CODE
|
|
|
|
/*
|
|
* NetHack main
|
|
*
|
|
* The following function is used in both the nongraphical nethack.exe, and
|
|
* in the graphical nethackw.exe.
|
|
*
|
|
* The function below is called main() in the non-graphical build of
|
|
* NetHack for Windows and is the primary entry point for the program.
|
|
*
|
|
* It is called nethackw_main() in the graphical build of NetHack for
|
|
* Windows, where WinMain() is the primary entry point for the program
|
|
* and this nethackw_main() is called as a sub function.
|
|
*
|
|
* The code in WinMain() (in win/win32/nethackw.c) is primarily focused
|
|
* on setting up the graphical windows environment, and leaves the
|
|
* NetHack-specific startup code to this function.
|
|
*
|
|
*/
|
|
|
|
#if defined(MSWIN_GRAPHICS)
|
|
#define MAIN nethackw_main
|
|
#else
|
|
#define MAIN main
|
|
#endif
|
|
|
|
int
|
|
MAIN(int argc, char *argv[])
|
|
{
|
|
boolean resuming = FALSE; /* assume new game */
|
|
NHFILE *nhfp;
|
|
char *windowtype = NULL;
|
|
char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
|
|
char failbuf[BUFSZ];
|
|
int getlock_result = 0;
|
|
HWND hwnd;
|
|
HDC hdc;
|
|
int bpp;
|
|
|
|
#ifdef _MSC_VER
|
|
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
|
|
#endif
|
|
|
|
#ifdef WIN32CON
|
|
/*
|
|
* Get a set of valid safe windowport function
|
|
* pointers during early startup initialization.
|
|
*/
|
|
safe_routines();
|
|
#endif /* WIN32CON */
|
|
|
|
#ifndef MSWIN_GRAPHICS
|
|
early_init(argc, argv); /* already in WinMain for MSWIN_GRAPHICS */
|
|
#endif
|
|
/* setting iflags.colorcount has to be after early_init()
|
|
* because it zeros out all of iflags */
|
|
hwnd = GetDesktopWindow();
|
|
hdc = GetDC(hwnd);
|
|
if (hdc) {
|
|
bpp = GetDeviceCaps(hdc, BITSPIXEL);
|
|
iflags.colorcount = (bpp >= 16) ? 16777216 : (bpp >= 8) ? 256 : 16;
|
|
ReleaseDC(hwnd, hdc);
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#ifdef DEBUG
|
|
/* set these appropriately for VS debugging */
|
|
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
|
|
_CrtSetReportMode(_CRT_ERROR,
|
|
_CRTDBG_MODE_DEBUG); /* | _CRTDBG_MODE_FILE);*/
|
|
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
|
|
/*| _CRTDBG_MODE_FILE | _CRTDBG_MODE_WNDW);*/
|
|
/* use STDERR by default
|
|
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
|
|
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);*/
|
|
/* Heap Debugging
|
|
_CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)
|
|
| _CRTDBG_ALLOC_MEM_DF
|
|
| _CRTDBG_CHECK_ALWAYS_DF
|
|
| _CRTDBG_CHECK_CRT_DF
|
|
| _CRTDBG_DELAY_FREE_MEM_DF
|
|
| _CRTDBG_LEAK_CHECK_DF);
|
|
_CrtSetBreakAlloc(1423);
|
|
*/
|
|
#endif
|
|
#endif
|
|
|
|
gh.hname = "NetHack"; /* used for syntax messages */
|
|
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
/* Save current directory and make sure it gets restored when
|
|
* the game is exited.
|
|
*/
|
|
if (getcwd(orgdir, sizeof orgdir) == (char *) 0)
|
|
error("NetHack: current directory path too long");
|
|
#endif
|
|
initoptions_init(); // This allows OPTIONS in syscf on Windows.
|
|
set_default_prefix_locations(argv[0]);
|
|
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdir(gf.fqn_prefix[HACKPREFIX]);
|
|
#endif
|
|
|
|
/* if (GUILaunched || IsDebuggerPresent()) */
|
|
getreturn_enabled = TRUE;
|
|
|
|
check_recordfile((char *) 0);
|
|
iflags.windowtype_deferred = TRUE;
|
|
copy_sysconf_content();
|
|
copy_symbols_content();
|
|
initoptions();
|
|
|
|
/* Now that sysconf has had a chance to set the TROUBLEPREFIX, don't
|
|
allow it to be changed from here on out. */
|
|
fqn_prefix_locked[TROUBLEPREFIX] = TRUE;
|
|
|
|
copy_config_content();
|
|
process_options(argc, argv);
|
|
|
|
/* did something earlier flag a need to exit without starting a game? */
|
|
if (windows_startup_state > 0) {
|
|
raw_printf("Exiting.");
|
|
nethack_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Finished processing options, lock all directory paths */
|
|
for (int i = 0; i < PREFIX_COUNT; i++)
|
|
fqn_prefix_locked[i] = TRUE;
|
|
|
|
if (!validate_prefix_locations(failbuf)) {
|
|
raw_printf("Some invalid directory locations were specified:\n\t%s\n",
|
|
failbuf);
|
|
nethack_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
copy_hack_content();
|
|
|
|
/*
|
|
* It seems you really want to play.
|
|
*/
|
|
#ifndef CURSES_GRAPHICS
|
|
if (argc >= 1 && !strcmpi(default_window_sys, "mswin")
|
|
&& (strstri(argv[0], "nethackw.exe") || GUILaunched))
|
|
iflags.windowtype_locked = TRUE;
|
|
#endif
|
|
windowtype = default_window_sys;
|
|
|
|
#ifdef DLB
|
|
if (!dlb_init()) {
|
|
pline("%s\n%s\n%s\n%s\n\n", copyright_banner_line(1),
|
|
copyright_banner_line(2), copyright_banner_line(3),
|
|
copyright_banner_line(4));
|
|
pline("NetHack was unable to open the required file \"%s\"", DLBFILE);
|
|
if (file_exists(DLBFILE))
|
|
pline("\nAre you perhaps trying to run NetHack within a zip "
|
|
"utility?");
|
|
error("dlb_init failure.");
|
|
}
|
|
#endif
|
|
|
|
if (!iflags.windowtype_locked) {
|
|
#if defined(TTY_GRAPHICS)
|
|
Strcpy(default_window_sys, "tty");
|
|
#else
|
|
#if defined(CURSES_GRAPHICS) && !defined(MSWIN_GRAPHICS)
|
|
Strcpy(default_window_sys, "curses");
|
|
#endif /* CURSES */
|
|
#endif /* TTY */
|
|
if (iflags.windowtype_deferred && gc.chosen_windowtype[0])
|
|
windowtype = gc.chosen_windowtype;
|
|
}
|
|
choose_windows(windowtype);
|
|
#if defined(SND_LIB_FMOD)
|
|
assign_soundlib(soundlib_fmod);
|
|
#elif defined(SND_LIB_WINDSOUND)
|
|
assign_soundlib(soundlib_windsound);
|
|
#endif
|
|
|
|
u.uhp = 1; /* prevent RIP on early quits */
|
|
u.ux = 0; /* prevent flush_screen() */
|
|
|
|
nethack_enter(argc, argv);
|
|
iflags.use_background_glyph = FALSE;
|
|
if (WINDOWPORT(mswin))
|
|
iflags.use_background_glyph = TRUE;
|
|
#ifdef WIN32CON
|
|
if (WINDOWPORT(tty))
|
|
consoletty_open(1);
|
|
#endif
|
|
#ifdef WINCHAIN
|
|
commit_windowchain();
|
|
#endif
|
|
|
|
init_nhwindows(&argc, argv);
|
|
|
|
#ifdef WIN32CON
|
|
if (WINDOWPORT(tty))
|
|
toggle_mouse_support();
|
|
#endif
|
|
|
|
if (gs.symset[PRIMARYSET].handling
|
|
&& !symset_is_compatible(gs.symset[PRIMARYSET].handling,
|
|
windowprocs.wincap2)) {
|
|
/* current symset handling and windowtype are
|
|
not compatible, feature-wise. Use IBM defaults */
|
|
load_symset("IBMGraphics_2", PRIMARYSET);
|
|
load_symset("RogueEpyx", ROGUESET);
|
|
}
|
|
/* Has the callback for the symset been invoked? Config file processing to
|
|
load a symset runs too early to accomplish that because
|
|
the various *graphics_mode_callback pointers don't get set until
|
|
term_start_screen, unfortunately */
|
|
#if defined(TERMLIB) || defined(CURSES_GRAPHICS)
|
|
if (SYMHANDLING(H_DEC) && decgraphics_mode_callback)
|
|
(*decgraphics_mode_callback)();
|
|
#endif /* TERMLIB || CURSES */
|
|
#if 0
|
|
#ifdef CURSES_GRAPHICS
|
|
if (WINDOWPORT(curses))
|
|
(*cursesgraphics_mode_callback)();
|
|
#endif
|
|
#endif
|
|
#ifdef ENHANCED_SYMBOLS
|
|
if (SYMHANDLING(H_UTF8) && utf8graphics_mode_callback)
|
|
(*utf8graphics_mode_callback)();
|
|
#endif
|
|
|
|
/* strip role,race,&c suffix; calls askname() if svp.plname[] is empty
|
|
or holds a generic user name like "player" or "games" */
|
|
plnamesuffix();
|
|
set_playmode(); /* sets svp.plname to "wizard" for wizard mode */
|
|
/* until the getlock code is resolved, override askname()'s
|
|
setting of renameallowed; when False, player_selection()
|
|
won't resent renaming as an option */
|
|
iflags.renameallowed = FALSE;
|
|
/* Obtain the name of the logged on user and incorporate
|
|
* it into the name. */
|
|
Sprintf(fnamebuf, "%s", svp.plname);
|
|
(void) fname_encode(
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.", '%',
|
|
fnamebuf, encodedfnamebuf, BUFSZ);
|
|
Sprintf(gl.lock, "%s", encodedfnamebuf);
|
|
/* regularize(lock); */ /* we encode now, rather than substitute */
|
|
if ((getlock_result = getlock()) == 0)
|
|
nethack_exit(EXIT_SUCCESS);
|
|
|
|
if (getlock_result < 0) {
|
|
set_savefile_name(TRUE);
|
|
}
|
|
/* Set up level 0 file to keep the game state.
|
|
*/
|
|
nhfp = create_levelfile(0, (char *) 0);
|
|
if (!nhfp) {
|
|
raw_print("Cannot create lock file");
|
|
} else {
|
|
svh.hackpid = GetCurrentProcessId();
|
|
(void) write(nhfp->fd, (genericptr_t) &svh.hackpid, sizeof(svh.hackpid));
|
|
close_nhfile(nhfp);
|
|
}
|
|
/*
|
|
* Initialize the vision system. This must be before mklev() on a
|
|
* new game or before a level restore on a saved game.
|
|
*/
|
|
vision_init();
|
|
init_sound_disp_gamewindows();
|
|
/*
|
|
* First, try to find and restore a save file for specified character.
|
|
* We'll return here if new game player_selection() renames the hero.
|
|
*/
|
|
attempt_restore:
|
|
if ((getlock_result != -1) && (nhfp = restore_saved_game()) != 0) {
|
|
#ifdef NEWS
|
|
if (iflags.news) {
|
|
display_file(NEWS, FALSE);
|
|
iflags.news = FALSE;
|
|
}
|
|
#endif
|
|
if (ge.early_raw_messages)
|
|
raw_print("Restoring save file...");
|
|
else
|
|
pline("Restoring save file...");
|
|
mark_synch(); /* flush output */
|
|
if (dorecover(nhfp)) {
|
|
resuming = TRUE; /* not starting new game */
|
|
if (discover)
|
|
You("are in non-scoring discovery mode.");
|
|
if (discover || wizard) {
|
|
if (y_n("Do you want to keep the save file?") == 'n')
|
|
(void) delete_savefile();
|
|
else {
|
|
nh_compress(fqname(gs.SAVEF, SAVEPREFIX, 0));
|
|
}
|
|
}
|
|
}
|
|
if (program_state.in_self_recover) {
|
|
program_state.in_self_recover = FALSE;
|
|
set_savefile_name(TRUE);
|
|
}
|
|
}
|
|
|
|
if (!resuming) {
|
|
/* new game: start by choosing role, race, etc;
|
|
player might change the hero's name while doing that,
|
|
in which case we try to restore under the new name
|
|
and skip selection this time if that didn't succeed */
|
|
if (!iflags.renameinprogress) {
|
|
player_selection();
|
|
if (iflags.renameinprogress) {
|
|
/* player has renamed the hero while selecting role;
|
|
discard current lock file and create another for
|
|
the new character name */
|
|
goto attempt_restore;
|
|
}
|
|
}
|
|
newgame();
|
|
if (discover)
|
|
You("are in non-scoring discovery mode.");
|
|
}
|
|
|
|
// iflags.debug_fuzzer = TRUE;
|
|
|
|
moveloop(resuming);
|
|
nethack_exit(EXIT_SUCCESS);
|
|
/*NOTREACHED*/
|
|
return 0;
|
|
}
|
|
|
|
RESTORE_WARNING_UNREACHABLE_CODE
|
|
|
|
static void
|
|
process_options(int argc, char * argv[])
|
|
{
|
|
int i;
|
|
|
|
/*
|
|
* Process options.
|
|
*/
|
|
if (argc > 1) {
|
|
if (argcheck(argc, argv, ARG_VERSION) == 2)
|
|
nethack_exit(EXIT_SUCCESS);
|
|
|
|
if (argcheck(argc, argv, ARG_SHOWPATHS) == 2) {
|
|
iflags.initoptions_noterminate = TRUE;
|
|
initoptions();
|
|
iflags.initoptions_noterminate = FALSE;
|
|
reveal_paths();
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
#ifndef NODUMPENUMS
|
|
if (argcheck(argc, argv, ARG_DUMPENUMS) == 2) {
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
#ifdef ENHANCED_SYMBOLS
|
|
if (argcheck(argc, argv, ARG_DUMPGLYPHIDS) == 2) {
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
#endif
|
|
#endif
|
|
if (argcheck(argc, argv, ARG_DEBUG) == 1) {
|
|
argc--;
|
|
argv++;
|
|
}
|
|
if (argcheck(argc, argv, ARG_WINDOWS) == 1) {
|
|
argc--;
|
|
argv++;
|
|
}
|
|
#if defined(CRASHREPORT)
|
|
if (argcheck(argc, argv, ARG_BIDSHOW) == 2) {
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
#endif
|
|
if (argc > 1 && !strncmp(argv[1], "-d", 2) && argv[1][2] != 'e') {
|
|
/* avoid matching "-dec" for DECgraphics; since the man page
|
|
* says -d directory, hope nobody's using -desomething_else
|
|
*/
|
|
argc--;
|
|
argv++;
|
|
const char * dir = argv[0] + 2;
|
|
if (*dir == '=' || *dir == ':')
|
|
dir++;
|
|
if (!*dir && argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
dir = argv[0];
|
|
}
|
|
if (!*dir)
|
|
error("Flag -d must be followed by a directory name.");
|
|
Strcpy(gh.hackdir, dir);
|
|
}
|
|
|
|
if (argc > 1) {
|
|
/*
|
|
* Now we know the directory containing 'record' and
|
|
* may do a prscore().
|
|
*/
|
|
if (!strncmp(argv[1], "-s", 2)) {
|
|
#ifdef SYSCF
|
|
initoptions();
|
|
#endif
|
|
prscore(argc, argv);
|
|
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
#ifdef MSWIN_GRAPHICS
|
|
if (GUILaunched) {
|
|
if (!strncmpi(argv[1], "-clearreg", 6)) { /* clear registry */
|
|
mswin_destroy_reg();
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
}
|
|
#endif
|
|
/* Don't initialize the full window system just to print usage */
|
|
if (!strncmp(argv[1], "-?", 2) || !strncmp(argv[1], "/?", 2)) {
|
|
nhusage();
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
while (argc > 1 && argv[1][0] == '-') {
|
|
argv++;
|
|
argc--;
|
|
switch (argv[0][1]) {
|
|
case 'a':
|
|
if (argv[0][2]) {
|
|
if ((i = str2align(&argv[0][2])) >= 0)
|
|
flags.initalign = i;
|
|
} else if (argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
if ((i = str2align(argv[0])) >= 0)
|
|
flags.initalign = i;
|
|
}
|
|
break;
|
|
case 'D':
|
|
wizard = TRUE, discover = FALSE;
|
|
break;
|
|
case 'X':
|
|
discover = TRUE, wizard = FALSE;
|
|
break;
|
|
#ifdef NEWS
|
|
case 'n':
|
|
iflags.news = FALSE;
|
|
break;
|
|
#endif
|
|
case 'u':
|
|
if (argv[0][2])
|
|
(void) strncpy(svp.plname, argv[0] + 2, sizeof(svp.plname) - 1);
|
|
else if (argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
(void) strncpy(svp.plname, argv[0], sizeof(svp.plname) - 1);
|
|
} else
|
|
raw_print("Player name expected after -u");
|
|
break;
|
|
case 'g':
|
|
if (argv[0][2]) {
|
|
if ((i = str2gend(&argv[0][2])) >= 0)
|
|
flags.initgend = i;
|
|
} else if (argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
if ((i = str2gend(argv[0])) >= 0)
|
|
flags.initgend = i;
|
|
}
|
|
break;
|
|
case 'p': /* profession (role) */
|
|
if (argv[0][2]) {
|
|
if ((i = str2role(&argv[0][2])) >= 0)
|
|
flags.initrole = i;
|
|
} else if (argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
if ((i = str2role(argv[0])) >= 0)
|
|
flags.initrole = i;
|
|
}
|
|
break;
|
|
case 'r': /* race */
|
|
if (argv[0][2]) {
|
|
if ((i = str2race(&argv[0][2])) >= 0)
|
|
flags.initrace = i;
|
|
} else if (argc > 1) {
|
|
argc--;
|
|
argv++;
|
|
if ((i = str2race(argv[0])) >= 0)
|
|
flags.initrace = i;
|
|
}
|
|
break;
|
|
case 'w': /* windowtype */
|
|
config_error_init(FALSE, "command line", FALSE);
|
|
if (strlen(&argv[0][2]) < (WINTYPELEN - 1))
|
|
Strcpy(gc.chosen_windowtype, &argv[0][2]);
|
|
config_error_done();
|
|
break;
|
|
case '@':
|
|
flags.randomall = 1;
|
|
break;
|
|
default:
|
|
if ((i = str2role(&argv[0][1])) >= 0) {
|
|
flags.initrole = i;
|
|
break;
|
|
} else
|
|
raw_printf("\nUnknown switch: %s", argv[0]);
|
|
FALLTHROUGH;
|
|
case '?':
|
|
nhusage();
|
|
nethack_exit(EXIT_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
nhusage(void)
|
|
{
|
|
char buf1[BUFSZ], buf2[BUFSZ], *bufptr;
|
|
|
|
buf1[0] = '\0';
|
|
bufptr = buf1;
|
|
|
|
#define ADD_USAGE(s) \
|
|
if ((strlen(buf1) + strlen(s)) < (BUFSZ - 1)) \
|
|
Strcat(bufptr, s);
|
|
|
|
/* -role still works for those cases which aren't already taken, but
|
|
* is deprecated and will not be listed here.
|
|
*/
|
|
(void) Sprintf(buf2, "\nUsage:\n%s [-d dir] -s [-r race] [-p profession] "
|
|
"[maxrank] [name]...\n or",
|
|
gh.hname);
|
|
ADD_USAGE(buf2);
|
|
|
|
(void) Sprintf(
|
|
buf2, "\n%s [-d dir] [-u name] [-r race] [-p profession] [-[DX]]",
|
|
gh.hname);
|
|
ADD_USAGE(buf2);
|
|
#ifdef NEWS
|
|
ADD_USAGE(" [-n]");
|
|
#endif
|
|
(void) Sprintf(buf2, "\n or\n%s [--showpaths]",
|
|
gh.hname);
|
|
ADD_USAGE(buf2);
|
|
if (!iflags.window_inited)
|
|
raw_printf("%s\n", buf1);
|
|
else
|
|
(void) printf("%s\n", buf1);
|
|
#undef ADD_USAGE
|
|
}
|
|
|
|
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
|
|
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;
|
|
}
|
|
|
|
static char portable_device_path[MAX_PATH];
|
|
|
|
const char *
|
|
get_portable_device(void)
|
|
{
|
|
return (const char *) portable_device_path;
|
|
}
|
|
|
|
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);
|
|
|
|
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 {
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* copy file if destination does not exist */
|
|
void
|
|
copy_file(const char *dst_folder, const char *dst_name,
|
|
const char *src_folder, const char *src_name,
|
|
boolean copy_even_if_it_exists)
|
|
{
|
|
char dst_path[MAX_PATH];
|
|
strcpy(dst_path, dst_folder);
|
|
strcat(dst_path, dst_name);
|
|
|
|
char src_path[MAX_PATH];
|
|
strcpy(src_path, src_folder);
|
|
strcat(src_path, src_name);
|
|
|
|
if (!file_exists(src_path))
|
|
error("Unable to copy file '%s' as it does not exist", src_path);
|
|
|
|
if (file_exists(dst_path) && !copy_even_if_it_exists)
|
|
return;
|
|
|
|
BOOL success = CopyFileA(src_path, dst_path, !copy_even_if_it_exists);
|
|
if (!success)
|
|
error("Failed to copy '%s' to '%s' (%d)", src_path, dst_path, errno);
|
|
}
|
|
|
|
/* update file copying if it does not exist or src is newer then dst */
|
|
void
|
|
update_file(const char *dst_folder, const char *dst_name,
|
|
const char *src_folder, const char *src_name, BOOL save_copy)
|
|
{
|
|
char dst_path[MAX_PATH];
|
|
strcpy(dst_path, dst_folder);
|
|
strcat(dst_path, dst_name);
|
|
|
|
char src_path[MAX_PATH];
|
|
strcpy(src_path, src_folder);
|
|
strcat(src_path, src_name);
|
|
|
|
char save_path[MAX_PATH];
|
|
strcpy(save_path, dst_folder);
|
|
strcat(save_path, dst_name);
|
|
strcat(save_path, ".save");
|
|
|
|
if (!file_exists(src_path))
|
|
error("Unable to copy file '%s' as it does not exist", src_path);
|
|
|
|
if (!file_newer(src_path, dst_path))
|
|
return;
|
|
|
|
if (file_exists(dst_path) && save_copy)
|
|
CopyFileA(dst_path, save_path, FALSE);
|
|
|
|
BOOL success = CopyFileA(src_path, dst_path, FALSE);
|
|
if (!success)
|
|
error("Failed to update '%s' to '%s'", src_path, dst_path);
|
|
}
|
|
|
|
void
|
|
copy_symbols_content(void)
|
|
{
|
|
char dst_path[MAX_PATH], interim_path[MAX_PATH], orig_path[MAX_PATH];
|
|
|
|
boolean no_template = FALSE;
|
|
|
|
/* Using the SYSCONFPREFIX path, lock it so that it does not change */
|
|
fqn_prefix_locked[SYSCONFPREFIX] = TRUE;
|
|
|
|
strcpy(orig_path, gf.fqn_prefix[DATAPREFIX]);
|
|
strcat(orig_path, SYMBOLS_TEMPLATE);
|
|
strcpy(interim_path, gf.fqn_prefix[SYSCONFPREFIX]);
|
|
strcat(interim_path, SYMBOLS_TEMPLATE);
|
|
strcpy(dst_path, gf.fqn_prefix[SYSCONFPREFIX]);
|
|
strcat(dst_path, SYMBOLS);
|
|
|
|
if (!file_exists(orig_path)) {
|
|
char alt_orig_path[MAX_PATH];
|
|
|
|
strcpy(alt_orig_path, gf.fqn_prefix[DATAPREFIX]);
|
|
strcat(alt_orig_path, SYMBOLS);
|
|
if (file_exists(alt_orig_path)) {
|
|
no_template = TRUE;
|
|
/* <dist>symbols -> <dist>symbols.template */
|
|
copy_file(gf.fqn_prefix[DATAPREFIX], SYMBOLS_TEMPLATE,
|
|
gf.fqn_prefix[DATAPREFIX], SYMBOLS, TRUE);
|
|
}
|
|
}
|
|
if (!file_exists(interim_path) || file_newer(orig_path, interim_path)) {
|
|
/* <dist>symbols.template -> <playground>symbols.template */
|
|
copy_file(gf.fqn_prefix[SYSCONFPREFIX], SYMBOLS_TEMPLATE,
|
|
gf.fqn_prefix[DATAPREFIX], SYMBOLS_TEMPLATE, TRUE);
|
|
}
|
|
if (!file_exists(dst_path) || file_newer(interim_path, dst_path)) {
|
|
/* <playground>symbols.template -> <playground>symbols */
|
|
copy_file(gf.fqn_prefix[SYSCONFPREFIX], SYMBOLS,
|
|
gf.fqn_prefix[SYSCONFPREFIX], SYMBOLS_TEMPLATE, TRUE);
|
|
}
|
|
}
|
|
|
|
void
|
|
copy_sysconf_content(void)
|
|
{
|
|
/* Using the SYSCONFPREFIX path, lock it so that it does not change */
|
|
fqn_prefix_locked[SYSCONFPREFIX] = TRUE;
|
|
|
|
update_file(gf.fqn_prefix[SYSCONFPREFIX], SYSCF_TEMPLATE,
|
|
gf.fqn_prefix[DATAPREFIX], SYSCF_TEMPLATE, FALSE);
|
|
|
|
/* If the required early game file does not exist, copy it */
|
|
copy_file(gf.fqn_prefix[SYSCONFPREFIX], SYSCF_FILE,
|
|
gf.fqn_prefix[DATAPREFIX], SYSCF_TEMPLATE, FALSE);
|
|
}
|
|
|
|
void
|
|
copy_config_content(void)
|
|
{
|
|
/* Using the CONFIGPREFIX path, lock it so that it does not change */
|
|
fqn_prefix_locked[CONFIGPREFIX] = TRUE;
|
|
|
|
/* Keep templates up to date */
|
|
update_file(gf.fqn_prefix[CONFIGPREFIX], CONFIG_TEMPLATE,
|
|
gf.fqn_prefix[DATAPREFIX], CONFIG_TEMPLATE, FALSE);
|
|
|
|
/* If the required early game file does not exist, copy it */
|
|
/* NOTE: We never replace .nethackrc or sysconf */
|
|
copy_file(gf.fqn_prefix[CONFIGPREFIX], CONFIG_FILE,
|
|
gf.fqn_prefix[DATAPREFIX], CONFIG_TEMPLATE, FALSE);
|
|
}
|
|
|
|
void
|
|
copy_hack_content(void)
|
|
{
|
|
nhassert(fqn_prefix_locked[HACKPREFIX]);
|
|
|
|
/* Keep Guidebook and opthelp up to date */
|
|
update_file(gf.fqn_prefix[HACKPREFIX], GUIDEBOOK_FILE,
|
|
gf.fqn_prefix[DATAPREFIX], GUIDEBOOK_FILE, FALSE);
|
|
update_file(gf.fqn_prefix[HACKPREFIX], OPTIONFILE,
|
|
gf.fqn_prefix[DATAPREFIX], OPTIONFILE, FALSE);
|
|
}
|
|
|
|
#ifdef WIN32CON
|
|
void
|
|
safe_routines(void)
|
|
{
|
|
/*
|
|
* Get a set of valid safe windowport function
|
|
* pointers during early startup initialization.
|
|
*/
|
|
if (!WINDOWPORT(safestartup))
|
|
windowprocs = *get_safe_procs(1);
|
|
if (!GUILaunched)
|
|
windowprocs.win_nhgetch = windows_console_custom_nhgetch;
|
|
}
|
|
#endif
|
|
|
|
#ifdef PORT_HELP
|
|
void
|
|
port_help(void)
|
|
{
|
|
/* display port specific help file */
|
|
display_file(PORT_HELP, 1);
|
|
}
|
|
#endif /* PORT_HELP */
|
|
|
|
/* validate wizard mode if player has requested access to it */
|
|
boolean
|
|
authorize_wizard_mode(void)
|
|
{
|
|
if (!strcmp(svp.plname, WIZARD_NAME))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/* similar to above, validate explore mode access */
|
|
boolean
|
|
authorize_explore_mode(void)
|
|
{
|
|
return TRUE; /* no restrictions on explore mode */
|
|
}
|
|
|
|
#define PATH_SEPARATOR '\\'
|
|
|
|
#if defined(WIN32) && !defined(WIN32CON)
|
|
static char exenamebuf[PATHLEN];
|
|
HANDLE hConIn;
|
|
HANDLE hConOut;
|
|
boolean has_fakeconsole;
|
|
|
|
char *
|
|
exename(void)
|
|
{
|
|
int bsize = PATHLEN;
|
|
char *tmp = exenamebuf, *tmp2;
|
|
|
|
#ifdef UNICODE
|
|
{
|
|
TCHAR wbuf[PATHLEN * 4];
|
|
GetModuleFileName((HANDLE) 0, wbuf, PATHLEN * 4);
|
|
WideCharToMultiByte(CP_ACP, 0, wbuf, -1, tmp, bsize, NULL, NULL);
|
|
}
|
|
#else
|
|
*(tmp + GetModuleFileName((HANDLE) 0, tmp, bsize)) = '\0';
|
|
#endif
|
|
tmp2 = strrchr(tmp, PATH_SEPARATOR);
|
|
if (tmp2)
|
|
*tmp2 = '\0';
|
|
tmp2++;
|
|
return tmp2;
|
|
}
|
|
|
|
boolean
|
|
fakeconsole(void)
|
|
{
|
|
if (!hStdOut) {
|
|
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
if (!hStdOut && !hStdIn) {
|
|
/* Bool rval; */
|
|
AllocConsole();
|
|
AttachConsole(GetCurrentProcessId());
|
|
/* rval = SetStdHandle(STD_OUTPUT_HANDLE, hWrite); */
|
|
(void) freopen("CON", "w", stdout);
|
|
(void) freopen("CON", "r", stdin);
|
|
}
|
|
has_fakeconsole = TRUE;
|
|
}
|
|
|
|
/* Obtain handles for the standard Console I/O devices */
|
|
hConIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
#if 0
|
|
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE)) {
|
|
/* Unable to set control handler */
|
|
cmode = 0; /* just to have a statement to break on for debugger */
|
|
}
|
|
#endif
|
|
return has_fakeconsole;
|
|
}
|
|
void freefakeconsole(void)
|
|
{
|
|
if (has_fakeconsole) {
|
|
FreeConsole();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static boolean path_buffer_set = FALSE;
|
|
static char path_buffer[MAX_PATH];
|
|
|
|
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 * seperator = strrchr(path_buffer, PATH_SEPARATOR);
|
|
if (seperator)
|
|
*seperator = '\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;
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
windows_raw_print(const char *str)
|
|
{
|
|
if (str)
|
|
fprintf(stdout, "%s\n", str);
|
|
windows_nhgetch();
|
|
return;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
windows_raw_print_bold(const char *str)
|
|
{
|
|
windows_raw_print(str);
|
|
return;
|
|
}
|
|
|
|
int
|
|
windows_nhgetch(void)
|
|
{
|
|
return getchar();
|
|
}
|
|
|
|
|
|
void
|
|
windows_nhbell(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
int
|
|
windows_nh_poskey(int *x UNUSED, int *y UNUSED, int *mod UNUSED)
|
|
{
|
|
return '\033';
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
char
|
|
windows_yn_function(const char *query UNUSED, const char *resp UNUSED,
|
|
char def UNUSED)
|
|
{
|
|
return '\033';
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
windows_getlin(const char *prompt UNUSED, char *outbuf)
|
|
{
|
|
Strcpy(outbuf, "\033");
|
|
}
|
|
|
|
#ifdef PC_LOCKING
|
|
static int
|
|
eraseoldlocks(void)
|
|
{
|
|
int i;
|
|
|
|
/* cannot use maxledgerno() here, because we need to find a lock name
|
|
* before starting everything (including the dungeon initialization
|
|
* that sets astral_level, needed for maxledgerno()) up
|
|
*/
|
|
for (i = 1; i <= MAXDUNGEON * MAXLEVEL + 1; i++) {
|
|
/* try to remove all */
|
|
set_levelfile_name(gl.lock, i);
|
|
(void) unlink(fqname(gl.lock, LEVELPREFIX, 0));
|
|
}
|
|
set_levelfile_name(gl.lock, 0);
|
|
#ifdef HOLD_LOCKFILE_OPEN
|
|
really_close();
|
|
#endif
|
|
if (unlink(fqname(gl.lock, LEVELPREFIX, 0)))
|
|
return 0; /* cannot remove it */
|
|
return (1); /* success! */
|
|
}
|
|
|
|
DISABLE_WARNING_UNREACHABLE_CODE
|
|
|
|
int
|
|
getlock(void)
|
|
{
|
|
int fd, ern = 0, prompt_result = 1;
|
|
int fcmask = FCMASK;
|
|
#ifndef SELF_RECOVER
|
|
char tbuf[BUFSZ];
|
|
#endif
|
|
const char *fq_lock;
|
|
#define OOPS_BUFSZ 512
|
|
char oops[OOPS_BUFSZ];
|
|
#ifdef WIN32CON
|
|
boolean istty = WINDOWPORT(tty);
|
|
#endif
|
|
|
|
/* we ignore QUIT and INT at this point */
|
|
if (!lock_file(HLOCK, LOCKPREFIX, 10)) {
|
|
wait_synch();
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
error("Quitting.");
|
|
}
|
|
|
|
/* regularize(lock); */ /* already done in pcmain */
|
|
/*Sprintf(tbuf, "%s", fqname(gl.lock, LEVELPREFIX, 0)); */
|
|
set_levelfile_name(gl.lock, 0);
|
|
fq_lock = fqname(gl.lock, LEVELPREFIX, 1);
|
|
if ((fd = open(fq_lock, 0)) == -1) {
|
|
if (errno == ENOENT)
|
|
goto gotlock; /* no such file */
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
error("Bad directory or name: %s\n%s\n", fq_lock,
|
|
strerror(errno));
|
|
unlock_file(HLOCK);
|
|
Sprintf(oops, "Cannot open %s", fq_lock);
|
|
raw_print(oops);
|
|
nethack_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
(void) nhclose(fd);
|
|
|
|
#ifdef WIN32CON
|
|
if (WINDOWPORT(tty))
|
|
prompt_result = tty_self_recover_prompt();
|
|
else
|
|
#endif
|
|
prompt_result = other_self_recover_prompt();
|
|
/*
|
|
* prompt_result == 1 means recover old game.
|
|
* prompt_result == -1 means willfully destroy the old game.
|
|
* prompt_result == 0 should just exit.
|
|
*/
|
|
Sprintf(oops, "You chose to %s.",
|
|
(prompt_result == -1)
|
|
? "destroy the old game and start a new one"
|
|
: (prompt_result == 1)
|
|
? "recover the old game"
|
|
: "not start a new game");
|
|
#ifdef WIN32CON
|
|
if (istty)
|
|
term_clear_screen();
|
|
#endif
|
|
raw_printf("%s", oops);
|
|
if (prompt_result == 1) { /* recover */
|
|
if (recover_savefile()) {
|
|
#if 0
|
|
if (istty)
|
|
term_clear_screen(); /* display gets fouled up otherwise */
|
|
#endif
|
|
goto gotlock;
|
|
} else {
|
|
unlock_file(HLOCK);
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
raw_print("Couldn't recover the old game.");
|
|
}
|
|
} else if (prompt_result < 0) { /* destroy old game */
|
|
if (eraseoldlocks()) {
|
|
#ifdef WIN32CON
|
|
if (istty)
|
|
term_clear_screen(); /* display gets fouled up otherwise */
|
|
#endif
|
|
goto gotlock;
|
|
} else {
|
|
unlock_file(HLOCK);
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
raw_print("Couldn't destroy the old game.");
|
|
return 0;
|
|
}
|
|
} else {
|
|
unlock_file(HLOCK);
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
gotlock:
|
|
fd = creat(fq_lock, fcmask);
|
|
if (fd == -1)
|
|
ern = errno;
|
|
unlock_file(HLOCK);
|
|
if (fd == -1) {
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
Sprintf(oops, "cannot creat file (%s.)\n%s\n%s\"%s\" exists?\n", fq_lock,
|
|
strerror(ern), " Are you sure that the directory",
|
|
gf.fqn_prefix[LEVELPREFIX]);
|
|
raw_print(oops);
|
|
} else {
|
|
if (write(fd, (char *) &svh.hackpid, sizeof(svh.hackpid))
|
|
!= sizeof(svh.hackpid)) {
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
error("cannot write lock (%s)", fq_lock);
|
|
}
|
|
if (nhclose(fd) == -1) {
|
|
#if defined(CHDIR) && !defined(NOCWD_ASSUMPTIONS)
|
|
chdirx(orgdir, 0);
|
|
#endif
|
|
error("cannot close lock (%s)", fq_lock);
|
|
}
|
|
}
|
|
return prompt_result;
|
|
}
|
|
#endif /* PC_LOCKING */
|
|
|
|
boolean
|
|
file_exists(const char *path)
|
|
{
|
|
struct stat sb;
|
|
|
|
/* Just see if it's there */
|
|
if (stat(path, &sb)) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
RESTORE_WARNING_UNREACHABLE_CODE
|
|
|
|
/*
|
|
file_newer returns TRUE if the file at a_path is newer then the file
|
|
at b_path. If a_path does not exist, it returns FALSE. If b_path
|
|
does not exist, it returns TRUE.
|
|
*/
|
|
boolean
|
|
file_newer(const char *a_path, const char *b_path)
|
|
{
|
|
struct stat a_sb = { 0 };
|
|
struct stat b_sb = { 0 };
|
|
double timediff;
|
|
|
|
if (stat(a_path, &a_sb))
|
|
return FALSE;
|
|
|
|
if (stat(b_path, &b_sb))
|
|
return TRUE;
|
|
timediff = difftime(a_sb.st_mtime, b_sb.st_mtime);
|
|
|
|
if(timediff > 0)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef WIN32CON
|
|
/*
|
|
* returns:
|
|
* 1 if game should be recovered
|
|
* -1 if old game should be destroyed, allowing new game to proceed.
|
|
*/
|
|
int
|
|
tty_self_recover_prompt(void)
|
|
{
|
|
int c, ci, ct, pl, retval = 0;
|
|
/* for saving/replacing functions, if needed */
|
|
struct window_procs saved_procs = {0};
|
|
|
|
pl = 1;
|
|
c = 'n';
|
|
ct = 0;
|
|
saved_procs = windowprocs;
|
|
if (!WINDOWPORT(safestartup))
|
|
windowprocs = *get_safe_procs(2); /* arg 2 uses no-newline variant */
|
|
windowprocs.win_nhgetch = windows_console_custom_nhgetch;
|
|
raw_print("\n");
|
|
raw_print("\n");
|
|
raw_print("\n");
|
|
raw_print("\n");
|
|
raw_print("\n");
|
|
raw_print("There are files from a game in progress under your name. ");
|
|
raw_print("Recover? [yn] ");
|
|
|
|
tty_ask_again:
|
|
|
|
while ((ci = nhgetch()) && !(ci == '\n' || ci == 13)) {
|
|
if (ct > 0) {
|
|
/* invalid answer */
|
|
raw_print("\b \b");
|
|
ct = 0;
|
|
c = 'n';
|
|
}
|
|
if (ci == 'y' || ci == 'n' || ci == 'Y' || ci == 'N') {
|
|
ct = 1;
|
|
c = ci;
|
|
#ifdef _MSC_VER
|
|
_putch(ci);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (pl == 1 && (c == 'n' || c == 'N')) {
|
|
/* no to recover */
|
|
raw_print("\n\nAre you sure you wish to destroy the old game rather than try to\n");
|
|
raw_print("recover it? [yn] ");
|
|
c = 'n';
|
|
ct = 0;
|
|
pl = 2;
|
|
goto tty_ask_again;
|
|
}
|
|
|
|
if (pl == 2 && (c == 'n' || c == 'N')) {
|
|
/* no to destruction of old game */
|
|
retval = 0;
|
|
} else {
|
|
/* only yes answers get here */
|
|
if (pl == 2)
|
|
retval = -1; /* yes, do destroy the old game anyway */
|
|
else
|
|
retval = 1; /* yes, do recover the old game */
|
|
}
|
|
if (saved_procs.name[0]) {
|
|
windowprocs = saved_procs;
|
|
raw_clear_screen();
|
|
}
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
other_self_recover_prompt(void)
|
|
{
|
|
int c, ci, ct, pl, retval = 0;
|
|
boolean ismswin = WINDOWPORT(mswin),
|
|
iscurses = WINDOWPORT(curses);
|
|
|
|
pl = 1;
|
|
c = 'n';
|
|
ct = 0;
|
|
if (iflags.window_inited || WINDOWPORT(curses)) {
|
|
c = y_n("There are files from a game in progress under your name. "
|
|
"Recover?");
|
|
} else {
|
|
c = 'n';
|
|
ct = 0;
|
|
raw_print("There are files from a game in progress under your name. "
|
|
"Recover? [yn]");
|
|
}
|
|
|
|
other_ask_again:
|
|
|
|
if (!ismswin && !iscurses) {
|
|
while ((ci = nhgetch()) && !(ci == '\n' || ci == 13)) {
|
|
if (ct > 0) {
|
|
/* invalid answer */
|
|
raw_print("\b \b");
|
|
ct = 0;
|
|
c = 'n';
|
|
}
|
|
if (ci == 'y' || ci == 'n' || ci == 'Y' || ci == 'N') {
|
|
ct = 1;
|
|
c = ci;
|
|
}
|
|
}
|
|
}
|
|
if (pl == 1 && (c == 'n' || c == 'N')) {
|
|
/* no to recover */
|
|
c = y_n("Are you sure you wish to destroy the old game, rather than try to "
|
|
"recover it? [yn] ");
|
|
pl = 2;
|
|
if (!ismswin && !iscurses) {
|
|
c = 'n';
|
|
ct = 0;
|
|
goto other_ask_again;
|
|
}
|
|
}
|
|
if (pl == 2 && (c == 'n' || c == 'N')) {
|
|
/* no to destruction of old game */
|
|
retval = 0;
|
|
} else {
|
|
/* only yes answers get here */
|
|
if (pl == 2)
|
|
retval = -1; /* yes, do destroy the old game anyway */
|
|
else
|
|
retval = 1; /* yes, do recover the old game */
|
|
}
|
|
return retval;
|
|
}
|
|
/*windmain.c*/
|