void replay(void) { char buf[BUFSZ]; fnchar logdir[BUFSZ], savedir[BUFSZ], filename[1024], *dir, **files; struct nh_menuitem *items; int i, n, fd, icount, size, filecount, pick[1]; enum nh_log_status status; struct nh_game_info gi; if (!get_gamedir(LOG_DIR, logdir)) logdir[0] = '\0'; if (!get_gamedir(SAVE_DIR, savedir)) savedir[0] = '\0'; if (*logdir) dir = logdir; else if (*savedir) dir = savedir; else { curses_msgwin("There are no games to replay."); return; } while (1) { filename[0] = '\0'; files = list_gamefiles(dir, &filecount); /* make sure there are some files to show */ if (!filecount) { if (dir == savedir) { curses_msgwin("There are no saved games to replay."); savedir[0] = '\0'; } else { curses_msgwin("There are no completed games to replay."); logdir[0] = '\0'; } dir = (dir == savedir) ? logdir : savedir; if (!*dir) return; continue; } icount = 0; size = filecount + 2; items = malloc(size * sizeof (struct nh_menuitem)); /* add all the files to the menu */ for (i = 0; i < filecount; i++) { fd = sys_open(files[i], O_RDWR, 0660); status = nh_get_savegame_status(fd, &gi); close(fd); describe_game(buf, status, &gi); add_menu_item(items, size, icount, (status == LS_IN_PROGRESS) ? 0 : icount + 1, buf, 0, FALSE); } if (dir == logdir && *savedir) { add_menu_txt(items, size, icount, "", MI_NORMAL); add_menu_item(items, size, icount, -1, "View saved games instead", '!', FALSE); } else if (dir == savedir && *logdir) { add_menu_txt(items, size, icount, "", MI_NORMAL); add_menu_item(items, size, icount, -1, "View saved games instead", '!', FALSE); } n = curses_display_menu(items, icount, "Pick a game to view", PICK_ONE, PLHINT_ANYWHERE, pick); free(items); filename[0] = '\0'; if (n > 0 && pick[0] != -1) fnncat(filename, files[pick[0] - 1], sizeof (filename) / sizeof (fnchar) - 1); for (i = 0; i < filecount; i++) free(files[i]); free(files); if (n <= 0) return; if (pick[0] == -1) { dir = (dir == savedir) ? logdir : savedir; continue; } /* we have a valid filename */ break; } fd = sys_open(filename, O_RDWR, 0660); replay_commandloop(fd); close(fd); }
nh_bool loadgame(void) { char buf[BUFSZ]; fnchar savedir[BUFSZ], filename[1024], **files; struct nh_menuitem *items; int size, icount, fd, i, n, ret, pick[1]; enum nh_log_status status; struct nh_game_info gi; if (!get_gamedir(SAVE_DIR, savedir)) { curses_raw_print("Could not find or create the save directory."); return FALSE; } files = list_gamefiles(savedir, &size); if (!size) { curses_msgwin("No saved games found."); return FALSE; } icount = 0; items = malloc(size * sizeof(struct nh_menuitem)); for (i = 0; i < size; i++) { fd = sys_open(files[i], O_RDWR, FILE_OPEN_MASK); status = nh_get_savegame_status(fd, &gi); close(fd); describe_game(buf, status, &gi); add_menu_item(items, size, icount, (status == LS_IN_PROGRESS) ? 0 : icount + 1, buf, 0, FALSE); } n = curses_display_menu(items, icount, "saved games", PICK_ONE, pick); free(items); filename[0] = '\0'; if (n > 0) fnncat(filename, files[pick[0]-1], sizeof(filename)/sizeof(fnchar)-1); for (i = 0; i < icount; i++) free(files[i]); free(files); if (n <= 0) return FALSE; fd = sys_open(filename, O_RDWR, FILE_OPEN_MASK); create_game_windows(); if (nh_restore_game(fd, NULL, FALSE) != GAME_RESTORED) { destroy_game_windows(); close(fd); if (curses_yn_function("Failed to load the save. Do you wish to delete the file?", "yn", 'n') == 'y') unlink(filename); return FALSE; } load_keymap(); /* need to load the keymap after the game has been started */ ret = commandloop(); free_keymap(); close(fd); destroy_game_windows(); cleanup_messages(); game_ended(ret, filename); return TRUE; }
static void ccmd_list_games(json_t * params) { char filename[1024]; int completed, limit, show_all, count, i, fd; struct gamefile_info *files; enum nh_log_status status; struct nh_game_info gi; json_t *jarr, *jobj; if (json_unpack (params, "{si,si*}", "completed", &completed, "limit", &limit) == -1) exit_client("Bad parameters for list_games"); if (json_unpack(params, "{si*}", "show_all", &show_all) == -1) show_all = 0; /* step 1: get a list of files from the db. */ files = db_list_games(completed, show_all ? 0 : user_info.uid, limit, &count); jarr = json_array(); /* step 2: get extra info for each file. */ for (i = 0; i < count; i++) { if (completed) snprintf(filename, 1024, "%s/completed/%s", settings.workdir, files[i].filename); else snprintf(filename, 1024, "%s/save/%s/%s", settings.workdir, user_info.username, files[i].filename); fd = open(filename, O_RDWR); if (fd == -1) { log_msg("Game file %s could not be opened in ccmd_list_games.", files[i].filename); continue; } status = nh_get_savegame_status(fd, &gi); jobj = json_pack("{si,si,si,ss,ss,ss,ss,ss}", "gameid", files[i].gid, "status", status, "playmode", gi.playmode, "plname", gi.name, "plrole", gi.plrole, "plrace", gi.plrace, "plgend", gi.plgend, "plalign", gi.plalign); if (status == LS_SAVED) { json_object_set_new(jobj, "level_desc", json_string(gi.level_desc)); json_object_set_new(jobj, "moves", json_integer(gi.moves)); json_object_set_new(jobj, "depth", json_integer(gi.depth)); json_object_set_new(jobj, "has_amulet", json_integer(gi.has_amulet)); } else if (status == LS_DONE) { json_object_set_new(jobj, "death", json_string(gi.death)); json_object_set_new(jobj, "moves", json_integer(gi.moves)); json_object_set_new(jobj, "depth", json_integer(gi.depth)); } json_array_append_new(jarr, jobj); free((void *)files[i].username); free((void *)files[i].filename); close(fd); } free(files); client_msg("list_games", json_pack("{so}", "games", jarr)); }
enum nh_restore_status nh_restore_game(int fd, struct nh_window_procs *rwinprocs, boolean force_replay) { int playmode, irole, irace, igend, ialign; char namebuf[PL_NSIZ]; /* some compilers can't cope with the fact that all subsequent stores to error * are not dead, but become important if the error handler longjumps back * volatile is required to prevent invalid optimization based on that wrong * assumption. */ volatile enum nh_restore_status error = GAME_RESTORED; if (fd == -1) return ERR_BAD_ARGS; switch (nh_get_savegame_status(fd, NULL)) { case LS_INVALID: return ERR_BAD_FILE; case LS_DONE: return ERR_GAME_OVER; case LS_CRASHED: force_replay = TRUE; break; case LS_IN_PROGRESS: return ERR_IN_PROGRESS; case LS_SAVED: break; /* default, everything is A-OK */ } if (!api_entry_checkpoint()) goto error_out; error = ERR_BAD_FILE; replay_set_logfile(fd); /* store the fd and try to get a lock or exit */ replay_begin(); program_state.restoring = TRUE; iflags.disable_log = TRUE; /* don't log any of the commands, they're already in the log */ /* Read the log header for this game. */ replay_read_newgame(&turntime, &playmode, namebuf, &irole, &irace, &igend, &ialign); /* set special windowprocs which will autofill requests for user input * with data from the log file */ replay_setup_windowprocs(rwinprocs); startup_common(namebuf, playmode); u.initrole = irole; u.initrace = irace; u.initgend = igend; u.initalign = ialign; if (!force_replay) { error = ERR_RESTORE_FAILED; replay_run_cmdloop(TRUE, FALSE, TRUE); replay_jump_to_endpos(); if (!dorecover_fd(fd)) { replay_undo_jump_to_endpos(); goto error_out2; } replay_undo_jump_to_endpos(); wd_message(); program_state.game_running = 1; post_init_tasks(); } else { replay_run_cmdloop(TRUE, TRUE, FALSE); /* option setup only */ newgame(); /* try replaying instead */ error = ERR_REPLAY_FAILED; replay_run_cmdloop(FALSE, FALSE, TRUE); replay_sync_save(); } /* restore standard window procs */ replay_restore_windowprocs(); program_state.restoring = FALSE; iflags.disable_log = FALSE; /* clean up data used for replay */ replay_end(); log_truncate(); log_init(); /* must be called before we start writing to the log */ /* info might not have reached the ui while alternate window procs were set */ doredraw(); /* nh_start_game() does this via newgame(), but since this function doesn't * call newgame(), we have to do it here instead. */ notify_levelchange(NULL); bot(); flush_screen(); was_on_elbereth = !sengr_at("Elbereth", u.ux, u.uy); /* force botl update later */ welcome(FALSE); realtime_messages(TRUE, TRUE); update_inventory(); api_exit(); return GAME_RESTORED; error_out2: api_exit(); error_out: replay_restore_windowprocs(); program_state.restoring = FALSE; iflags.disable_log = FALSE; replay_end(); unlock_fd(fd); if (error == ERR_RESTORE_FAILED) { raw_printf("Restore failed. Attempting to replay instead.\n"); error = nh_restore_game(fd, rwinprocs, TRUE); } return error; }