diff options
Diffstat (limited to 'serialize')
-rw-r--r-- | serialize/atlas-type.hpp | 2 | ||||
-rw-r--r-- | serialize/old-savegame.cpp | 632 | ||||
-rw-r--r-- | serialize/savegame.cpp | 86 |
3 files changed, 698 insertions, 22 deletions
diff --git a/serialize/atlas-type.hpp b/serialize/atlas-type.hpp index 01b682c3..1d3ea5aa 100644 --- a/serialize/atlas-type.hpp +++ b/serialize/atlas-type.hpp @@ -3,6 +3,6 @@ namespace floormat::Serialize { enum class atlas_type : uint8_t { - none, ground, wall, object, + none, ground, wall, anim, vobj, }; } // namespace floormat::Serialize diff --git a/serialize/old-savegame.cpp b/serialize/old-savegame.cpp new file mode 100644 index 00000000..9c5f7090 --- /dev/null +++ b/serialize/old-savegame.cpp @@ -0,0 +1,632 @@ +#include "binary-reader.inl" +#include "src/world.hpp" +#include "src/scenery.hpp" +#include "src/critter.hpp" +#include "src/light.hpp" +#include "loader/loader.hpp" +#include "loader/scenery.hpp" +#include "src/ground-atlas.hpp" +#include "src/anim-atlas.hpp" +#include "src/chunk-scenery.hpp" +#include "compat/strerror.hpp" +#include "src/tile.hpp" +#include "src/pass-mode.hpp" +#include "src/rotation.hpp" +#include "src/object-type.hpp" +#include <bit> +#include <cstdio> +#include <cerrno> +#include <cstring> +#include <concepts> +#include <memory> +#include <vector> + + +/* protocol changelog: + * 1) Initial version. + * 2) Tile atlas variant now always a uint8_t. Was uint16_t or uint8_t + * depending on value of the tile flag (1 << 6) which is now removed. + * 3) Serialize scenery. Tile flag (1 << 6) added. + * 4) Scenery dt now stored as fixed-point uint16_t. + * 5) Serialize scenery pixel offset. + * 6) Serialize scenery bboxes. + * 7) Serialize scenery bbox_size offset. + * 8) Entity subtypes. + * 9) Interned strings. + * 10) Chunk Z level. + * 11) RLE empty tiles. + * 12) Don't write object name twice. + * 13) Entity counter initialized to 1024. + * 14) Always store object offset, rework how sc_exact works. + * 15) Add light alpha. + * 16) One more bit for light falloff enum. + * 17) Switch critter::offset_frac to unsigned. + */ + +namespace floormat { +struct object; +struct object_proto; +} // namespace floormat + +namespace floormat::Serialize { + +using tilemeta = uint8_t; +using atlasid = uint16_t; +using chunksiz = uint16_t; +using proto_t = uint16_t; + +template<typename T> struct int_traits; + +template<std::unsigned_integral T> struct int_traits<T> { static constexpr T max = T(-1); }; +template<std::signed_integral T> struct int_traits<T> { static constexpr T max = T(-1)&~(T(1) << sizeof(T)*8-1); }; + +namespace { + +#define file_magic ".floormat.save" + +constexpr inline proto_t proto_version = 19; + +constexpr inline size_t atlas_name_max = 128; +constexpr inline auto null_atlas = (atlasid)-1LL; + +constexpr inline size_t critter_name_max = 128; +constexpr inline size_t string_max = 512; + +constexpr inline proto_t min_proto_version = 1; +constexpr inline auto chunk_magic = (uint16_t)~0xc0d3; +constexpr inline auto scenery_magic = (uint16_t)~0xb00b; + +constexpr inline auto pass_mask = (1 << pass_mode_BITS)-1; + +template<typename T, size_t N, size_t off> +constexpr inline auto highbits = (T(1) << N)-1 << sizeof(T)*8-N-off; + +template<size_t N, std::unsigned_integral T = uint8_t> +constexpr T lowbits = N == sizeof(T)*8 ? (T)-1 : T((T{1} << N)-T{1}); + +constexpr inline uint8_t meta_short_scenery_bit = highbits<uint8_t, 1, 0>; +constexpr inline uint8_t meta_rotation_bits = highbits<uint8_t, rotation_BITS, 1>; +constexpr inline uint8_t scenery_id_flag_mask = meta_short_scenery_bit | meta_rotation_bits; +constexpr inline uint8_t scenery_id_max = int_traits<uint8_t>::max & ~scenery_id_flag_mask; + +} // namespace + +template<typename T> concept object_subtype = std::is_base_of_v<object, T> || std::is_base_of_v<object_proto, T>; + +enum : tilemeta { + meta_ground = 1 << 2, + meta_wall_n = 1 << 3, + meta_wall_w = 1 << 4, + meta_rle = 1 << 7, + + meta_short_atlasid_ = 1 << 5, + meta_short_variant_ = 1 << 6, + meta_scenery_ = 1 << 7, +}; + +} // namespace floormat::Serialize + +namespace floormat { + +namespace { + +struct FILE_raii final { + FILE_raii(FILE* s) noexcept : s{s} {} + ~FILE_raii() noexcept { close(); } + operator FILE*() noexcept { return s; } + void close() noexcept { if (s) ::fclose(s); s = nullptr; } +private: + FILE* s; +}; + +} // namespace + +} // namespace floormat + +namespace { + +using namespace floormat; +using namespace floormat::Serialize; + +constexpr inline atlasid meta_short_scenery_bit_ = highbits<atlasid, 1, 0>; +constexpr inline atlasid meta_rotation_bits_ = highbits<atlasid, rotation_BITS, 1>; +constexpr inline atlasid scenery_id_flag_mask_ = meta_short_scenery_bit_ | meta_rotation_bits_; +constexpr inline atlasid scenery_id_max_ = int_traits<atlasid>::max & ~scenery_id_flag_mask_; + +struct reader_state final { + explicit reader_state(world& world) noexcept; + void deserialize_world(ArrayView<const char> buf, proto_t proto); + +private: + using reader_t = binary_reader<decltype(ArrayView<const char>{}.cbegin())>; + + StringView lookup_atlas(atlasid id); + const scenery_proto& lookup_scenery(atlasid id); + StringView lookup_string(uint32_t idx); + void read_atlases(reader_t& reader); + void read_sceneries(reader_t& reader); + void read_strings(reader_t& reader); + void read_chunks(reader_t& reader); + void read_old_scenery(reader_t& s, chunk_coords_ ch, size_t i); + void preload_chunks(); + + std::vector<String> strings; + std::vector<scenery_proto> sceneries; + std::vector<String> atlases; + world* _world; + uint16_t PROTO = proto_version; + + Array<chunk::object_draw_order> draw_array; + Array<std::array<chunk::vertex, 4>> draw_vertexes; + Array<std::array<UnsignedShort, 6>> draw_indexes; +}; + +reader_state::reader_state(world& world) noexcept : _world{&world} {} + +void reader_state::read_atlases(reader_t& s) +{ + const auto N = s.read<atlasid>(); + atlases.reserve(N); + for (atlasid i = 0; i < N; i++) + { + Vector2ub size; + size[0] << s; + size[1] << s; + const auto& [buf, len] = s.read_asciiz_string<atlas_name_max>(); + atlases.push_back({buf, len}); + } +} + +template<typename T, object_subtype U> +bool read_object_flags(binary_reader<T>& s, U& e) +{ + constexpr auto tag = object_type_<U>::value; + uint8_t flags; flags << s; + e.pass = pass_mode(flags & pass_mask); + if (e.type != tag) + fm_throw("invalid object type '{}'"_cf, (int)e.type); + if constexpr(tag == object_type::scenery) + { + e.active = !!(flags & 1 << 2); + e.closing = !!(flags & 1 << 3); + e.interactive = !!(flags & 1 << 4); + } + else if constexpr(tag == object_type::critter) + { + e.playable = !!(flags & 1 << 2); + } + else + { + static_assert(tag == object_type::none); + static_assert(tag != object_type::none); + } + return flags & 1 << 7; +} + +void reader_state::read_sceneries(reader_t& s) +{ + (void)loader.sceneries(); + + uint16_t magic; magic << s; + if (magic != scenery_magic) + fm_throw("bad scenery magic"_cf); + atlasid sz; sz << s; + fm_soft_assert(sz < scenery_id_max_); + sceneries.resize(sz); + + auto i = 0uz; + while (i < sz) + { + uint8_t num; num << s; + fm_soft_assert(num > 0); + auto str = s.read_asciiz_string<atlas_name_max>(); + auto sc = loader.scenery(str); + for (auto n = 0uz; n < num; n++) + { + atlasid id; id << s; + fm_soft_assert(id < sz); + fm_soft_assert(!sceneries[id]); + bool short_frame = read_object_flags(s, sc); + fm_debug_assert(sc.atlas != nullptr); + if (short_frame) + sc.frame = s.read<uint8_t>(); + else + sc.frame << s; + fm_soft_assert(sc.frame < sc.atlas->info().nframes); + sceneries[id] = sc; + } + i += num; + } + fm_soft_assert(i == sz); +} + +void reader_state::read_strings(reader_t& s) +{ + uint32_t size; size << s; + strings.reserve(size); + for (auto i = 0uz; i < size; i++) + { + auto str = s.read_asciiz_string<string_max>(); + strings.emplace_back(StringView{str}); + } +} + +StringView reader_state::lookup_atlas(atlasid id) +{ + if (id < atlases.size()) + return atlases[id]; + else + fm_throw("no such atlas: '{}'"_cf, id); +} + +const scenery_proto& reader_state::lookup_scenery(atlasid id) +{ + if (id < sceneries.size()) + return sceneries[id]; + else + fm_throw("no such scenery: '{}'"_cf, id); +} + +StringView reader_state::lookup_string(uint32_t idx) +{ + fm_soft_assert(idx < strings.size()); + return strings[idx]; +} + +#ifndef FM_NO_DEBUG +# define SET_CHUNK_SIZE() do { nbytes_read = s.bytes_read() - nbytes_start; } while (false) +#else +# define SET_CHUNK_SIZE() void() +#endif + +void reader_state::read_chunks(reader_t& s) +{ + Array<typename chunk::object_draw_order> array; + const auto N = s.read<chunksiz>(); +#ifndef FM_NO_DEBUG + [[maybe_unused]] size_t nbytes_read = 0; +#endif + + for (auto k = 0uz; k < N; k++) + { + const auto nbytes_start = s.bytes_read(); + + std::decay_t<decltype(chunk_magic)> magic; + magic << s; + if (magic != chunk_magic) + fm_throw("bad chunk magic"_cf); + chunk_coords_ ch; + ch.x << s; + ch.y << s; + if (PROTO >= 10) [[likely]] + ch.z << s; + auto& c = (*_world)[ch]; + c.mark_modified(); + for (auto i = 0uz; i < TILE_COUNT; i++) + { + SET_CHUNK_SIZE(); + const tilemeta flags = s.read<tilemeta>(); + + if (PROTO >= 11) [[likely]] + if (flags & meta_rle) + { + auto j = flags & 0x7fuz; + i += j; + continue; + } + + tile_ref t = c[i]; + using uchar = uint8_t; + const auto make_atlas = [&]<typename T>() -> image_proto_<T> { + atlasid id; + if (PROTO < 8) [[unlikely]] + id = flags & meta_short_atlasid_ ? atlasid{s.read<uchar>()} : s.read<atlasid>(); + else + id << s; + uint8_t v; + if (PROTO >= 2) [[likely]] + v << s; + else + v = flags & meta_short_variant_ + ? s.read<uint8_t>() + : uint8_t(s.read<uint16_t>()); + auto name = lookup_atlas(id); + if constexpr(std::is_same_v<ground_atlas, T>) + { + auto atlas = loader.ground_atlas(name, loader_policy::warn); + fm_soft_assert(v < atlas->num_tiles()); + return { atlas, v }; + } + else if (std::is_same_v<wall_atlas, T>) + { + auto atlas = loader.wall_atlas(name, loader_policy::warn); + return { atlas, v }; + } + else + std::unreachable(); + }; + SET_CHUNK_SIZE(); + //t.passability() = pass_mode(flags & pass_mask); + if (flags & meta_ground) + t.ground() = make_atlas.operator()<ground_atlas>(); + if (flags & meta_wall_n) + t.wall_north() = make_atlas.operator()<wall_atlas>(); + if (flags & meta_wall_w) + t.wall_west() = make_atlas.operator()<wall_atlas>(); + if (PROTO >= 3 && PROTO < 8) [[unlikely]] + if (flags & meta_scenery_) + read_old_scenery(s, ch, i); + SET_CHUNK_SIZE(); + } + uint32_t object_count = 0; + if (PROTO >= 8) [[likely]] + object_count << s; + + SET_CHUNK_SIZE(); + + for (auto i = 0uz; i < object_count; i++) + { + object_id oid; + object_type type; + if (PROTO >= 18) [[likely]] + { + oid << s; + fm_soft_assert((oid & lowbits<collision_data_BITS, object_id>) == oid); + type = object_type(s.read<std::underlying_type_t<object_type>>()); + fm_soft_assert(type < object_type::COUNT); + } + else + { + object_id _id; _id << s; + oid = _id & lowbits<60, object_id>; + fm_soft_assert(oid != 0); + type = object_type(_id >> 61); + } + const auto local = local_coords{s.read<uint8_t>()}; + + Vector2b offset; + if (PROTO >= 14) [[likely]] + { + offset[0] << s; + offset[1] << s; + } + constexpr auto read_bbox = [](auto& s, auto& e) { + s >> e.bbox_offset[0]; + s >> e.bbox_offset[1]; + s >> e.bbox_size[0]; + s >> e.bbox_size[1]; + }; + SET_CHUNK_SIZE(); + switch (type) + { + case object_type::critter: { + critter_proto proto; + proto.offset = offset; + uint8_t id; id << s; + proto.r = rotation(id >> sizeof(id)*8-1-rotation_BITS & rotation_MASK); + if (read_object_flags(s, proto)) + proto.frame = s.read<uint8_t>(); + else + proto.frame << s; + Vector2us offset_frac; + offset_frac[0] << s; + offset_frac[1] << s; + if (PROTO < 17) [[unlikely]] + offset_frac = {}; + const bool exact = id & meta_short_scenery_bit_; + SET_CHUNK_SIZE(); + + if (PROTO >= 9) [[likely]] + { + uint32_t id; id << s; + auto name = lookup_string(id); + fm_soft_assert(name.size() < critter_name_max); + proto.name = name; + } + else + { + auto [buf, len] = s.read_asciiz_string<critter_name_max>(); + auto name = StringView{buf, len}; + proto.name = name; + } + if (!exact) + { + if (PROTO < 14) [[unlikely]] + { + s >> proto.offset[0]; + s >> proto.offset[1]; + } + read_bbox(s, proto); + } + SET_CHUNK_SIZE(); + auto e = _world->make_object<critter, false>(oid, {ch, local}, proto); + e->offset_frac = offset_frac; + (void)e; + break; + } + case object_type::scenery: { + atlasid id; id << s; + bool exact; + rotation r; + if (PROTO >= 19) [[likely]] + { + uint8_t bits; bits << s; + exact = bits & meta_short_scenery_bit; + r = rotation(bits >> sizeof(bits)*8-1-rotation_BITS & rotation_MASK); + } + else + { + exact = id & meta_short_scenery_bit_; + r = rotation(id >> sizeof(id)*8-1-rotation_BITS & rotation_MASK); + id &= ~scenery_id_flag_mask_; + } + auto sc = lookup_scenery(id); + sc.offset = offset; + (void)sc.atlas->group(r); + sc.r = r; + if (!exact) + { + if (read_object_flags(s, sc)) + sc.frame = s.read<uint8_t>(); + else + sc.frame << s; + (void)sc.atlas->frame(sc.r, sc.frame); + if (PROTO < 14) [[unlikely]] + { + s >> sc.offset[0]; + s >> sc.offset[1]; + } + read_bbox(s, sc); + if (sc.active) + sc.delta << s; + } + auto e = _world->make_object<scenery, false>(oid, {ch, local}, sc); + (void)e; + break; + } + case object_type::light: { + light_proto proto; + proto.offset = offset; + + uint8_t flags; flags << s; + const bool exact = flags & 1; + proto.r = rotation((flags >> 1) & lowbits<rotation_BITS>); + bool enabled; + if (PROTO >= 16) [[likely]] + { + proto.falloff = light_falloff((flags >> 4) & lowbits<light_falloff_BITS>); + enabled = (flags >> 7) & 1; + } + else + { + proto.falloff = light_falloff((flags >> 4) & lowbits<2>); + enabled = (flags >> 6) & 1; + } + s >> proto.max_distance; + for (auto i = 0uz; i < 3; i++) + s >> proto.color[i]; + if (PROTO >= 15) [[likely]] + s >> proto.color[3]; + if (!exact) + { + uint16_t frame; frame << s; + auto pass = pass_mode((frame >> 14) & lowbits<2>); + frame &= lowbits<14, uint16_t>; + proto.pass = pass; + proto.frame = frame; + read_bbox(s, proto); + } + SET_CHUNK_SIZE(); + auto L = _world->make_object<light, false>(oid, {ch, local}, proto); + L->enabled = enabled; + (void)L; + break; + } + default: + fm_throw("invalid_object_type '{}'"_cf, (int)type); + } + } + + SET_CHUNK_SIZE(); + fm_assert(c.is_scenery_modified()); + fm_assert(c.is_passability_modified()); + c.sort_objects(); + } +} + +void reader_state::preload_chunks() +{ + for (auto& [coord, _] : _world->chunks()) + { + auto* c = _world->at(coord); + fm_assert(c); + c->ensure_ground_mesh(); + c->ensure_wall_mesh(); + c->ensure_scenery_mesh({ draw_array, draw_vertexes, draw_indexes }); + c->ensure_passability(); + } +} + +void reader_state::read_old_scenery(reader_t& s, chunk_coords_ ch, size_t i) +{ + atlasid id; id << s; + const bool exact = id & meta_short_scenery_bit_; + const auto r = rotation(id >> sizeof(id)*8-1-rotation_BITS & rotation_MASK); + id &= ~scenery_id_flag_mask_; + auto sc = lookup_scenery(id); + (void)sc.atlas->group(r); + sc.r = r; + if (!exact) + { + if (read_object_flags(s, sc)) + sc.frame = s.read<uint8_t>(); + else + sc.frame << s; + if (PROTO >= 5) [[likely]] + { + sc.offset[0] << s; + sc.offset[1] << s; + } + if (PROTO >= 6) [[likely]] + { + sc.bbox_size[0] << s; + sc.bbox_size[1] << s; + } + if (PROTO >= 7) [[likely]] + { + sc.bbox_offset[0] << s; + sc.bbox_offset[1] << s; + } + if (sc.active) + { + if (PROTO >= 4) [[likely]] + sc.delta << s; + else + sc.delta = (uint16_t)Math::clamp(int(s.read<float>() * 65535), 0, 65535); + } + } + global_coords coord{ch, local_coords{i}}; + auto e = _world->make_object<scenery, false>(_world->make_id(), coord, sc); + (void)e; +} + +void reader_state::deserialize_world(ArrayView<const char> buf, proto_t proto) +{ + fm_assert(_world != nullptr); + auto s = binary_reader{buf}; + if (!(proto >= min_proto_version && proto <= proto_version)) + fm_throw("bad proto version '{}' (should be between '{}' and '{}')"_cf, + (size_t)proto, (size_t)min_proto_version, (size_t)proto_version); + PROTO = proto; + fm_assert(PROTO > 0); + object_id object_counter = world::object_counter_init; + read_atlases(s); + if (PROTO >= 3) [[likely]] + read_sceneries(s); + if (PROTO >= 9) [[likely]] + read_strings(s); + if (PROTO >= 8) [[likely]] + object_counter << s; + read_chunks(s); + s.assert_end(); + if (PROTO >= 8) [[likely]] + fm_assert(_world->object_counter() == world::object_counter_init); + if (PROTO >= 13) [[likely]] + _world->set_object_counter(object_counter); + else if (PROTO >= 8) [[likely]] + _world->set_object_counter(std::max(world::object_counter_init, object_counter)); + preload_chunks(); + _world = nullptr; +} + +} // namespace + +namespace floormat { + +void world::deserialize_old(class world& w, ArrayView<const char> buf, proto_t proto) +{ + reader_state s{w}; + s.deserialize_world(buf, proto); +} + +} // namespace floormat diff --git a/serialize/savegame.cpp b/serialize/savegame.cpp index f2bb604c..6b473833 100644 --- a/serialize/savegame.cpp +++ b/serialize/savegame.cpp @@ -141,7 +141,15 @@ struct visitor_ void visit_object_internal(object& obj, F&& f, object_id id, object_type type, chunk_coords_ ch) { non_const(obj.id) = id; - do_visit_nonconst(obj.atlas, f); + switch (type) + { + case object_type::light: + static_cast<Derived&>(*this).visit(non_const(obj.atlas), atlas_type::vobj, f); + break; + default: + static_cast<Derived&>(*this).visit(non_const(obj.atlas), atlas_type::anim, f); + break; + } //do_visit(*obj.c, f); auto pt = obj.coord.local(); @@ -324,9 +332,9 @@ struct writer final : visitor_<writer> using visitor_<writer>::visit; template<typename F> - void visit(std::shared_ptr<anim_atlas>& a, F&& f) + void visit(std::shared_ptr<anim_atlas>& a, atlas_type type, F&& f) { - atlasid id = intern_atlas(a, atlas_type::object); + atlasid id = intern_atlas(a, type); do_visit(id, f); } @@ -362,8 +370,12 @@ struct writer final : visitor_<writer> { case atlas_type::ground: name = reinterpret_cast<const ground_atlas*>(atlas)->name(); goto ok; case atlas_type::wall: name = reinterpret_cast<const wall_atlas*>(atlas)->name(); goto ok; - case atlas_type::object: name = reinterpret_cast<const anim_atlas*>(atlas)->name(); goto ok; - case atlas_type::none: break; + case atlas_type::vobj: + case atlas_type::anim: + name = reinterpret_cast<const anim_atlas*>(atlas)->name(); + goto ok; + case atlas_type::none: + break; } fm_abort("invalid atlas type '%d'", (int)type); @@ -639,7 +651,8 @@ namespace { template<atlas_type Type> struct atlas_from_type; template<> struct atlas_from_type<atlas_type::ground> { using Type = ground_atlas; static StringView name(void* ptr) { return reinterpret_cast<Type*>(ptr)->name(); } }; template<> struct atlas_from_type<atlas_type::wall> { using Type = wall_atlas; static StringView name(void* ptr) { return reinterpret_cast<Type*>(ptr)->name(); }}; -template<> struct atlas_from_type<atlas_type::object> { using Type = anim_atlas; static StringView name(void* ptr) { return reinterpret_cast<Type*>(ptr)->name(); } }; +template<> struct atlas_from_type<atlas_type::anim> { using Type = anim_atlas; static StringView name(void* ptr) { return reinterpret_cast<Type*>(ptr)->name(); } }; +template<> struct atlas_from_type<atlas_type::vobj> { using Type = anim_atlas; static StringView name(void* ptr) { return reinterpret_cast<Type*>(ptr)->name(); } }; struct reader final : visitor_<reader> { @@ -687,11 +700,20 @@ struct reader final : visitor_<reader> } template<typename F> - void visit(std::shared_ptr<anim_atlas>& a, F&& f) + void visit(std::shared_ptr<anim_atlas>& a, atlas_type type, F&& f) { atlasid id = (atlasid)-1; f(id); - a = loader.anim_atlas(get_atlas<atlas_type::object>(id), {}); + switch (type) + { + case atlas_type::anim: a = loader.anim_atlas(get_atlas<atlas_type::anim>(id), {}); return; + case atlas_type::vobj: a = loader.vobj(get_atlas<atlas_type::vobj>(id)).atlas; return; + case atlas_type::ground: + case atlas_type::wall: + case atlas_type::none: + break; + } + fm_throw("invalid atlas type {}"_cf, (int)type); } template<typename F> @@ -708,26 +730,38 @@ struct reader final : visitor_<reader> case object_type::none: case object_type::COUNT: break; - case object_type::light: obj = w.make_unconnected_object<light>(); goto ok; - case object_type::critter: obj = w.make_unconnected_object<critter>(); goto ok; - case object_type::scenery: obj = w.make_unconnected_object<scenery>(); goto ok; + case object_type::light: + obj = w.make_unconnected_object<light>(); goto ok; + case object_type::critter: + obj = w.make_unconnected_object<critter>(); goto ok; + case object_type::scenery: + obj = w.make_unconnected_object<scenery>(); goto ok; } fm_throw("invalid object type {}"_cf, type_); ok: visit_object_internal(*obj, f, id, object_type(type), ch); } - void deserialize_header_(binary_reader<const char*>& s) + bool deserialize_header_(binary_reader<const char*>& s, ArrayView<const char> buf) { fm_assert(PROTO == (proto_t)-1); auto magic = s.read<file_magic.size()>(); fm_soft_assert(StringView{magic.data(), magic.size()} == file_magic); PROTO << s; - fm_soft_assert(PROTO >= proto_version_min); - fm_soft_assert(PROTO <= proto_version); - nstrings << s; - natlases << s; - nchunks << s; + if (PROTO < proto_version_min && PROTO > 0) + { + w.deserialize_old(w, buf.exceptPrefix(s.bytes_read()), PROTO); + return true; + } + else + { + fm_soft_assert(PROTO >= proto_version_min); + fm_soft_assert(PROTO <= proto_version); + nstrings << s; + natlases << s; + nchunks << s; + return false; + } } StringView get_string(atlasid id) @@ -767,9 +801,18 @@ ok: switch (type) { case atlas_type::none: break; - case atlas_type::ground: atlas = loader.ground_atlas(str, loader_policy::warn).get(); goto ok; - case atlas_type::wall: atlas = loader.wall_atlas(str, loader_policy::warn).get(); goto ok; - case atlas_type::object: atlas = loader.anim_atlas(str, {}).get(); goto ok; + case atlas_type::ground: + atlas = loader.ground_atlas(str, loader_policy::warn).get(); + goto ok; + case atlas_type::wall: + atlas = loader.wall_atlas(str, loader_policy::warn).get(); + goto ok; + case atlas_type::anim: + atlas = loader.anim_atlas(str, {}).get(); + goto ok; + case atlas_type::vobj: + atlas = loader.vobj(str).atlas.get(); + goto ok; } fm_throw("invalid atlas_type {}"_cf, (size_t)type); ok: @@ -876,7 +919,8 @@ ok: void deserialize_world(ArrayView<const char> buf) { binary_reader s{buf.data(), buf.data() + buf.size()}; - deserialize_header_(s); + if (deserialize_header_(s, buf)) + return; deserialize_strings_(s); deserialize_atlases(s); for (uint32_t i = 0; i < nchunks; i++) |