diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-12-04 13:58:02 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-12-15 23:04:39 +0100 |
| commit | 600ff28dd73f2cf17725382b68a4b1b2573f2e34 (patch) | |
| tree | a105c686458d8998438652aeca6299cf9000edcd /src/abc | |
Diffstat (limited to 'src/abc')
| -rw-r--r-- | src/abc/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/abc/abc.cpp | 912 | ||||
| -rw-r--r-- | src/abc/abc.h | 177 |
3 files changed, 1093 insertions, 0 deletions
diff --git a/src/abc/CMakeLists.txt b/src/abc/CMakeLists.txt new file mode 100644 index 0000000..52460e3 --- /dev/null +++ b/src/abc/CMakeLists.txt @@ -0,0 +1,4 @@ +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_sources(${PROJECT_NAME} PRIVATE + abc.cpp +) diff --git a/src/abc/abc.cpp b/src/abc/abc.cpp new file mode 100644 index 0000000..2ad45f1 --- /dev/null +++ b/src/abc/abc.cpp @@ -0,0 +1,912 @@ +#include <abc.h> +#include <cstdint> +#include <cstring> +#include <functional> +#include <iostream> +#include <string> + +int abc::parse(rez::file &f, std::string &name) const +{ + uint16_t len; + + if (f.read_le(len)) + { + std::cerr << f.path() << ": failed to read name length\n"; + return -1; + } + else if (len && f.read(name, len)) + { + std::cerr << f.path() << ": failed to read name\n"; + return -1; + } + + return 0; +} + +int abc::parse_header(rez::file &f) +{ + struct header &h = this->header; + + if (f.read_le(h.version) || + f.read_le(h.n_keyframe) || + f.read_le(h.n_anim) || + f.read_le(h.n_nodes) || + f.read_le(h.n_pieces) || + f.read_le(h.n_children) || + f.read_le(h.n_triangles) || + f.read_le(h.n_vertices) || + f.read_le(h.n_vertweights) || + f.read_le(h.n_lod) || + f.read_le(h.n_socket) || + f.read_le(h.n_weightsets) || + f.read_le(h.n_strings) || + f.read_le(h.n_strlen)) + { + std::cerr << f.path() << ": failed to read header\n"; + return -1; + } + + // Apparently, versions are numbered from 0x6a to 0x6c, as opposed + // to other titles with the same engine, that used 0xa to 0xc instead. + const uint32_t min_version = 0x6a, max_version = 0x6c; + + if (h.version < min_version || h.version > max_version) + { + std::cerr << f.path() << ": unsupported version " << h.version + << ", expected {" << min_version << ", " << max_version << "}\n"; + return -1; + } + + uint16_t cmdlen; + std::string cmd; + uint32_t n_loddist; + + if (f.read_le(cmdlen)) + { + std::cerr << f.path() << ": failed to read command length\n"; + return -1; + } + else if (cmdlen && f.read(cmd, cmdlen)) + { + std::cerr << f.path() << ": failed to read command\n"; + return -1; + } + + // These do not appear on the ABC v13 specification. + // Maybe they are game-specific? + uint32_t unknown[3], uint_radius; + + for (auto &w : unknown) + if (f.read_le(w)) + return -1; + + if (f.read_le(uint_radius)) + { + std::cerr << f.path() << ": failed to read internal radius\n"; + return -1; + } + else if (f.read_le(n_loddist)) + { + std::cerr << f.path() << ": failed to read LoD distance count\n"; + return -1; + } + + long cur = f.tell(); + + if (cur < 0) + { + std::cerr << f.path() << ": failed to tell offset\n"; + return -1; + } + else if (f.seek(cur + 60)) + { + std::cerr << f.path() << ": failed to skip padding bytes\n"; + return -1; + } + + std::vector<float> lod_distances; + + for (uint32_t i = 0; i < n_loddist; i++) + { + uint32_t v; + + if (f.read_le(v)) + { + std::cerr << f.path() << ": failed to read LoD number " + << i << std::endl; + return -1; + } + + lod_distances.push_back(v); + } + + memcpy(&int_radius, &uint_radius, sizeof uint_radius); + return 0; +} + +int abc::parse(rez::file &f, triangle &triangle) const +{ + uint16_t trifs; + uint32_t a, b; + + if (f.read_le(a) || f.read_le(b) || f.read_le(trifs)) + { + std::cerr << f.path() << ": failed to read triangle coords\n"; + return -1; + } + + memcpy(&triangle.a, &a, sizeof a); + memcpy(&triangle.b, &b, sizeof b); + triangle.trifs = trifs; + return 0; +} + +int abc::parse(rez::file &f, vector &vector) const +{ + uint32_t x, y, z; + + if (f.read_le(x) || f.read_le(y) || f.read_le(z)) + return -1; + + memcpy(&vector.x, &x, sizeof x); + memcpy(&vector.y, &y, sizeof y); + memcpy(&vector.z, &z, sizeof z); + return 0; +} + +int abc::parse(rez::file &f, weight &weight) const +{ + uint32_t bias; + + if (f.read_le(weight.node_index)) + { + std::cerr << f.path() << ": failed to read node index\n"; + return -1; + } + else if (parse(f, weight.location)) + { + std::cerr << f.path() << ": failed to read location\n"; + return -1; + } + else if (f.read_le(bias)) + { + std::cerr << f.path() << ": failed to read bias\n"; + return -1; + } + + memcpy(&weight.bias, &bias, sizeof bias); + return 0; +} + +int abc::parse(rez::file &f, vertex &vertex) const +{ + uint16_t n_weights, unknown; + + if (f.read_le(n_weights)) + { + std::cerr << f.path() << ": failed to read number of weights\n"; + return -1; + } + else if (f.read_le(unknown)) + { + std::cerr << f.path() << ": failed to read reserved halfword\n"; + return -1; + } + + for (uint16_t i = 0; i < n_weights; i++) + { + weight weight; + + if (parse(f, weight)) + return -1; + + vertex.weights.push_back(weight); + } + + if (parse(f, vertex.vertex)) + { + std::cerr << f.path() << ": failed to read vertex\n"; + return -1; + } + else if (parse(f, vertex.normal)) + { + std::cerr << f.path() << ": failed to read normal\n"; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, piece &piece) const +{ + long offset; + + if (f.read_le(piece.tex_index)) + { + std::cerr << f.path() << ": failed to read texture index\n"; + return -1; + } + else if (f.read_le(piece.specular_power)) + { + std::cerr << f.path() << ": failed to read specular power\n"; + return -1; + } + else if (f.read_le(piece.specular_scale)) + { + std::cerr << f.path() << ": failed to read specular scale\n"; + return -1; + } + else if (f.read_le(piece.lod_weight)) + { + std::cerr << f.path() << ": failed to read LoD weight\n"; + return -1; + } + else if ((offset = f.tell()) < 0) + { + std::cerr << f.path() << ": failed to get offset\n"; + return -1; + } + else if (f.seek(offset + sizeof (uint64_t))) + { + std::cerr << f.path() << ": failed to seek\n"; + return -1; + } + else if (parse(f, piece.name)) + { + std::cerr << f.path() << ": failed to read piece name\n"; + return -1; + } + else if (f.read_le(piece.n_tris)) + { + std::cerr << f.path() << ": failed to read n. trianges\n"; + return -1; + } + + piece.triangles.reserve(piece.n_tris * 3); + + for (uint32_t i = 0; i < piece.n_tris * 3; i++) + { + struct triangle triangle; + + if (parse(f, triangle)) + return -1; + + piece.triangles.push_back(triangle); + } + + uint32_t n_vertices; + + if (f.read_le(n_vertices)) + { + std::cerr << f.path() << ": failed to read number of vertices\n"; + return -1; + } + + piece.vertices.reserve(n_vertices); + + for (uint32_t i = 0; i < n_vertices; i++) + { + struct vertex vertex; + + if (parse(f, vertex)) + return -1; + + piece.vertices.push_back(vertex); + } + + return 0; +} + +int abc::parse_pieces(rez::file &f) +{ + uint32_t n_weights, n_pieces; + + if (f.read_le(n_weights)) + { + std::cerr << f.path() << ": failed to get weight count\n"; + return -1; + } + else if (f.read_le(n_pieces)) + { + std::cerr << f.path() << ": failed to get piece count\n"; + return -1; + } + + for (uint32_t i = 0; i < n_pieces; i++) + { + piece piece; + + if (parse(f, piece)) + { + std::cerr << f.path() << ": failed to read piece " << i + << std::endl; + return -1; + } + + pieces.push_back(piece); + } + + return 0; +} + +int abc::parse(rez::file &f, set &set) const +{ + uint32_t n_weights; + + if (parse(f, set.name)) + { + std::cerr << f.path() << ": failed to read set name\n"; + return -1; + } + else if (f.read_le(n_weights)) + { + std::cerr << f.path() << ": failed to read number of weights\n"; + return -1; + } + + for (uint32_t i = 0; i < n_weights; i++) + { + uint32_t weight; + float fweight; + + if (f.read_le(weight)) + { + std::cerr << f.path() << ": failed to read weight " << i + << std::endl; + return -1; + } + + memcpy(&fweight, &weight, sizeof weight); + set.weights.push_back(fweight); + } + + return 0; +} + +int abc::parse_sets(rez::file &f) +{ + uint32_t n_sets; + + if (f.read_le(n_sets)) + { + std::cerr << f.path() << ": failed to read number of sets\n"; + return -1; + } + + std::cerr << "n_sets: " << n_sets << std::endl; + + for (uint32_t i = 0; i < n_sets; i++) + { + set set; + + if (parse(f, set)) + return -1; + + sets.push_back(set); + } + + return 0; +} + +int abc::parse(rez::file &f, rotation &rot) const +{ + uint32_t w; + + if (parse(f, rot.v) || f.read_le(w)) + return -1; + + memcpy(&rot.w, &w, sizeof w); + return 0; +} + +int abc::parse(rez::file &f, node &parent) +{ + uint32_t n_children; + uint32_t unknown2; + node node; + + if (parse(f, node.name)) + { + std::cerr << f.path() << ": failed to read node name\n"; + return -1; + } + else if (f.read_le(node.transform_index)) + { + std::cerr << f.path() << ": failed to read transform index\n"; + return -1; + } + else if (f.read(node.flags)) + { + std::cerr << f.path() << ": failed to read node flags\n"; + return -1; + } + else if (f.read_le(unknown2)) + { + std::cerr << f.path() << ": failed to read unknown word\n"; + return -1; + } + + for (auto &rot : node.matrix) + if (parse(f, rot)) + { + std::cerr << f.path() << ": failed to read rotation\n"; + return -1; + } + + if (f.read_le(n_children)) + { + std::cerr << f.path() << ": failed to read number of children\n"; + return -1; + } + + for (uint32_t i = 0; i < n_children; i++) + if (parse(f, node)) + return -1; + + parent.children.push_back(node); + return 0; +} + +int abc::print(const node &node, unsigned level) const +{ + for (unsigned i = 0; i < level; i++) + std::cout << "|\t"; + + std::cout << node.name << '\n'; + + for (const auto &child : node.children) + print(child, level + 1); + + return 0; +} + +size_t abc::count(const node &node) const +{ + size_t ret = 0; + + for (const auto &child : node.children) + ret += count(child); + + return ret + node.children.size(); +} + +int abc::parse_nodes(rez::file &f) +{ + uint32_t unknown, n_nodes; + + if (f.read_le(unknown)) + { + std::cerr << f.path() << ": failed to read unknown word\n"; + return -1; + } + else if (parse(f, root)) + { + std::cerr << f.path() << ": failed to parse node\n"; + return -1; + } + else if ((n_nodes = count(root)) != header.n_nodes) + { + std::cerr << f.path() << ": expected to read " << header.n_nodes + << " nodes, got " << n_nodes << '\n'; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, item &item) const +{ + if (parse(f, item.pos)) + { + std::cerr << f.path() << ": failed to read position\n"; + return -1; + } + else if (parse(f, item.rot)) + { + std::cerr << f.path() << ": failed to read rotation\n"; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, childmodel &cm, bool self) const +{ + uint32_t unknown; + + if (self) + cm.name = "SELF"; + else if (parse(f, cm.name)) + return -1; + + if (f.read_le(unknown)) + { + std::cerr << f.path() << ": failed to read unknown word\n"; + return -1; + } + + cm.items.reserve(header.n_nodes); + + for (uint32_t i = 0; i < header.n_nodes; i++) + { + item item; + + if (parse(f, item)) + return -1; + + cm.items.push_back(item); + } + + return 0; +} + +int abc::parse_childmodels(rez::file &f) +{ + childmodel self; + uint32_t n; + + if (f.read_le(n)) + { + std::cerr << f.path() << ": failed to read number of child models\n"; + return -1; + } + else if (parse(f, self, true)) + { + std::cerr << f.path() << ": failed to read self child model\n"; + return -1; + } + + childmodels.reserve(n); + childmodels.push_back(self); + + for (uint32_t i = 1; i < n; i++) + { + childmodel cm; + + if (parse(f, cm)) + return -1; + + childmodels.push_back(cm); + } + + return 0; +} + +int abc::parse(rez::file &f, keyframe &keyframe) const +{ + if (f.read_le(keyframe.time)) + { + std::cerr << f.path() << ": failed to read keyframe time\n"; + return -1; + } + else if (parse(f, keyframe.cmd)) + { + std::cerr << f.path() << ": failed to read keyframe cmd\n"; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, animitem &animitem) const +{ + if (parse(f, animitem.item)) + return -1; + else if (f.read_le(animitem.unknown)) + { + std::cerr << f.path() << ": failed to read unknown word 1\n"; + return -1; + } + else if (f.read_le(animitem.unknown2)) + { + std::cerr << f.path() << ": failed to read unknown word 2\n"; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, nodeanim &nodeanim, uint32_t n_keyframes) const +{ + nodeanim.animitems.reserve(n_keyframes); + + for (uint32_t i = 0; i < n_keyframes; i++) + { + animitem animitem; + + if (parse(f, animitem)) + return -1; + + nodeanim.animitems.push_back(animitem); + } + + return 0; +} + +int abc::parse(rez::file &f, animation &animation) const +{ + uint32_t n_keyframes; + + if (parse(f, animation.dimensions)) + { + std::cerr << f.path() << ": failed to read dimensions\n"; + return -1; + } + else if (parse(f, animation.name)) + { + std::cerr << f.path() << ": failed to read animation name\n"; + return -1; + } + else if (f.read_le(animation.compression_type)) + { + std::cerr << f.path() << ": failed to read compression type\n"; + return -1; + } + else if (f.read_le(animation.interp_time)) + { + std::cerr << f.path() << ": failed to read interp time\n"; + return -1; + } + else if (f.read_le(n_keyframes)) + { + std::cerr << f.path() << ": failed to read number of keyframes\n"; + return -1; + } + + animation.keyframes.reserve(n_keyframes); + + for (uint32_t i = 0; i < n_keyframes; i++) + { + keyframe keyframe; + + if (parse(f, keyframe)) + return -1; + + animation.keyframes.push_back(keyframe); + } + + animation.nodeanims.reserve(header.n_nodes); + + for (uint32_t i = 0; i < header.n_nodes; i++) + { + nodeanim nodeanim; + uint32_t unknown; + + if (f.read_le(unknown)) + { + std::cerr << f.path() << ": failed to read unknown word\n"; + return -1; + } + else if (parse(f, nodeanim, n_keyframes)) + return -1; + + animation.nodeanims.push_back(nodeanim); + } + + return 0; +} + +int abc::parse_animation(rez::file &f) +{ + uint32_t n; + + if (f.read_le(n)) + { + std::cerr << f.path() << ": failed to read number of animations\n"; + return -1; + } + + animations.reserve(n); + + for (uint32_t i = 0; i < n; i++) + { + animation animation; + + if (parse(f, animation)) + return -1; + + animations.push_back(animation); + } + + return 0; +} + +int abc::parse(rez::file &f, socket &socket) const +{ + if (f.read_le(socket.node_index)) + { + std::cerr << f.path() << ": failed to read node index\n"; + return -1; + } + else if (parse(f, socket.name)) + { + std::cerr << f.path() << ": failed to read socket name\n"; + return -1; + } + else if (parse(f, socket.quat)) + { + std::cerr << f.path() << ": failed to read socket quat\n"; + return -1; + } + else if (parse(f, socket.pos)) + { + std::cerr << f.path() << ": failed to read socket position\n"; + return -1; + } + + return 0; +} + +int abc::parse_sockets(rez::file &f) +{ + uint32_t n; + + if (f.read_le(n)) + { + std::cerr << f.path() << ": failed to read number of sockets\n"; + return -1; + } + + for (uint32_t i = 0; i < n; i++) + { + socket socket; + + if (parse(f, socket)) + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, childanim &childanim) const +{ + if (parse(f, childanim.name)) + { + std::cerr << f.path() << ": failed to read childanim name\n"; + return -1; + } + else if (parse(f, childanim.dimensions)) + { + std::cerr << f.path() << ": failed to read childanim dimensions\n"; + return -1; + } + else if (parse(f, childanim.translation)) + { + std::cerr << f.path() << ": failed to read childanim translation\n"; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f, animbinding &animbinding) const +{ + uint32_t n; + + if (f.read_le(n)) + { + std::cerr << f.path() << ": failed to read number of animbindings\n"; + return -1; + } + + animbinding.childanims.reserve(n); + + for (uint32_t i = 0; i < n; i++) + { + childanim childanim; + + if (parse(f, childanim)) + return -1; + + animbinding.childanims.push_back(childanim); + } + + return 0; +} + +int abc::parse_animbindings(rez::file &f) +{ + animbindings.reserve(childmodels.size()); + + for (size_t i = 0; i < childmodels.size(); i++) + { + animbinding animbinding; + + if (parse(f, animbinding)) + return -1; + + animbindings.push_back(animbinding); + } + + return 0; +} + +int abc::parse_hitgroups(rez::file &f) +{ + uint32_t unknown, unknown2; + + if (f.read_le(unknown) || f.read_le(unknown2)) + { + std::cerr << f.path() << ": failed to read unknown words\n"; + return -1; + } + + return 0; +} + +int abc::parse(rez::file &f) +{ + int32_t offset; + + do + { + uint32_t uoffset; + std::string chunk; + + if (parse(f, chunk) || f.read_le(uoffset)) + return -1; + + offset = uoffset; + + static const struct + { + const char *chunk; + int (abc::*fn)(rez::file &); + } fns[] = + { + {"Header", &abc::parse_header}, + {"Pieces", &abc::parse_pieces}, + {"Nodes", &abc::parse_nodes}, + {"ChildModels", &abc::parse_childmodels}, + {"Animation", &abc::parse_animation}, + {"Sockets", &abc::parse_sockets}, + {"AnimBindings", &abc::parse_animbindings}, + {"HitGroups", &abc::parse_hitgroups} + }; + + bool found = false; + + for (const auto &fn : fns) + { + long before, after; + + if (chunk != fn.chunk) + continue; + else if ((before = f.tell()) < 0) + { + std::cerr << f.path() << ": failed to get \"before\" offset\n"; + return -1; + } + else if (std::invoke(fn.fn, this, f)) + return -1; + else if ((after = f.tell()) < 0) + { + std::cerr << f.path() << ": failed to get offset\n"; + return -1; + } + else if (offset > 0 && after != offset) + { + std::cerr << f.path() << ": expected file offset " + << offset << " after reading section " << chunk + << ", but current offset is " << after << '\n'; + return -1; + } + + found = true; + break; + } + + if (!found) + { + std::cerr << f.path() << ": unsupported section: " << chunk << '\n'; + return -1; + } + } while (offset != -1); + + return 0; +} + +abc::abc() : + int_radius(0.0f) +{ +} diff --git a/src/abc/abc.h b/src/abc/abc.h new file mode 100644 index 0000000..b86f13c --- /dev/null +++ b/src/abc/abc.h @@ -0,0 +1,177 @@ +#ifndef ABC_H +#define ABC_H + +#include <rez/file.h> +#include <cstdint> +#include <list> +#include <memory> +#include <vector> + +class abc +{ +public: + abc(); + int parse(rez::file &f); + +private: + + struct vector + { + float x, y, z; + }; + + struct rotation + { + vector v; + float w; + }; + + struct weight + { + uint32_t node_index; + vector location; + float bias; + }; + + struct vertex + { + std::vector<weight> weights; + vector vertex, normal; + }; + + struct triangle + { + float a, b; + uint16_t trifs; + }; + + struct piece + { + uint16_t tex_index; + uint32_t specular_power, specular_scale, lod_weight, n_tris; + std::string name; + std::vector<triangle> triangles; + std::vector<vertex> vertices; + }; + + struct set + { + std::string name; + std::vector<float> weights; + }; + + struct node + { + std::string name; + uint16_t transform_index; + uint8_t flags; + rotation matrix[4]; + std::list<node> children; + }; + + struct header + { + uint32_t version, n_keyframe, n_anim, n_nodes, n_pieces, n_children, + n_triangles, n_vertices, n_vertweights, n_lod, n_socket, + n_weightsets, n_strings, n_strlen; + }; + + struct item + { + vector pos; + rotation rot; + }; + + struct childmodel + { + std::string name; + std::vector<item> items; + }; + + struct keyframe + { + std::string cmd; + uint32_t time; + }; + + struct animitem + { + abc::item item; + uint32_t unknown, unknown2; + }; + + struct nodeanim + { + std::vector<animitem> animitems; + }; + + struct animation + { + std::string name; + vector dimensions; + uint32_t compression_type, interp_time; + std::vector<keyframe> keyframes; + std::vector<nodeanim> nodeanims; + }; + + struct socket + { + std::string name; + uint32_t node_index; + rotation quat; + vector pos; + }; + + struct childanim + { + std::string name; + vector dimensions, translation; + }; + + struct animbinding + { + std::vector<childanim> childanims; + }; + + int parse_header(rez::file &f); + int parse_pieces(rez::file &f); + int parse_nodes(rez::file &f); + int parse_sets(rez::file &f); + int parse_childmodels(rez::file &f); + int parse_animation(rez::file &f); + int parse_sockets(rez::file &f); + int parse_animbindings(rez::file &f); + int parse_hitgroups(rez::file &f); + int parse(rez::file &f, std::string &name) const; + int parse(rez::file &f, node &parent); + int parse(rez::file &f, piece &piece) const; + int parse(rez::file &f, triangle &triangle) const; + int parse(rez::file &f, vertex &vertex) const; + int parse(rez::file &f, weight &weight) const; + int parse(rez::file &f, vector &vector) const; + int parse(rez::file &f, rotation &rot) const; + int parse(rez::file &f, set &set) const; + int parse(rez::file &f, childmodel &cm, bool self = false) const; + int parse(rez::file &f, item &item) const; + int parse(rez::file &f, animation &animation) const; + int parse(rez::file &f, keyframe &keyframe) const; + int parse(rez::file &f, nodeanim &nodeanim, uint32_t n_keyframes) const; + int parse(rez::file &f, animitem &animitem) const; + int parse(rez::file &f, socket &socket) const; + int parse(rez::file &f, animbinding &animbinding) const; + int parse(rez::file &f, childanim &childanim) const; + int print(const node &node, unsigned level) const; + size_t count(const node &node) const; + + node root; + header header; + std::vector<piece> pieces; + std::vector<abc::set> sets; + std::vector<childmodel> childmodels; + std::vector<animation> animations; + std::vector<socket> sockets; + std::vector<animbinding> animbindings; + float int_radius; +}; + +#endif |
