#include "save.h" #include "ent.h" #include "env.h" typedef struct { i64 size_pos; i64 data_pos; } SaveChunk; typedef struct { u64 next_pid; f32 day_cur, day_len; } SaveLoadContext; static bool read_u8_le(FILE *fp, u8 *val) { return fread(val, 1, 1, fp) == 1; } static bool write_u8_le(FILE *fp, u8 val) { return fwrite(&val, 1, 1, fp) == 1; } static bool read_u16_le(FILE *fp, u16 *val) { u8 bytes[2]; if (fread(bytes, 1, 2, fp) != 2) { return false; } *val = (u16)bytes[0] | (u16)bytes[1] << 8; return true; } static bool write_u16_le(FILE *fp, u16 val) { u8 bytes[2] = { (u8)(val >> 0), (u8)(val >> 8), }; return fwrite(bytes, 1, 2, fp) == 2; } static bool read_u32_le(FILE *fp, u32 *val) { u8 bytes[4]; if (fread(bytes, 1, 4, fp) != 4) { return false; } *val = (u32)bytes[0] << 0 | (u32)bytes[1] << 8 | (u32)bytes[2] << 16 | (u32)bytes[3] << 24; return true; } static bool write_u32_le(FILE *fp, u32 val) { u8 bytes[4] = { (u8)(val >> 0), (u8)(val >> 8), (u8)(val >> 16), (u8)(val >> 24), }; return fwrite(bytes, 1, 4, fp) == 4; } static bool read_u64_le(FILE *fp, u64 *val) { u8 bytes[8]; if (fread(bytes, 1, 8, fp) != 8) { return false; } *val = (u64)bytes[0] << 0 | (u64)bytes[1] << 8 | (u64)bytes[2] << 16 | (u64)bytes[3] << 24 | (u64)bytes[4] << 32 | (u64)bytes[5] << 40 | (u64)bytes[6] << 48 | (u64)bytes[7] << 56; return true; } static bool write_u64_le(FILE *fp, u64 val) { u8 bytes[8] = { (u8)(val >> 0), (u8)(val >> 8), (u8)(val >> 16), (u8)(val >> 24), (u8)(val >> 32), (u8)(val >> 40), (u8)(val >> 48), (u8)(val >> 56), }; return fwrite(bytes, 1, 8, fp) == 8; } static bool read_f32_le(FILE *fp, f32 *val) { u32 bits; if (!read_u32_le(fp, &bits)) { return false; } memcpy(val, &bits, sizeof(*val)); return true; } static bool write_f32_le(FILE *fp, f32 value) { u32 bits; memcpy(&bits, &value, 4); return write_u32_le(fp, bits); } static bool read_vec3(FILE *fp, Vec3 *val) { return read_f32_le(fp, &val->x) && read_f32_le(fp, &val->y) && read_f32_le(fp, &val->z); } static bool write_vec3(FILE *fp, Vec3 val) { return write_f32_le(fp, val.x) && write_f32_le(fp, val.y) && write_f32_le(fp, val.z); } static bool read_quat(FILE *fp, Quaternion *val) { return read_f32_le(fp, &val->x) && read_f32_le(fp, &val->y) && read_f32_le(fp, &val->z) && read_f32_le(fp, &val->w); } static bool write_quat(FILE *fp, Quaternion val) { return write_f32_le(fp, val.x) && write_f32_le(fp, val.y) && write_f32_le(fp, val.z) && write_f32_le(fp, val.w); } static bool write_byte_arr(FILE *fp, const void *data, u32 len) { return fwrite(data, 1, len, fp) == len; } /* some entities are 'transient', so we need a persistent entity count */ static u32 persistent_entity_count(void) { u32 cnt = 0; for (u32 i = 0; i < entity_count(); ++i) { if (entity_data()[i].pid != 0) { cnt++; } } return cnt; } static bool chunk_begin(FILE *fp, const char label[4], SaveChunk *chunk) { if (!write_byte_arr(fp, label, 4)) { return false; } chunk->size_pos = ftell(fp); if (chunk->size_pos < 0 || !write_u32_le(fp, 0)) { return false; } chunk->data_pos = ftell(fp); return chunk->data_pos >= 0; } static bool chunk_end(FILE *fp, SaveChunk *chunk) { u32 end_pos = ftell(fp); if (end_pos < chunk->data_pos) { return false; } u64 size = (u64)(end_pos - chunk->data_pos); if (size > UINT32_MAX) { return false; } if (fseek(fp, chunk->size_pos, SEEK_SET) != 0 || !write_u32_le(fp, (u32)size) || fseek(fp, end_pos, SEEK_SET) != 0) { return false; } return true; } static bool read_header(FILE *fp) { char magic[4]; u16 major, minor; if (fread(magic, 1, 4, fp) != 4 || !read_u16_le(fp, &major) || !read_u16_le(fp, &minor)) { return false; } if (memcmp(magic, "FGSF", 4) != 0) { TraceLog(LOG_ERROR, "invalid save file magic."); return false; } if (major != SAVE_VERSION_MAJOR || minor != SAVE_VERSION_MINOR) { TraceLog(LOG_ERROR, "unsupported save file version %u.%u.", major, minor); return false; } return true; } static bool write_header(FILE *fp) { return fwrite("FGSF", 1, 4, fp) == 4 && write_u16_le(fp, SAVE_VERSION_MAJOR) && write_u16_le(fp, SAVE_VERSION_MINOR); } static bool read_entity_chunk(FILE *fp, SaveLoadContext *ctx) { u64 next_pid; u32 entity_cnt; if (!read_u64_le(fp, &next_pid) || !read_u32_le(fp, &entity_cnt)) { return false; } if (next_pid == 0) { return false; } ctx->next_pid = next_pid; for (u32 i = 0; i < entity_cnt; ++i) { u64 pid = 0; read_u64_le(fp, &pid); if (pid == 0 || pid >= next_pid) { TraceLog(LOG_ERROR, "entity PID invalid."); return false; } u32 name_len = 0; read_u32_le(fp, &name_len); u64 id = entity_restore(pid); if (!id) { TraceLog(LOG_ERROR, "entity pid already exists."); return false; } Entity *ent = entity_get(id); ent->name = malloc(name_len + 1); if (name_len > 0 && fread(ent->name, 1, name_len, fp) != name_len) { TraceLog(LOG_ERROR, "failed to read entity name."); return false; } ent->name[name_len] = '\0'; } entity_system_set_next_pid(next_pid); return true; } static bool write_entity_chunk(FILE *fp) { SaveChunk chunk = {0}; if (!chunk_begin(fp, "ENTT", &chunk) || !write_u64_le(fp, entity_next_pid()) || !write_u32_le(fp, persistent_entity_count())) { return false; } for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (ent->pid == 0) { continue; } if (!write_u64_le(fp, ent->pid) || !write_u32_le(fp, strlen(ent->name)) || !write_byte_arr(fp, ent->name, strlen(ent->name))) { return false; } } return chunk_end(fp, &chunk); } static bool read_transform_chunk(FILE *fp, SaveLoadContext *ctx) { u32 tr_cnt; read_u32_le(fp, &tr_cnt); u64 *parent_pids = calloc(ctx->next_pid, sizeof(u64)); if (!parent_pids) { TraceLog(LOG_FATAL, "calloc failed."); } for (u32 i = 0; i < tr_cnt; ++i) { u64 pid, parent_pid; if (!read_u64_le(fp, &pid) || !read_u64_le(fp, &parent_pid) || pid == 0 || pid >= ctx->next_pid || parent_pid >= ctx->next_pid) { goto fail; } u64 id = entity_id_from_pid(pid); Entity *ent = entity_get(id); if (!ent) { TraceLog(LOG_ERROR, "failed to properly load entity"); goto fail; } if (!read_vec3(fp, &ent->tr.pos) || !read_quat(fp, &ent->tr.rot) || !read_vec3(fp, &ent->tr.scale)) { goto fail; } parent_pids[pid] = parent_pid; } /* Build hierarchy only after all transforms exist. */ for (u64 pid = 1; pid < ctx->next_pid; ++pid) { u64 parent_pid = parent_pids[pid]; if (!parent_pid) { continue; } u64 id = entity_id_from_pid(pid); u64 parent_id = entity_id_from_pid(parent_pid); if (!id || !parent_id || !entity_set_parent(id, parent_id)) { TraceLog(LOG_ERROR, "entity hierarchy build failed."); goto fail; } } free(parent_pids); return true; fail: free(parent_pids); return false; } static bool write_transform_chunk(FILE *fp) { SaveChunk chunk; u32 tr_cnt = 0; for (u32 i = 0; i < entity_count(); ++i) { if (entity_data()[i].pid) { tr_cnt++; } } if (!chunk_begin(fp, "TRFM", &chunk) || !write_u32_le(fp, tr_cnt)) { return false; } for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (!ent->pid) { continue; } u64 parent_pid = 0; if (ent->tr.parent) { Entity *parent = entity_get(ent->tr.parent); if (!parent) { return false; } parent_pid = parent->pid; } if (!write_u64_le(fp, ent->pid) || !write_u64_le(fp, parent_pid) || !write_vec3(fp, ent->tr.pos) || !write_quat(fp, ent->tr.rot) || !write_vec3(fp, ent->tr.scale)) { return false; } } return chunk_end(fp, &chunk); } static bool read_model_chunk(FILE *fp, SaveLoadContext *ctx) { u32 mdl_cnt; if (!read_u32_le(fp, &mdl_cnt)) { return false; } for (u32 i = 0; i < mdl_cnt; ++i) { u64 pid; u32 key_len; u8 draw; if (!read_u64_le(fp, &pid) || !read_u32_le(fp, &key_len)) { return false; } if (pid == 0 || pid >= ctx->next_pid) { return false; } u64 id = entity_id_from_pid(pid); Entity *ent = entity_get(id); if (!ent || ent->mdl.has) { return false; } char *key = malloc(key_len + 1); if (!key) { return false; } if (key_len && fread(key, 1, key_len, fp) != key_len) { free(key); return false; } key[key_len] = '\0'; if (!read_u8_le(fp, &draw)) { free(key); return false; } ent->mdl.key = key; ent->mdl.draw = draw; ent->mdl.has = true; } return true; } static bool write_model_chunk(FILE *fp) { SaveChunk chunk = {0}; u32 mdl_cnt = 0; for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (ent->pid && ent->mdl.has) { mdl_cnt++; } } if (!chunk_begin(fp, "MODL", &chunk) || !write_u32_le(fp, mdl_cnt)) { return false; } for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (!ent->pid || !ent->mdl.has) { continue; } u32 key_len = (u32)strlen(ent->mdl.key); if (!write_u64_le(fp, ent->pid) || !write_u32_le(fp, key_len) || !write_byte_arr(fp, ent->mdl.key, key_len) || !write_u8_le(fp, ent->mdl.draw ? 1 : 0)) { return false; } } return chunk_end(fp, &chunk); } static bool read_material_chunk(FILE *fp, SaveLoadContext *ctx, MaterialProperty prop) { u32 count; if (!read_u32_le(fp, &count)) { return false; } for (u32 i = 0; i < count; ++i) { u64 pid; u32 mat_idx, key_len; if (!read_u64_le(fp, &pid) || !read_u32_le(fp, &mat_idx) || !read_u32_le(fp, &key_len)) { return false; } if (!pid || pid >= ctx->next_pid || mat_idx == UINT32_MAX || !key_len) { return false; } Entity *ent = entity_get(entity_id_from_pid(pid)); if (!ent || !ent->mdl.has) { return false; } ModelCmp *model = &ent->mdl; if (mat_idx >= model->mat_cnt) { size_t slots = (size_t)mat_idx + 1; if (slots > SIZE_MAX / sizeof(MaterialOverride)) { return false; } MaterialOverride *materials; materials = realloc(model->materials, slots * sizeof(*materials)); if (!materials) { return false; } memset(materials + model->mat_cnt, 0, (slots - model->mat_cnt) * sizeof(*materials)); model->materials = materials; model->mat_cnt = slots; } MaterialOverride *material; material = &model->materials[mat_idx]; char **field = prop == MAT_PROP_TEXTURE ? &material->tex_key : &material->shader_key; if (*field) { return false; } char *key = malloc((size_t)key_len + 1); if (!key) { return false; } if (fread(key, 1, key_len, fp) != key_len) { free(key); return false; } key[key_len] = '\0'; *field = key; } return true; } static bool write_material_chunk(FILE *fp, MaterialProperty prop) { SaveChunk chunk = {0}; u32 count = 0; cstr label = prop == MAT_PROP_TEXTURE ? "MTEX" : "MSHD"; for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (!ent->pid || !ent->mdl.has) { continue; } for (u32 j = 0; j < ent->mdl.mat_cnt; ++j) { const MaterialOverride *material; material = &ent->mdl.materials[j]; cstr key = prop == MAT_PROP_TEXTURE ? material->tex_key : material->shader_key; count += key != NULL; } } if (!chunk_begin(fp, label, &chunk) || !write_u32_le(fp, count)) { return false; } for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (!ent->pid || !ent->mdl.has) { continue; } for (u32 j = 0; j < ent->mdl.mat_cnt; ++j) { const MaterialOverride *material; material = &ent->mdl.materials[j]; cstr key = prop == MAT_PROP_TEXTURE ? material->tex_key : material->shader_key; if (!key) { continue; } u32 key_len = (u32)strlen(key); if (!write_u64_le(fp, ent->pid) || !write_u32_le(fp, j) || !write_u32_le(fp, key_len) || !write_byte_arr(fp, key, key_len)) { return false; } } } return chunk_end(fp, &chunk); } static bool read_physics_shape(FILE *fp, PhysicsShape *shape) { *shape = (PhysicsShape){0}; u8 tag = 0; if (!read_u8_le(fp, &tag)) { TraceLog(LOG_ERROR, "failed to read physics shape tag"); return false; } shape->tag = tag; if (!read_vec3(fp, &shape->pos) || !read_quat(fp, &shape->rot) || !read_f32_le(fp, &shape->density) || !read_f32_le(fp, &shape->friction) || !read_f32_le(fp, &shape->restitution) || !read_u32_le(fp, &shape->category_bits) || !read_u32_le(fp, &shape->mask_bits)) { TraceLog(LOG_ERROR, "failed to read physics shape."); return false; } switch (shape->tag) { case PHYS_SHAPE_BOX: return read_vec3(fp, &shape->Box.sz); break; case PHYS_SHAPE_SPHERE: return read_f32_le(fp, &shape->Sphere.radius); break; case PHYS_SHAPE_CAPSULE: return read_f32_le(fp, &shape->Capsule.height) && read_f32_le(fp, &shape->Capsule.radius); break; case PHYS_SHAPE_CYLINDER: return read_f32_le(fp, &shape->Cylinder.height) && read_f32_le(fp, &shape->Cylinder.radius); break; case PHYS_SHAPE_CONVEX_HULL: if (!read_u32_le(fp, &shape->ConvexHull.point_cnt)) { return false; } if (shape->ConvexHull.point_cnt) { shape->ConvexHull.points = calloc(shape->ConvexHull.point_cnt, sizeof(Vec3)); if (!shape->ConvexHull.points) { return false; } } for (u32 i = 0; i < shape->ConvexHull.point_cnt; ++i) { if (!read_vec3(fp, &shape->ConvexHull.points[i])) { return false; } } break; case PHYS_SHAPE_STATIC_TRIMESH: if (!read_u32_le(fp, &shape->TriMesh.point_cnt) || !read_u32_le(fp, &shape->TriMesh.idx_cnt)) { return false; } if (shape->TriMesh.point_cnt) { shape->TriMesh.points = calloc(shape->TriMesh.point_cnt, sizeof(Vec3)); if (!shape->TriMesh.points) { return false; } } if (shape->TriMesh.idx_cnt) { shape->TriMesh.indices = calloc(shape->TriMesh.idx_cnt, sizeof(u32)); if (!shape->TriMesh.indices) { return false; } } for (u32 i = 0; i < shape->TriMesh.point_cnt; ++i) { if (!read_vec3(fp, &shape->TriMesh.points[i])) { return false; } } for (u32 i = 0; i < shape->TriMesh.idx_cnt; ++i) { if (!read_u32_le(fp, &shape->TriMesh.indices[i])) { return false; } } break; }; return true; } static bool write_physics_shape(FILE *fp, PhysicsShape *shape) { if (!write_u8_le(fp, (u8)shape->tag) || !write_vec3(fp, shape->pos) || !write_quat(fp, shape->rot) || !write_f32_le(fp, shape->density) || !write_f32_le(fp, shape->friction) || !write_f32_le(fp, shape->restitution) || !write_u32_le(fp, shape->category_bits) || !write_u32_le(fp, shape->mask_bits)) { return false; } switch (shape->tag) { case PHYS_SHAPE_BOX: return write_vec3(fp, shape->Box.sz); break; case PHYS_SHAPE_SPHERE: return write_f32_le(fp, shape->Sphere.radius); break; case PHYS_SHAPE_CAPSULE: return write_f32_le(fp, shape->Capsule.height) && write_f32_le(fp, shape->Capsule.radius); break; case PHYS_SHAPE_CYLINDER: return write_f32_le(fp, shape->Cylinder.height) && write_f32_le(fp, shape->Cylinder.radius); break; case PHYS_SHAPE_CONVEX_HULL: if (shape->ConvexHull.point_cnt && !shape->ConvexHull.points) { return false; } if (!write_u32_le(fp, shape->ConvexHull.point_cnt)) { return false; } for (u32 i = 0; i < shape->ConvexHull.point_cnt; ++i) { if (!write_vec3(fp, shape->ConvexHull.points[i])) { return false; } } break; case PHYS_SHAPE_STATIC_TRIMESH: if ((shape->TriMesh.point_cnt && !shape->TriMesh.points) || (shape->TriMesh.idx_cnt && !shape->TriMesh.indices)) { return false; } if (!write_u32_le(fp, shape->TriMesh.point_cnt) || !write_u32_le(fp, shape->TriMesh.idx_cnt)) { return false; } for (u32 i = 0; i < shape->TriMesh.point_cnt; ++i) { if (!write_vec3(fp, shape->TriMesh.points[i])) { return false; } } for (u32 i = 0; i < shape->TriMesh.idx_cnt; ++i) { if (!write_u32_le(fp, shape->TriMesh.indices[i])) { return false; } } break; } return true; } static bool read_physics_body(FILE *fp, PhysicsBody *body) { u8 mode, enable_sleep; *body = (PhysicsBody){0}; if (!read_vec3(fp, &body->pos) || !read_quat(fp, &body->rot) || !read_f32_le(fp, &body->linear_damping) || !read_f32_le(fp, &body->angular_damping) || !read_f32_le(fp, &body->gravity_scale) || !read_u8_le(fp, &mode) || !read_u8_le(fp, &enable_sleep) || !read_u32_le(fp, &body->shape_cnt)) { return false; } body->mode = (PhysicsMode)mode; body->enable_sleep = enable_sleep; if (body->shape_cnt) { body->shapes = calloc(body->shape_cnt, sizeof(PhysicsShape)); if (!body->shapes) { return false; } } for (u32 i = 0; i < body->shape_cnt; ++i) { if (!read_physics_shape(fp, &body->shapes[i])) { return false; } } return true; } static bool write_physics_body(FILE *fp, PhysicsBody *body) { if (!write_vec3(fp, body->pos) || !write_quat(fp, body->rot) || !write_f32_le(fp, body->linear_damping) || !write_f32_le(fp, body->angular_damping) || !write_f32_le(fp, body->gravity_scale) || !write_u8_le(fp, (u8)body->mode) || !write_u8_le(fp, body->enable_sleep ? 1 : 0) || !write_u32_le(fp, body->shape_cnt)) { return false; } if (body->shape_cnt && !body->shapes) { return false; } for (u32 i = 0; i < body->shape_cnt; ++i) { if (!write_physics_shape(fp, &body->shapes[i])) { return false; } } return true; } static bool read_physics_joint(FILE *fp, PhysicsJoint *joint, u32 body_cnt) { u8 tag; *joint = (PhysicsJoint){0}; if (!read_u8_le(fp, &tag) || tag > PHYS_JOINT_DISTANCE || !read_u32_le(fp, &joint->body_a) || !read_u32_le(fp, &joint->body_b) || joint->body_a >= body_cnt || joint->body_b >= body_cnt || !read_u32_le(fp, &joint->flags) || !read_vec3(fp, &joint->anchor_a) || !read_vec3(fp, &joint->anchor_b) || !read_quat(fp, &joint->frame_rot_a) || !read_quat(fp, &joint->frame_rot_b) || !read_f32_le(fp, &joint->lower) || !read_f32_le(fp, &joint->upper) || !read_f32_le(fp, &joint->spring_hz) || !read_f32_le(fp, &joint->spring_damping) || !read_f32_le(fp, &joint->motor_speed) || !read_f32_le(fp, &joint->motor_force)) { return false; } joint->tag = (PhysicsJointTag)tag; return true; } static bool write_physics_joint(FILE *fp, PhysicsJoint *joint, u32 body_cnt) { if (joint->body_a >= body_cnt || joint->body_b >= body_cnt) { return false; } return write_u8_le(fp, (u8)joint->tag) && write_u32_le(fp, joint->body_a) && write_u32_le(fp, joint->body_b) && write_u32_le(fp, joint->flags) && write_vec3(fp, joint->anchor_a) && write_vec3(fp, joint->anchor_b) && write_quat(fp, joint->frame_rot_a) && write_quat(fp, joint->frame_rot_b) && write_f32_le(fp, joint->lower) && write_f32_le(fp, joint->upper) && write_f32_le(fp, joint->spring_hz) && write_f32_le(fp, joint->spring_damping) && write_f32_le(fp, joint->motor_speed) && write_f32_le(fp, joint->motor_force); } static bool read_physics_chunk(FILE *fp, SaveLoadContext *ctx) { u32 phys_cnt = 0; if (!read_u32_le(fp, &phys_cnt)) { return false; } for (u32 i = 0; i < phys_cnt; ++i) { u64 pid; u32 body_cnt, joint_cnt; if (!read_u64_le(fp, &pid) || !pid || pid >= ctx->next_pid) { return false; } u64 id = entity_id_from_pid(pid); Entity *ent = entity_get(id); if (!read_u32_le(fp, &body_cnt) || !ent || ent->phys.has) { return false; } u32 root_body; if (!read_u32_le(fp, &root_body)) { return false; } if ((body_cnt && root_body >= body_cnt) || (!body_cnt && root_body != 0)) { return false; } ent->phys.root_body = root_body; ent->phys.body_cnt = body_cnt; if (body_cnt) { ent->phys.bodies = calloc(body_cnt, sizeof(PhysicsBody)); if (!ent->phys.bodies) { return false; } } for (u32 j = 0; j < body_cnt; ++j) { if (!read_physics_body(fp, &ent->phys.bodies[j])) { return false; } } if (!read_u32_le(fp, &joint_cnt)) { return false; } ent->phys.joint_cnt = joint_cnt; if (joint_cnt) { ent->phys.joints = calloc(joint_cnt, sizeof(PhysicsJoint)); if (!ent->phys.joints) { return false; } } for (u32 j = 0; j < joint_cnt; ++j) { if (!read_physics_joint(fp, &ent->phys.joints[j], body_cnt)) { return false; } } ent->phys.has = true; } return true; } static bool write_physics_chunk(FILE *fp) { SaveChunk chunk = {0}; u32 phys_cnt = 0; for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; if (ent->pid && ent->phys.has) { phys_cnt++; } } if (!chunk_begin(fp, "PHYS", &chunk) || !write_u32_le(fp, phys_cnt)) { return false; } for (u32 i = 0; i < entity_count(); ++i) { Entity *ent = &entity_data()[i]; PhysicsCmp *phys = &ent->phys; if (!ent->pid || !phys->has) { continue; } if ((phys->body_cnt && !phys->bodies) || (phys->joint_cnt && !phys->joints) || (phys->body_cnt && phys->root_body >= phys->body_cnt) || (!phys->body_cnt && phys->root_body != 0) || !write_u64_le(fp, ent->pid) || !write_u32_le(fp, phys->body_cnt) || !write_u32_le(fp, phys->root_body)) { return false; } for (u32 j = 0; j < phys->body_cnt; ++j) { if (!write_physics_body(fp, &phys->bodies[j])) { return false; } } if (!write_u32_le(fp, phys->joint_cnt)) { return false; } for (u32 j = 0; j < phys->joint_cnt; ++j) { if (!write_physics_joint(fp, &phys->joints[j], phys->body_cnt)) { return false; } } } return chunk_end(fp, &chunk); } static bool read_environment_chunk(FILE *fp, SaveLoadContext *ctx) { f32 day_cur, day_len; if (!read_f32_le(fp, &day_cur) || !read_f32_le(fp, &day_len)) { return false; } if (!isfinite(day_cur) || day_cur < 0.0f || day_cur >= 1.0f || !isfinite(day_len) || day_len <= 0.0f) { return false; } ctx->day_cur = day_cur; ctx->day_len = day_len; return true; } static bool write_environment_chunk(FILE *fp) { SaveChunk chunk; return chunk_begin(fp, "ENVR", &chunk) && write_f32_le(fp, env.day_cur) && write_f32_le(fp, env.day_len) && chunk_end(fp, &chunk); } bool save_file_read(cstr path) { FILE *fp = fopen(path, "rb"); if (!fp) { TraceLog(LOG_ERROR, "save file '%s' failed to load.", path); return false; } SaveLoadContext ctx = { .day_cur = env.day_cur, .day_len = env.day_len, }; bool success = read_header(fp); bool read_entt = false; while (success) { char label[4]; size_t read_cnt = fread(label, 1, 4, fp); if (read_cnt == 0 && feof(fp)) { break; } if (read_cnt != 4) { TraceLog(LOG_ERROR, "failed to read chunk label."); success = false; break; } u32 chunk_size; if (!read_u32_le(fp, &chunk_size)) { TraceLog(LOG_ERROR, "failed to read chunk size."); success = false; break; } u32 data_pos = ftell(fp); if (data_pos < 0) { success = false; break; } u32 end_pos = data_pos + chunk_size; if (memcmp(label, "ENVR", 4) == 0) { success = chunk_size >= 8 && read_environment_chunk(fp, &ctx); } else if (memcmp(label, "ENTT", 4) == 0) { if (read_entt) { TraceLog(LOG_ERROR, "duplicate ENTT chunk."); success = false; break; } success = read_entity_chunk(fp, &ctx); read_entt = success; } else if (memcmp(label, "TRFM", 4) == 0) { if (!read_entt) { TraceLog(LOG_ERROR, "TRFM found before ENTT."); success = false; break; } success = read_transform_chunk(fp, &ctx); } else if (memcmp(label, "MODL", 4) == 0) { if (!read_entt) { TraceLog(LOG_ERROR, "TRFM found before ENTT."); success = false; break; } success = read_model_chunk(fp, &ctx); } else if (memcmp(label, "MTEX", 4) ==0 || memcmp(label, "MSHD", 4) == 0) { if (!read_entt) { success = false; break; } MaterialProperty prop; prop = memcmp(label, "MTEX", 4) == 0 ? MAT_PROP_TEXTURE : MAT_PROP_SHADER; success = read_material_chunk(fp, &ctx, prop); } else if (memcmp(label, "PHYS", 4) == 0) { if (!read_entt) { TraceLog(LOG_ERROR, "PHYS found before ENTT."); success = false; break; } success = read_physics_chunk(fp, &ctx); } else { TraceLog(LOG_INFO, "skipped unknown chunk."); } if (!success) { break; } u32 pos = ftell(fp); if (pos < 0 || pos > end_pos) { TraceLog(LOG_ERROR, "chunk reader exceeded chunk size."); success = false; break; } if (fseek(fp, end_pos, SEEK_SET) != 0) { success = false; break; } } if (!read_entt) { TraceLog(LOG_ERROR, "save contains no ENTT chunk."); success = false; } if (fclose(fp) != 0) { success = false; } if (success) { env.day_cur = ctx.day_cur; env.day_len = ctx.day_len; env.cycle = env.day_cur; } return success; } bool save_file_write(cstr path) { FILE *fp = fopen(path, "wb"); if (!fp) { return false; } bool success = write_header(fp) && write_environment_chunk(fp) && write_entity_chunk(fp) && write_transform_chunk(fp) && write_model_chunk(fp) && write_material_chunk(fp, MAT_PROP_TEXTURE) && write_material_chunk(fp, MAT_PROP_SHADER) && write_physics_chunk(fp); if (fclose(fp) != 0) { success = false; } return success; }