save.c (11404B)
1 #define _POSIX_C_SOURCE 200809L 2 3 #include "engine/save.h" 4 5 #include <errno.h> 6 #include <stdint.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <unistd.h> 11 12 enum { PH_SAVE_MAGIC = 0x50485356, PH_SAVE_RECORD_LIMIT = 1000000 }; 13 14 static int 15 ph_write_u32(FILE *fp, uint32_t value) 16 { 17 unsigned char bytes[4] = { 18 (unsigned char)value, (unsigned char)(value >> 8), 19 (unsigned char)(value >> 16), (unsigned char)(value >> 24), 20 }; 21 return fwrite(bytes, 1, sizeof(bytes), fp) == sizeof(bytes) ? 0 : -1; 22 } 23 24 static int 25 ph_read_u32(FILE *fp, uint32_t *value) 26 { 27 unsigned char bytes[4]; 28 29 if (fread(bytes, 1, sizeof(bytes), fp) != sizeof(bytes)) return -1; 30 *value = (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 | 31 (uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24; 32 return 0; 33 } 34 35 static int ph_write_int(FILE *fp, int value) { return ph_write_u32(fp, (uint32_t)value); } 36 37 static int 38 ph_read_int(FILE *fp, int *value) 39 { 40 uint32_t raw; 41 if (ph_read_u32(fp, &raw) < 0) return -1; 42 *value = (int)(int32_t)raw; 43 return 0; 44 } 45 46 static int 47 ph_write_float(FILE *fp, float value) 48 { 49 uint32_t raw; 50 memcpy(&raw, &value, sizeof(raw)); 51 return ph_write_u32(fp, raw); 52 } 53 54 static int 55 ph_read_float(FILE *fp, float *value) 56 { 57 uint32_t raw; 58 if (ph_read_u32(fp, &raw) < 0) return -1; 59 memcpy(value, &raw, sizeof(raw)); 60 return 0; 61 } 62 63 static int 64 ph_write_vec(FILE *fp, PhVec2 value) 65 { 66 return ph_write_float(fp, value.x) < 0 || ph_write_float(fp, value.y) < 0 ? -1 : 0; 67 } 68 69 static int 70 ph_read_vec(FILE *fp, PhVec2 *value) 71 { 72 return ph_read_float(fp, &value->x) < 0 || ph_read_float(fp, &value->y) < 0 ? -1 : 0; 73 } 74 75 static int 76 ph_write_entity(FILE *fp, const PhEntity *entity) 77 { 78 int i; 79 80 if (ph_write_int(fp, entity->type_id) < 0 || ph_write_vec(fp, entity->pos) < 0 || 81 ph_write_vec(fp, entity->motion.target) < 0 || 82 ph_write_float(fp, entity->motion.credit) < 0 || 83 ph_write_float(fp, entity->motion.animation_distance) < 0 || 84 ph_write_int(fp, entity->motion.facing_x) < 0 || 85 ph_write_int(fp, entity->motion.facing_y) < 0 || 86 ph_write_int(fp, entity->motion.moving) < 0 || 87 ph_write_vec(fp, entity->behavior.home) < 0 || 88 ph_write_vec(fp, entity->behavior.last_seen) < 0 || 89 ph_write_int(fp, entity->behavior.territory_radius) < 0 || 90 ph_write_int(fp, entity->behavior.chase_turns) < 0 || 91 ph_write_int(fp, entity->vitals.hp) < 0 || 92 ph_write_int(fp, entity->vitals.mp) < 0 || 93 ph_write_int(fp, entity->xp) < 0 || ph_write_int(fp, entity->gold) < 0 || 94 ph_write_int(fp, entity->accuracy.value) < 0 || 95 ph_write_int(fp, entity->accuracy.turns) < 0) return -1; 96 for (i = 0; i < PH_EQUIP_COUNT; ++i) 97 if (ph_write_int(fp, entity->equipment[i]) < 0) return -1; 98 return 0; 99 } 100 101 static int 102 ph_read_entity(FILE *fp, PhEntity *entity, int area_id) 103 { 104 int i; 105 106 memset(entity, 0, sizeof(*entity)); 107 if (ph_read_int(fp, &entity->type_id) < 0 || ph_read_vec(fp, &entity->pos) < 0 || 108 ph_read_vec(fp, &entity->motion.target) < 0 || 109 ph_read_float(fp, &entity->motion.credit) < 0 || 110 ph_read_float(fp, &entity->motion.animation_distance) < 0 || 111 ph_read_int(fp, &entity->motion.facing_x) < 0 || 112 ph_read_int(fp, &entity->motion.facing_y) < 0 || 113 ph_read_int(fp, &entity->motion.moving) < 0 || 114 ph_read_vec(fp, &entity->behavior.home) < 0 || 115 ph_read_vec(fp, &entity->behavior.last_seen) < 0 || 116 ph_read_int(fp, &entity->behavior.territory_radius) < 0 || 117 ph_read_int(fp, &entity->behavior.chase_turns) < 0 || 118 ph_read_int(fp, &entity->vitals.hp) < 0 || 119 ph_read_int(fp, &entity->vitals.mp) < 0 || 120 ph_read_int(fp, &entity->xp) < 0 || ph_read_int(fp, &entity->gold) < 0 || 121 ph_read_int(fp, &entity->accuracy.value) < 0 || 122 ph_read_int(fp, &entity->accuracy.turns) < 0) return -1; 123 for (i = 0; i < PH_EQUIP_COUNT; ++i) 124 if (ph_read_int(fp, &entity->equipment[i]) < 0) return -1; 125 entity->area_id = area_id; 126 entity->active = 1; 127 return 0; 128 } 129 130 static int 131 ph_write_item(FILE *fp, const PhGroundItem *item) 132 { 133 return ph_write_int(fp, item->item_id) < 0 || ph_write_vec(fp, item->pos) < 0 || 134 ph_write_int(fp, item->amount) < 0 ? -1 : 0; 135 } 136 137 static int 138 ph_read_item(FILE *fp, PhGroundItem *item, int area_id) 139 { 140 memset(item, 0, sizeof(*item)); 141 if (ph_read_int(fp, &item->item_id) < 0 || ph_read_vec(fp, &item->pos) < 0 || 142 ph_read_int(fp, &item->amount) < 0) return -1; 143 item->area_id = area_id; 144 item->active = 1; 145 return 0; 146 } 147 148 static int 149 ph_save_stream(FILE *fp, PhWorld *world, const PhSpellBook *spells) 150 { 151 const PhEntity *player = ph_world_player(world); 152 int inventory_count = 0; 153 int i; 154 int j; 155 156 if (!player || ph_world_capture_area(world) < 0) return -1; 157 for (i = 0; i < world->content.item_count; ++i) 158 if (world->inventory[i] > 0) ++inventory_count; 159 if (ph_write_u32(fp, PH_SAVE_MAGIC) < 0 || ph_write_u32(fp, PH_SAVE_VERSION) < 0 || 160 ph_write_int(fp, world->area_id) < 0 || ph_write_entity(fp, player) < 0 || 161 ph_write_int(fp, inventory_count) < 0) return -1; 162 for (i = 0; i < world->content.item_count; ++i) { 163 if (world->inventory[i] <= 0) continue; 164 if (ph_write_int(fp, world->content.items[i].id) < 0 || 165 ph_write_int(fp, world->inventory[i]) < 0) return -1; 166 } 167 if (ph_write_int(fp, spells->learned_count) < 0) return -1; 168 for (i = 0; i < spells->learned_count; ++i) 169 if (ph_write_int(fp, spells->learned[i]) < 0) return -1; 170 if (ph_write_int(fp, world->initialized_area_count) < 0) return -1; 171 for (i = 0; i < world->initialized_area_count; ++i) { 172 const PhAreaState *state = &world->area_states[i]; 173 174 if (ph_write_int(fp, state->area_id) < 0 || 175 ph_write_int(fp, state->entity_count) < 0 || 176 ph_write_int(fp, state->ground_item_count) < 0) return -1; 177 for (j = 0; j < state->entity_count; ++j) 178 if (ph_write_entity(fp, &state->entities[j]) < 0) return -1; 179 for (j = 0; j < state->ground_item_count; ++j) 180 if (ph_write_item(fp, &state->ground_items[j]) < 0) return -1; 181 } 182 return 0; 183 } 184 185 int 186 ph_save_write(const char *path, PhWorld *world, const PhSpellBook *spells) 187 { 188 FILE *fp; 189 char *temporary; 190 size_t length; 191 int result = -1; 192 193 if (!path || !world || world->combat_active || !spells || 194 spells->learned_count < 0 || spells->learned_count > PH_SPELL_MAX) 195 return -1; 196 length = strlen(path); 197 temporary = malloc(length + 5); 198 if (!temporary) return -1; 199 memcpy(temporary, path, length); 200 memcpy(temporary + length, ".tmp", 5); 201 fp = fopen(temporary, "wb"); 202 if (!fp) goto done; 203 if (ph_save_stream(fp, world, spells) == 0 && fflush(fp) == 0 && 204 fsync(fileno(fp)) == 0) result = 0; 205 if (fclose(fp) != 0) result = -1; 206 fp = NULL; 207 if (result == 0 && rename(temporary, path) != 0) result = -1; 208 if (result < 0) remove(temporary); 209 done: 210 free(temporary); 211 return result; 212 } 213 214 static int 215 ph_item_index(const PhWorld *world, int item_id) 216 { 217 int i; 218 for (i = 0; i < world->content.item_count; ++i) 219 if (world->content.items[i].id == item_id) return i; 220 return -1; 221 } 222 223 static int 224 ph_area_known(const PhAreaCatalog *catalog, int area_id) 225 { 226 return ph_area_catalog_area(catalog, area_id) != NULL; 227 } 228 229 static int 230 ph_load_stream(FILE *fp, PhWorld *world, const PhWorldSetup *setup, 231 PhSpellBook *spells, float viewport_w, float viewport_h) 232 { 233 uint32_t magic; 234 uint32_t version; 235 PhArea area; 236 PhEntity player; 237 const PhEntityDef *player_def; 238 int area_id; 239 int count; 240 int i; 241 int j; 242 243 if (ph_read_u32(fp, &magic) < 0 || ph_read_u32(fp, &version) < 0 || 244 magic != PH_SAVE_MAGIC || version != PH_SAVE_VERSION || 245 ph_read_int(fp, &area_id) < 0 || !ph_area_known(setup->areas, area_id) || 246 ph_read_entity(fp, &player, area_id) < 0 || 247 ph_area_catalog_load(&area, setup->areas, area_id) < 0) return -1; 248 if (ph_world_init(world, area, area_id, setup->content, viewport_w, viewport_h) < 0) { 249 ph_area_free(&area); 250 return -1; 251 } 252 player_def = ph_world_entity_def(world, player.type_id); 253 if (!player_def || player_def->kind != PH_ENTITY_PLAYER) goto fail; 254 world->entities[0] = player; 255 world->entity_count = 1; 256 world->player_index = 0; 257 if (ph_read_int(fp, &count) < 0 || count < 0 || count > world->content.item_count) 258 goto fail; 259 for (i = 0; i < count; ++i) { 260 int item_id; 261 int amount; 262 int index; 263 if (ph_read_int(fp, &item_id) < 0 || ph_read_int(fp, &amount) < 0 || 264 amount <= 0 || (index = ph_item_index(world, item_id)) < 0 || 265 world->inventory[index] != 0) goto fail; 266 world->inventory[index] = amount; 267 } 268 if (ph_read_int(fp, &count) < 0 || count < 0 || count > PH_SPELL_MAX) goto fail; 269 spells->learned_count = 0; 270 for (i = 0; i < count; ++i) { 271 int spell_id; 272 if (ph_read_int(fp, &spell_id) < 0 || 273 ph_spellbook_learn(spells, spell_id) < 0) goto fail; 274 } 275 if (ph_read_int(fp, &count) < 0 || count <= 0 || count > PH_SAVE_RECORD_LIMIT) 276 goto fail; 277 world->area_states = calloc((size_t)count, sizeof(*world->area_states)); 278 if (!world->area_states) goto fail; 279 world->area_state_capacity = count; 280 for (i = 0; i < count; ++i) { 281 PhAreaState *state = &world->area_states[i]; 282 if (ph_read_int(fp, &state->area_id) < 0 || 283 !ph_area_known(setup->areas, state->area_id) || 284 ph_read_int(fp, &state->entity_count) < 0 || state->entity_count < 0 || 285 state->entity_count > PH_MAX_ENTITIES - 1 || 286 ph_read_int(fp, &state->ground_item_count) < 0 || 287 state->ground_item_count < 0 || 288 state->ground_item_count > PH_MAX_GROUND_ITEMS) goto fail; 289 if (i > 0 && world->area_states[i - 1].area_id >= state->area_id) goto fail; 290 world->initialized_area_count = i + 1; 291 if (state->entity_count) { 292 state->entities = calloc((size_t)state->entity_count, sizeof(*state->entities)); 293 if (!state->entities) goto fail; 294 } 295 if (state->ground_item_count) { 296 state->ground_items = calloc((size_t)state->ground_item_count, 297 sizeof(*state->ground_items)); 298 if (!state->ground_items) goto fail; 299 } 300 for (j = 0; j < state->entity_count; ++j) 301 if (ph_read_entity(fp, &state->entities[j], state->area_id) < 0 || 302 !ph_world_entity_def(world, state->entities[j].type_id)) goto fail; 303 for (j = 0; j < state->ground_item_count; ++j) 304 if (ph_read_item(fp, &state->ground_items[j], state->area_id) < 0 || 305 !ph_world_item_def(world, state->ground_items[j].item_id) || 306 state->ground_items[j].amount <= 0) goto fail; 307 } 308 for (i = 0; i < world->initialized_area_count; ++i) 309 if (world->area_states[i].area_id == area_id) break; 310 if (i == world->initialized_area_count) goto fail; 311 if (world->area_states[i].entity_count) { 312 memcpy(&world->entities[1], world->area_states[i].entities, 313 (size_t)world->area_states[i].entity_count * sizeof(*world->entities)); 314 world->entity_count += world->area_states[i].entity_count; 315 } 316 if (world->area_states[i].ground_item_count) { 317 memcpy(world->ground_items, world->area_states[i].ground_items, 318 (size_t)world->area_states[i].ground_item_count * sizeof(*world->ground_items)); 319 world->ground_item_count = world->area_states[i].ground_item_count; 320 } 321 ph_world_set_player(world, 0); 322 return 0; 323 324 fail: 325 ph_world_destroy(world); 326 return -1; 327 } 328 329 int 330 ph_save_load(const char *path, PhWorld *world, const PhWorldSetup *setup, 331 PhSpellBook *spells, float viewport_w, float viewport_h) 332 { 333 FILE *fp; 334 int result; 335 336 if (!path || !world || !setup || !setup->areas || !spells) return -1; 337 fp = fopen(path, "rb"); 338 if (!fp) return errno == ENOENT ? 0 : -1; 339 result = ph_load_stream(fp, world, setup, spells, viewport_w, viewport_h); 340 if (fclose(fp) != 0) { 341 if (result == 0) ph_world_destroy(world); 342 result = -1; 343 } 344 return result < 0 ? -1 : 1; 345 }