/* File: save.c */ /* Purpose: save the current game into a savefile */ /* * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke * * This software may be copied and distributed for educational, research, and * not for profit purposes provided that this copyright and statement are * included in all such copies. */ #include "angband.h" #ifdef FUTURE_SAVEFILES /* * XXX XXX XXX Ignore this for now... * * The basic format of Angband 2.8.0 (and later) savefiles is simple. * * The savefile itself is a "header" (4 bytes) plus a series of "blocks". * * The "header" contains information about the "version" of the savefile. * Conveniently, pre-2.8.0 savefiles also use a 4 byte header, though the * interpretation of the "sf_extra" byte is very different. Unfortunately, * savefiles from Angband 2.5.X reverse the sf_major and sf_minor fields, * and must be handled specially, until we decide to start ignoring them. * * Each "block" is a "type" (2 bytes), plus a "size" (2 bytes), plus "data", * plus a "check" (2 bytes), plus a "stamp" (2 bytes). The function of the * "check" and "stamp" bytes is still being contemplated. * * Standard types: * TYPE_CLOSE --> checksum data, savefile info, etc * TYPE_OPTIONS --> option settings * TYPE_MESSAGES --> message recall * TYPE_PLAYER --> player information * TYPE_SPELLS --> spell information * TYPE_INVEN --> player inven/equip * TYPE_STORES --> store information * TYPE_RACES --> monster race data * TYPE_KINDS --> object kind data * TYPE_ARTIFACTS --> artifact info * TYPE_UNIQUES --> unique info * TYPE_QUESTS --> quest info * * Dungeon information: * TYPE_DUNGEON --> dungeon flags * TYPE_FEATURES --> dungeon features * TYPE_OBJECTS --> dungeon objects * TYPE_MONSTERS --> dungeon monsters * * Conversions: * Break old "races" into normals/uniques * Extract info about the "unique" monsters * * Question: * Should there be a single "block" for info about all the * stores, or one "block" for each store? Likewise with all * the other pieces of information. And should we dump all * of the pieces, or just the ones that are interesting? */ #define TYPE_OPTIONS 17362 /* * Hack -- current savefile */ static int data_fd = -1; /* * Hack -- current block type */ static u16b data_type; /* * Hack -- current block size */ static u16b data_size; /* * Hack -- pointer to the data buffer */ static byte *data_head; /* * Hack -- pointer into the data buffer */ static byte *data_next; /* * Hack -- write the current "block" to the savefile */ static errr wr_block(void) { errr err; byte fake[4]; /* Save the type and size */ fake[0] = (byte)(data_type); fake[1] = (byte)(data_type >> 8); fake[2] = (byte)(data_size); fake[3] = (byte)(data_size >> 8); /* Dump the head */ err = fd_write(data_fd, (char*)&fake, 4); /* Dump the actual data */ err = fd_write(data_fd, (char*)data_head, data_size); /* XXX XXX XXX */ fake[0] = 0; fake[1] = 0; fake[2] = 0; fake[3] = 0; /* Dump the tail */ err = fd_write(data_fd, (char*)&fake, 4); /* Hack -- reset */ data_next = data_head; /* Success */ return (0); } /* * Hack -- add data to the current block */ static void put_byte(byte v) { *data_next++ = v; } /* * Hack -- add data to the current block */ static void put_char(char v) { put_byte((byte)(v)); } /* * Hack -- add data to the current block */ static void put_u16b(u16b v) { *data_next++ = (byte)(v); *data_next++ = (byte)(v >> 8); } /* * Hack -- add data to the current block */ static void put_s16b(s16b v) { put_u16b((u16b)(v)); } /* * Hack -- add data to the current block */ static void put_u32b(u32b v) { *data_next++ = (byte)(v); *data_next++ = (byte)(v >> 8); *data_next++ = (byte)(v >> 16); *data_next++ = (byte)(v >> 24); } /* * Hack -- add data to the current block */ static void put_s32b(s32b v) { put_u32b((u32b)(v)); } /* * Hack -- add data to the current block */ static void put_string(char *str) { while ((*data_next++ = *str++) != '\0') ; } /* * Hack -- put the "options" (flags and masks) * * Alternative method -- dump actual option "names" and settings */ static void put_options() { int i; u16b c; u32b flag[8]; u32b mask[8]; /*** Normal options ***/ /* Clear the option flags */ for (i = 0; i < 8; i++) flag[i] = 0L; /* Clear the option masks */ for (i = 0; i < 8; i++) mask[i] = 0L; /* Analyze the options */ for (i = 0; options[i].o_desc; i++) { int os = options[i].o_set; int ob = options[i].o_bit; /* Extract a variable setting, if possible */ if (options[i].o_var && os) { /* Mark the mask */ mask[os-1] |= (1L << ob); /* Mark the flag if needed */ if (*options[i].o_var) flag[os-1] |= (1L << ob); } } /* Put the flags */ for (i = 0; i < 8; i++) put_u32b(flag[i]); /* Put the masks */ for (i = 0; i < 8; i++) put_u32b(mask[i]); /*** Special Options ***/ /* Write "delay_spd" */ put_byte(delay_spd); /* Write "hitpoint_warn" */ put_byte(hitpoint_warn); /*** Cheating options ***/ c = 0; if (wizard) c |= 0x0002; if (cheat_peek) c |= 0x0100; if (cheat_hear) c |= 0x0200; if (cheat_room) c |= 0x0400; if (cheat_xtra) c |= 0x0800; if (cheat_know) c |= 0x1000; if (cheat_live) c |= 0x2000; put_u16b(c); } /* * Write a savefile for Angband 2.8.0 */ static errr wr_savefile() { int i; u32b now; byte tmp8u; u16b tmp16u; errr err; byte fake[4]; /*** Hack -- extract some data ***/ /* Hack -- Acquire the current time */ now = time((time_t*)(NULL)); /* Note the operating system */ sf_xtra = 0L; /* Note when the file was saved */ sf_when = now; /* Note the number of saves */ sf_saves++; /*** Actually write the file ***/ /* Open the file XXX XXX XXX */ data_fd = -1; /* Dump the version */ fake[0] = (byte)(VERSION_MAJOR); fake[1] = (byte)(VERSION_MINOR); fake[2] = (byte)(VERSION_PATCH); fake[3] = (byte)(VERSION_TYPE); /* Dump the data */ err = fd_write(data_fd, (char*)&fake, 4); /* Make array XXX XXX XXX */ C_MAKE(data_head, 65535, byte); /* Hack -- reset */ data_next = data_head; #if 0 /* Operating system */ wr_u32b(sf_xtra); /* Time file last saved */ wr_u32b(sf_when); /* Number of past lives */ wr_u16b(sf_lives); /* Number of times saved */ wr_u16b(sf_saves); /* XXX XXX XXX */ /* Set the type */ data_type = TYPE_SAVEFILE; /* Set the "options" size */ data_size = (data_next - data_head); /* Write the block */ wr_block(); #endif /* Dump the "options" */ put_options(); /* Set the type */ data_type = TYPE_OPTIONS; /* Set the "options" size */ data_size = (data_next - data_head); /* Write the block */ wr_block(); /* XXX XXX XXX */ #if 0 /* Dump the "messages" */ /* Dump the number of "messages" */ tmp16u = message_num(); if (compress_savefile && (tmp16u > 40)) tmp16u = 40; wr_u16b(tmp16u); /* Dump the messages (oldest first!) */ for (i = tmp16u - 1; i >= 0; i--) { wr_string(message_str(i)); } /* Dump the monster lore */ tmp16u = MAX_R_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) wr_lore(i); /* Dump the object memory */ tmp16u = MAX_K_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) wr_xtra(i); /* Hack -- Dump the quests */ tmp16u = MAX_Q_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) { wr_byte(q_list[i].level); wr_byte(0); wr_byte(0); wr_byte(0); } /* Hack -- Dump the artifacts */ tmp16u = MAX_A_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) { artifact_type *a_ptr = &a_info[i]; wr_byte(a_ptr->cur_num); wr_byte(0); wr_byte(0); wr_byte(0); } /* Write the "extra" information */ wr_extra(); /* Dump the "player hp" entries */ tmp16u = PY_MAX_LEVEL; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) { wr_s16b(player_hp[i]); } /* Write spell data */ for (i = 0; i < 64; i++) { wr_byte(spell_learned[i]); wr_byte(spell_worked[i]); wr_byte(spell_forgotten[i]); } /* Dump the ordered spells */ for (i = 0; i < 64; i++) { wr_byte(spell_order[i]); } /* Write the inventory */ for (i = 0; i < INVEN_TOTAL; i++) { if (inventory[i].k_idx) { wr_u16b(i); wr_item(&inventory[i]); } } /* Add a sentinel */ wr_u16b(0xFFFF); /* Note the stores */ tmp16u = MAX_STORES; wr_u16b(tmp16u); /* Dump the stores */ for (i = 0; i < tmp16u; i++) wr_store(&store[i]); /* Player is not dead, write the dungeon */ if (!death) { /* Dump the dungeon */ wr_dungeon(); /* Dump the ghost */ wr_ghost(); } #endif /* Dump the "final" marker XXX XXX XXX */ /* Type zero, Size zero, Contents zero, etc */ /* XXX XXX XXX Check for errors */ /* Kill array XXX XXX XXX */ C_KILL(data_head, 65535, byte); /* Success */ return (0); } /* * Hack -- read the next "block" from the savefile */ static errr rd_block(void) { errr err; byte fake[4]; /* Read the head data */ err = fd_read(data_fd, (char*)&fake, 4); /* Extract the type and size */ data_type = (fake[0] | ((u16b)fake[1] << 8)); data_size = (fake[2] | ((u16b)fake[3] << 8)); /* Read the actual data */ err = fd_read(data_fd, (char*)data_head, data_size); /* Read the tail data */ err = fd_read(data_fd, (char*)&fake, 4); /* XXX XXX XXX */ /* Hack -- reset */ data_next = data_head; /* Success */ return (0); } /* * Hack -- get data from the current block */ static void get_byte(byte *ip) { byte d1; d1 = (*data_next++); (*ip) = (d1); } /* * Hack -- get data from the current block */ static void get_char(char *ip) { get_byte((byte*)ip); } /* * Hack -- get data from the current block */ static void get_u16b(u16b *ip) { u16b d0, d1; d0 = (*data_next++); d1 = (*data_next++); (*ip) = (d0 | (d1 << 8)); } /* * Hack -- get data from the current block */ static void get_s16b(s16b *ip) { get_u16b((u16b*)ip); } /* * Hack -- get data from the current block */ static void get_u32b(u32b *ip) { u32b d0, d1, d2, d3; d0 = (*data_next++); d1 = (*data_next++); d2 = (*data_next++); d3 = (*data_next++); (*ip) = (d0 | (d1 << 8) | (d2 << 16) | (d3 << 24)); } /* * Hack -- get data from the current block */ static void get_s32b(s32b *ip) { get_u32b((u32b*)ip); } /* * Read a savefile for Angband 2.8.0 */ static errr rd_savefile() { bool done = FALSE; byte fake[4]; /* Open the savefile */ data_fd = fd_open(savefile, O_RDONLY); /* No file */ if (data_fd < 0) return (1); /* Strip the first four bytes */ if (fd_read(data_fd, (char*)(fake), 4)) return (1); /* Make array XXX XXX XXX */ C_MAKE(data_head, 65535, byte); /* Hack -- reset */ data_next = data_head; /* Read blocks */ while (!done) { /* Hack -- reset */ data_next = data_head; /* Read the block */ rd_block(); /* Analyze the type */ switch (data_type) { /* Done XXX XXX XXX */ case 0: done = TRUE; #if 0 /* Grab the options */ case TYPE_OPTIONS: if (get_options()) err = -1; break; #endif } /* XXX XXX XXX verify "data_next" */ if (data_next - data_head > data_size) return (1); } /* XXX XXX XXX Check for errors */ /* Kill array XXX XXX XXX */ C_KILL(data_head, 65535, byte); /* Success */ return (0); } #endif /* FUTURE_SAVEFILES */ /* * Some "local" parameters, used to help write savefiles */ static FILE *fff; /* Current save "file" */ static byte xor_byte; /* Simple encryption */ static u32b v_stamp = 0L; /* A simple "checksum" on the actual values */ static u32b x_stamp = 0L; /* A simple "checksum" on the encoded bytes */ /* * These functions place information into a savefile a byte at a time */ static void sf_put(byte v) { /* Encode the value, write a character */ xor_byte ^= v; (void)putc((int)xor_byte, fff); /* Maintain the checksum info */ v_stamp += v; x_stamp += xor_byte; } static void wr_byte(byte v) { sf_put(v); } /*static void wr_char(char v) { wr_byte((byte)v); }*/ static void wr_u16b(u16b v) { sf_put(v & 0xFF); sf_put((v >> 8) & 0xFF); } static void wr_s16b(s16b v) { wr_u16b((u16b)v); } static void wr_u32b(u32b v) { sf_put(v & 0xFF); sf_put((v >> 8) & 0xFF); sf_put((v >> 16) & 0xFF); sf_put((v >> 24) & 0xFF); } static void wr_s32b(s32b v) { wr_u32b((u32b)v); } static void wr_string(cptr str) { while (*str) { wr_byte(*str); str++; } wr_byte(*str); } /* * These functions write info in larger logical records */ /* * Write an "item" record */ static void wr_item(object_type *i_ptr) { wr_s16b(i_ptr->k_idx); wr_byte(i_ptr->iy); wr_byte(i_ptr->ix); wr_s16b(i_ptr->pval); wr_byte(i_ptr->discount); wr_byte(i_ptr->number); wr_byte(i_ptr->name1); wr_byte(i_ptr->name2); wr_s16b(i_ptr->timeout); wr_s16b(i_ptr->to_h); wr_s16b(i_ptr->to_d); wr_s16b(i_ptr->to_a); wr_s16b(i_ptr->ac); wr_byte(i_ptr->dd); wr_byte(i_ptr->ds); wr_byte(i_ptr->ident); wr_byte(i_ptr->marked); wr_u32b(0L); wr_u32b(0L); wr_u32b(0L); wr_u16b(0); wr_byte(i_ptr->xtra1); wr_byte(i_ptr->xtra2); /* Save the inscription (if any) */ if (i_ptr->note) { wr_string(quark_str(i_ptr->note)); } else { wr_string(""); } wr_s16b(i_ptr->next_i_idx); } /* * Write a "monster" record */ static void wr_monster(monster_type *m_ptr) { wr_s16b(m_ptr->r_idx); wr_byte(m_ptr->fy); wr_byte(m_ptr->fx); wr_s16b(m_ptr->hp); wr_s16b(m_ptr->maxhp); wr_s16b(m_ptr->csleep); wr_byte(m_ptr->mspeed); wr_byte(m_ptr->energy); wr_byte(m_ptr->stunned); wr_byte(m_ptr->confused); wr_byte(m_ptr->monfear); wr_byte(m_ptr->temp_speed); wr_byte(m_ptr->temp_slow); wr_byte(m_ptr->spawned); wr_byte(0); } /* * Write a "lore" record */ static void wr_lore(int r_idx) { monster_race *r_ptr = &r_info[r_idx]; /* Count sights/deaths/kills */ wr_s16b(r_ptr->r_sights); wr_s16b(r_ptr->r_deaths); wr_s16b(r_ptr->r_pkills); wr_s16b(r_ptr->r_tkills); /* Count wakes and ignores */ wr_byte(r_ptr->r_wake); wr_byte(r_ptr->r_ignore); /* Extra stuff */ wr_byte(r_ptr->r_xtra1); wr_byte(r_ptr->r_xtra2); /* Count drops */ wr_byte(r_ptr->r_drop_gold); wr_byte(r_ptr->r_drop_item); /* Count spells */ wr_byte(r_ptr->r_cast_inate); wr_byte(r_ptr->r_cast_spell); /* Count blows of each type */ wr_byte(r_ptr->r_blows[0]); wr_byte(r_ptr->r_blows[1]); wr_byte(r_ptr->r_blows[2]); wr_byte(r_ptr->r_blows[3]); /* Memorize flags */ wr_u32b(r_ptr->r_flags1); wr_u32b(r_ptr->r_flags2); wr_u32b(r_ptr->r_flags3); wr_u32b(r_ptr->r_flags4); wr_u32b(r_ptr->r_flags5); wr_u32b(r_ptr->r_flags6); /* Monster limit per level */ wr_byte(r_ptr->max_num); /* Later (?) */ wr_byte(0); wr_byte(0); wr_byte(0); } /* * Write an "xtra" record */ static void wr_xtra(int k_idx) { byte tmp8u = 0; object_kind *k_ptr = &k_info[k_idx]; if (k_ptr->aware) tmp8u |= 0x01; if (k_ptr->tried) tmp8u |= 0x02; wr_byte(tmp8u); } /* * Write a "store" record */ static void wr_store(store_type *st_ptr) { int j; /* Save the "open" counter */ wr_u32b(st_ptr->store_open); /* Save the "insults" */ wr_s16b(st_ptr->insult_cur); /* Save the current owner */ wr_byte(st_ptr->owner); /* Save the stock size */ wr_byte(st_ptr->stock_num); /* Save the "haggle" info */ wr_s16b(st_ptr->good_buy); wr_s16b(st_ptr->bad_buy); /* Save the stock */ for (j = 0; j < st_ptr->stock_num; j++) { /* Save each item in stock */ wr_item(&st_ptr->stock[j]); } } /* * Write the "options" */ static void wr_options(void) { int i; u16b c; u32b opt[4]; /*** Normal options ***/ /* Clear the option flag sets */ for (i = 0; i < 4; i++) opt[i] = 0L; /* Analyze the options */ for (i = 0; options[i].o_desc; i++) { int os = options[i].o_set; int ob = options[i].o_bit; /* Extract a variable setting, if possible */ if (options[i].o_var && os) { if (*options[i].o_var) opt[os-1] |= (1L << ob); } } /* Read the options */ for (i = 0; i < 4; i++) wr_u32b(opt[i]); /*** Special Options ***/ /* Write "delay_spd" */ wr_byte(delay_spd); /* Write "hitpoint_warn" */ wr_byte(hitpoint_warn); /*** Cheating options ***/ c = 0; if (wizard) c |= 0x0002; if (cheat_peek) c |= 0x0100; if (cheat_hear) c |= 0x0200; if (cheat_room) c |= 0x0400; if (cheat_xtra) c |= 0x0800; if (cheat_know) c |= 0x1000; if (cheat_live) c |= 0x2000; wr_u16b(c); } /* * Hack -- Write the "ghost" info */ static void wr_ghost() { int i; /* Name */ wr_string("Broken Ghost"); /* Hack -- stupid data */ for (i = 0; i < 60; i++) wr_byte(0); } /* * Write some "extra" info */ static void wr_extra() { int i; wr_string(player_name); wr_string(died_from); for (i = 0; i < 4; i++) { wr_string(history[i]); } /* Race/Class/Gender/Spells */ wr_byte(p_ptr->prace); wr_byte(p_ptr->pclass); wr_byte(p_ptr->male); wr_byte(0); /* oops */ wr_byte(p_ptr->hitdie); wr_byte(p_ptr->expfact); wr_s16b(p_ptr->age); wr_s16b(p_ptr->ht); wr_s16b(p_ptr->wt); /* Dump the stats (maximum and current) */ for (i = 0; i < 6; ++i) wr_s16b(p_ptr->stat_max[i]); for (i = 0; i < 6; ++i) wr_s16b(p_ptr->stat_cur[i]); /* Ignore the transient stats */ for (i = 0; i < 12; ++i) wr_s16b(0); wr_u32b(p_ptr->au); wr_u32b(p_ptr->max_exp); wr_u32b(p_ptr->exp); wr_u16b(p_ptr->exp_frac); wr_s16b(p_ptr->lev); wr_s16b(p_ptr->mhp); wr_s16b(p_ptr->chp); wr_u16b(p_ptr->chp_frac); wr_s16b(p_ptr->msp); wr_s16b(p_ptr->csp); wr_u16b(p_ptr->csp_frac); /* Max Player and Dungeon Levels */ wr_s16b(p_ptr->max_plv); wr_s16b(p_ptr->max_dlv); /* More info */ wr_s16b(0); /* oops */ wr_s16b(0); /* oops */ wr_s16b(0); /* oops */ wr_s16b(0); /* oops */ wr_s16b(p_ptr->sc); wr_s16b(0); /* oops */ wr_s16b(0); /* old "rest" */ wr_s16b(p_ptr->blind); wr_s16b(p_ptr->paralyzed); wr_s16b(p_ptr->confused); wr_s16b(p_ptr->food); wr_s16b(0); /* old "food_digested" */ wr_s16b(0); /* old "protection" */ wr_s16b(p_ptr->energy); wr_s16b(p_ptr->fast); wr_s16b(p_ptr->slow); wr_s16b(p_ptr->afraid); wr_s16b(p_ptr->cut); wr_s16b(p_ptr->stun); wr_s16b(p_ptr->poisoned); wr_s16b(p_ptr->image); wr_s16b(p_ptr->protevil); wr_s16b(p_ptr->invuln); wr_s16b(p_ptr->hero); wr_s16b(p_ptr->shero); wr_s16b(p_ptr->shield); wr_s16b(p_ptr->blessed); wr_s16b(p_ptr->tim_invis); wr_s16b(p_ptr->word_recall); wr_s16b(p_ptr->see_infra); wr_s16b(p_ptr->tim_infra); wr_s16b(p_ptr->oppose_fire); wr_s16b(p_ptr->oppose_cold); wr_s16b(p_ptr->oppose_acid); wr_s16b(p_ptr->oppose_elec); wr_s16b(p_ptr->oppose_pois); wr_byte(p_ptr->confusing); wr_byte(0); /* oops */ wr_byte(0); /* oops */ wr_byte(0); /* oops */ wr_byte(p_ptr->searching); wr_byte(p_ptr->maximize); wr_byte(p_ptr->preserve); wr_byte(0); /* Future use */ for (i = 0; i < 12; i++) wr_u32b(0L); /* Ignore some flags */ wr_u32b(0L); /* oops */ wr_u32b(0L); /* oops */ wr_u32b(0L); /* oops */ /* Write the "object seeds" */ wr_u32b(seed_flavor); wr_u32b(seed_town); /* Special stuff */ wr_u16b(panic_save); wr_u16b(total_winner); wr_u16b(noscore); /* Write death */ wr_byte(death); /* Write feeling */ wr_byte(feeling); /* Turn of last "feeling" */ wr_s32b(old_turn); /* Current turn */ wr_s32b(turn); } /* * New "cave grid" flags -- saved in savefile */ #define OLD_GRID_W_01 0x0001 /* Wall type (bit 1) */ #define OLD_GRID_W_02 0x0002 /* Wall type (bit 2) */ #define OLD_GRID_PERM 0x0004 /* Wall type is permanent */ #define OLD_GRID_QQQQ 0x0008 /* Unused */ #define OLD_GRID_MARK 0x0010 /* Grid is memorized */ #define OLD_GRID_GLOW 0x0020 /* Grid is illuminated */ #define OLD_GRID_ROOM 0x0040 /* Grid is part of a room */ #define OLD_GRID_ICKY 0x0080 /* Grid is anti-teleport */ /* * Masks for the new grid types */ #define OLD_GRID_WALL_MASK 0x0003 /* Wall type */ /* * Legal results of OLD_GRID_WALL_MASK */ #define OLD_GRID_WALL_NONE 0x0000 /* No wall */ #define OLD_GRID_WALL_MAGMA 0x0001 /* Magma vein */ #define OLD_GRID_WALL_QUARTZ 0x0002 /* Quartz vein */ #define OLD_GRID_WALL_GRANITE 0x0003 /* Granite wall */ /* * Write the current dungeon * * XXX XXX XXX Mega-Hack -- convert new "terrain feature" info back * into standard Angband 2.7.9 savefile format using "fake" objects, * so that I can use the new "terrain features" even though the new * savefile format is not ready yet. */ static void wr_dungeon() { int i, j, x, y; object_type forge; cave_type *c_ptr; object_type *i_ptr; /* Dungeon specific info follows */ wr_u16b(dun_level); wr_u16b(num_repro); wr_u16b(py); wr_u16b(px); wr_u16b(cur_hgt); wr_u16b(cur_wid); wr_u16b(max_panel_rows); wr_u16b(max_panel_cols); /* Dump the cave */ for (x = 0; x < cur_wid; x++) { for (y = 0; y < cur_hgt; y++) { /* Get the cave */ c_ptr = &cave[y][x]; /* Paranoia */ if (c_ptr->i_idx) { object_type *i_ptr = &i_list[c_ptr->i_idx]; i_ptr->set_location(x, y); } /* Paranoia */ if (c_ptr->m_idx) { monster_type *m_ptr = &m_list[c_ptr->m_idx]; m_ptr->set_location(x, y); } /* Write features/flags */ wr_byte(c_ptr->feat); wr_byte(c_ptr->info); } } /* Compact the objects */ compact_objects(0); /* Nothing yet */ j = 0; /* Point at the object */ i_ptr = &forge; /* Total objects */ wr_u16b(i_max + j); /* Dump the "real" items */ for (i = 1; i < i_max; i++) { i_ptr = &i_list[i]; wr_item(i_ptr); } /* Point at the object */ i_ptr = &forge; /* Compact the monsters */ compact_monsters(0); /* Dump the "real" monsters */ wr_u16b(m_max); for (i = 1; i < m_max; i++) { monster_type *m_ptr = &m_list[i]; wr_monster(m_ptr); } } /* * Actually write a save-file */ static bool wr_savefile_new(void) { int i; u32b now; u16b tmp16u; /* Guess at the current time */ now = time((time_t *)0); /* Note the operating system */ sf_xtra = 0L; /* Note when the file was saved */ sf_when = now; /* Note the number of saves */ sf_saves++; /*** Actually write the file ***/ /* Dump the file header */ xor_byte = 0; wr_byte(VERSION_MAJOR); xor_byte = 0; wr_byte(VERSION_MINOR); xor_byte = 0; wr_byte(VERSION_PATCH); xor_byte = 0; wr_byte(VERSION_TYPE); /* Reset the checksum */ v_stamp = 0L; x_stamp = 0L; /* Operating system */ wr_u32b(sf_xtra); /* Time file last saved */ wr_u32b(sf_when); /* Number of past lives */ wr_u16b(sf_lives); /* Number of times saved */ wr_u16b(sf_saves); /* Space */ wr_u32b(0L); wr_u32b(0L); /* Write the boolean "options" */ wr_options(); /* Dump the number of "messages" */ tmp16u = message_num(); if (compress_savefile && (tmp16u > 40)) tmp16u = 40; wr_u16b(tmp16u); /* Dump the messages (oldest first!) */ for (i = tmp16u - 1; i >= 0; i--) { wr_string(message_str(i)); } /* Dump the monster lore */ tmp16u = MAX_R_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) wr_lore(i); /* Dump the object memory */ tmp16u = MAX_K_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) wr_xtra(i); /* Hack -- Dump the quests */ tmp16u = MAX_Q_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) { wr_byte(q_list[i].level); wr_byte(0); wr_byte(0); wr_byte(0); } /* Hack -- Dump the artifacts */ tmp16u = MAX_A_IDX; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) { artifact_type *a_ptr = &a_info[i]; wr_byte(a_ptr->cur_num); wr_byte(0); wr_byte(0); wr_byte(0); } /* Write the "extra" information */ wr_extra(); /* Dump the "player hp" entries */ tmp16u = PY_MAX_LEVEL; wr_u16b(tmp16u); for (i = 0; i < tmp16u; i++) { wr_s16b(player_hp[i]); } /* Write spell data */ for (i = 0; i < 64; i++) { wr_byte(spell_learned[i]); wr_byte(spell_worked[i]); wr_byte(spell_forgotten[i]); } /* Dump the ordered spells */ for (i = 0; i < 64; i++) { wr_byte(spell_order[i]); } /* Write the inventory */ for (i = 0; i < INVEN_TOTAL; i++) { if (inventory[i].exists()) { wr_u16b(i); wr_item(&inventory[i]); } } /* Add a sentinel */ wr_u16b(0xFFFF); /* Note the stores */ tmp16u = MAX_STORES; wr_u16b(tmp16u); /* Dump the stores */ for (i = 0; i < tmp16u; i++) wr_store(&store[i]); /* Player is not dead, write the dungeon */ if (!death) { /* Dump the dungeon */ wr_dungeon(); /* Dump the ghost */ wr_ghost(); } /* Write the "value check-sum" */ wr_u32b(v_stamp); /* Write the "encoded checksum" */ wr_u32b(x_stamp); /* Error in save */ if (ferror(fff) || (fflush(fff) == EOF)) return FALSE; /* Successful save */ return TRUE; } /* * Medium level player saver * * XXX XXX XXX Angband 2.8.0 will use "fd" instead of "fff" if possible */ static bool save_player_aux(char *name) { bool ok = FALSE; int fd = -1; int mode = 0644; /* No file yet */ fff = NULL; #if defined(MACINTOSH) && !defined(applec) /* Global -- "save file" */ _feate = 'SAVE'; #endif /* Compress monsters */ compact_monsters(0); /* Create the savefile */ fd = fd_make(name, mode); /* File is okay */ if (fd >= 0) { /* Close the "fd" */ (void)fd_close(fd); /* Open the savefile */ fff = my_fopen(name, "wb"); /* Successful open */ if (fff) { /* Write the savefile */ if (wr_savefile_new()) ok = TRUE; /* Attempt to close it */ if (my_fclose(fff)) ok = FALSE; } /* Remove "broken" files */ if (!ok) (void)remove(name); } /* Failure */ if (!ok) return (FALSE); /* Successful save */ character_saved = TRUE; /* Success */ return (TRUE); } /* * Attempt to save the player in a savefile */ bool save_player() { int result = FALSE; char safe[1024]; #ifdef SET_UID # ifdef SECURE /* Get "games" permissions */ beGames(); # endif #endif /* New savefile */ strcpy(safe, savefile); strcat(safe, ".new"); #ifdef VM /* Hack -- support "flat directory" usage on VM/ESA */ strcpy(safe, savefile); strcat(safe, "n"); #endif /* VM */ /* Remove it */ fd_kill(safe); /* Attempt to save the player */ if (save_player_aux(safe)) { char temp[1024]; /* Old savefile */ strcpy(temp, savefile); strcat(temp, ".old"); #ifdef VM /* Hack -- support "flat directory" usage on VM/ESA */ strcpy(temp, savefile); strcat(temp, "o"); #endif /* VM */ /* Remove it */ fd_kill(temp); /* Preserve old savefile */ fd_move(savefile, temp); /* Activate new savefile */ fd_move(safe, savefile); /* Remove preserved savefile */ fd_kill(temp); /* Hack -- Pretend the character was loaded */ character_loaded = TRUE; #ifdef VERIFY_SAVEFILE /* Lock on savefile */ strcpy(temp, savefile); strcat(temp, ".lok"); /* Remove lock file */ fd_kill(temp); #endif /* Success */ result = TRUE; } #ifdef SET_UID # ifdef SECURE /* Drop "games" permissions */ bePlayer(); # endif #endif /* Return the result */ return (result); } /* * Attempt to Load a "savefile" * * Version 2.7.0 introduced a slightly different "savefile" format from * older versions, requiring a completely different parsing method. * * Note that savefiles from 2.7.0 - 2.7.2 were a little flaky, and are * thus obsolete, and will probably not be correctly loaded. * * Pre-2.8.0 savefiles lose all player ghost information. * * Pre-2.7.7 savefiles lose most monster memory. * * Pre-2.7.0 savefiles lose some of the "saved messages", some of the * "aware" flags, etc. * * On multi-user systems, you may only "read" a savefile if you will be * allowed to "write" it later, this prevents painful situations in which * the player loads a savefile belonging to someone else, and then is not * allowed to save his game when he quits. * * We return "TRUE" if the savefile was usable, and we set the global * flag "character_loaded" if a real, living, character was loaded. * * Note that we always try to load the "current" savefile, even if * there is no such file, so we must check for "empty" savefile names. */ bool load_player(void) { int fd = -1; errr err = 0; byte vvv[4]; #ifdef VERIFY_TIMESTAMP struct stat statbuf; #endif cptr what = "generic"; /* Paranoia */ turn = 0; /* Paranoia */ death = FALSE; /* Allow empty savefile name */ if (!savefile[0]) return (TRUE); #if !defined(MACINTOSH) && !defined(WINDOWS) && \ !defined(ACORN) && !defined(VM) /* XXX XXX XXX Fix this */ /* Verify the existance of the savefile */ if (access(savefile, 0) < 0) { /* Give a message */ msg_print("Savefile does not exist."); msg_print(NULL); /* Allow this */ return (TRUE); } #endif #ifdef VERIFY_SAVEFILE /* Verify savefile usage */ if (!err) { FILE *fkk; char temp[1024]; /* Extract name of lock file */ strcpy(temp, savefile); strcat(temp, ".lok"); /* Check for lock */ fkk = my_fopen(temp, "r"); /* Oops, lock exists */ if (fkk) { /* Close the file */ my_fclose(fkk); /* Message */ msg_print("Savefile is currently in use."); msg_print(NULL); /* Oops */ return (FALSE); } /* Create a lock file */ fkk = my_fopen(temp, "w"); /* Dump a line of info */ fprintf(fkk, "Lock file for savefile '%s'\n", savefile); /* Close the lock file */ my_fclose(fkk); } #endif /* Okay */ if (!err) { /* Open the savefile */ fd = fd_open(savefile, O_RDONLY); /* No file */ if (fd < 0) err = -1; /* Message (below) */ if (err) what = "Cannot open savefile"; } /* Process file */ if (!err) { #ifdef VERIFY_TIMESTAMP /* Get the timestamp */ (void)fstat(fd, &statbuf); #endif /* Read the first four bytes */ if (fd_read(fd, (char*)(vvv), 4)) err = -1; /* What */ if (err) what = "Cannot read savefile"; /* Close the file */ (void)fd_close(fd); } /* Process file */ if (!err) { /* Extract version */ sf_major = vvv[0]; sf_minor = vvv[1]; sf_patch = vvv[2]; sf_extra = vvv[3]; /* Clear screen */ clear_screen(); /* Check version */ if ((sf_major != version_major) || (sf_minor != version_minor) || (sf_patch != version_patch) || (sf_extra != version_type)) { err = -1; } /* Parse modern savefiles */ else { /* Attempt to load */ err = rd_savefile_new(); } /* Message (below) */ if (err) what = "Cannot parse savefile"; } /* Paranoia */ if (!err) { /* Invalid turn */ if (!turn) err = -1; /* Message (below) */ if (err) what = "Broken savefile"; } #ifdef VERIFY_TIMESTAMP /* Verify timestamp */ if (!err && !arg_wizard) { /* Hack -- Verify the timestamp */ if (sf_when > (statbuf.st_ctime + 100) || sf_when < (statbuf.st_ctime - 100)) { /* Message */ what = "Invalid timestamp"; /* Oops */ err = -1; } } #endif /* Okay */ if (!err) { /* Player is dead */ if (death) { /* Player is no longer "dead" */ death = FALSE; /* Hack -- delay death */ if (arg_wizard) return (TRUE); /* Count the past lives */ sf_lives++; /* Forget the turn, and old_turn */ turn = old_turn = 0; /* Done */ return (TRUE); } /* A character was loaded */ character_loaded = TRUE; /* Still alive */ if (p_ptr->chp >= 0) { /* Reset cause of death */ (void)strcpy(died_from, "(alive and well)"); } /* Success */ return (TRUE); } #ifdef VERIFY_SAVEFILE /* Verify savefile usage */ if (TRUE) { char temp[1024]; /* Extract name of lock file */ strcpy(temp, savefile); strcat(temp, ".lok"); /* Remove lock */ fd_kill(temp); } #endif /* Message */ if (sf_extra == 0) { msg_format("Error (%s) reading v%d.%da%d savefile.", what, sf_major, sf_minor, sf_patch); } else if (sf_extra == 1) { msg_format("Error (%s) reading v%d.%db%d savefile.", what, sf_major, sf_minor, sf_patch); } else if (sf_extra == 2) { msg_format("Error (%s) reading v%d.%d savefile.", what, sf_major, sf_minor); } else { msg_format("Error (%s) reading v%d savefile.", what, sf_major); } msg_print(NULL); /* Oops */ return (FALSE); }