Files
mtgodot-poc/formats/attribute_data.cpp
T

166 lines
5.5 KiB
C++

#include "attribute_data.h"
#include <cstring>
#include <fstream>
#include <limits>
namespace fmt {
namespace {
constexpr char kHeader[] = "AttributeData";
constexpr std::size_t kHeaderSize = sizeof(kHeader); // include trailing NUL
constexpr std::uint32_t kMaxRecords = 1u << 20;
constexpr std::uint32_t kMaxHeightVertices = 1u << 24;
struct Reader {
const std::uint8_t *data = nullptr;
std::size_t size = 0;
std::size_t offset = 0;
std::string *error = nullptr;
bool fail(const std::string &message) {
if (error)
*error = message + " at offset " + std::to_string(offset);
return false;
}
bool require(std::size_t count) {
if (count > size || offset > size - count)
return fail("AttributeData truncated");
return true;
}
template <typename T>
bool read(T &value) {
if (!require(sizeof(T)))
return false;
std::memcpy(&value, data + offset, sizeof(T));
offset += sizeof(T);
return true;
}
bool read_bytes(void *dst, std::size_t count) {
if (!require(count))
return false;
std::memcpy(dst, data + offset, count);
offset += count;
return true;
}
};
std::size_t dimension_count(std::uint32_t type) {
switch (static_cast<AttributeCollisionType>(type)) {
case AttributeCollisionType::Plane:
case AttributeCollisionType::Cylinder:
return 2;
case AttributeCollisionType::Box:
case AttributeCollisionType::Sphere:
case AttributeCollisionType::Aabb:
case AttributeCollisionType::Obb:
return type == static_cast<std::uint32_t>(AttributeCollisionType::Sphere) ? 1 : 3;
default:
return 0;
}
}
std::string fixed_name(const char (&raw)[32]) {
const void *nul = std::memchr(raw, '\0', 32);
const auto count = nul ? static_cast<const char *>(nul) - raw : 32;
return std::string(raw, static_cast<std::size_t>(count));
}
} // namespace
bool parse_attribute_data(const std::uint8_t *data, std::size_t size,
AttributeData &out, std::string *err) {
out = AttributeData{};
if (data == nullptr)
return err ? ((*err = "AttributeData buffer is null"), false) : false;
Reader r{data, size, 0, err};
char header[kHeaderSize] = {};
if (!r.read_bytes(header, kHeaderSize))
return false;
if (std::memcmp(header, kHeader, kHeaderSize) != 0)
return r.fail("AttributeData bad header");
std::uint32_t collision_count = 0;
std::uint32_t height_count = 0;
if (!r.read(collision_count) || !r.read(height_count))
return false;
if (collision_count > kMaxRecords || height_count > kMaxRecords)
return r.fail("AttributeData record count is unreasonable");
out.collisions.reserve(collision_count);
for (std::uint32_t i = 0; i < collision_count; ++i) {
AttributeCollision collision;
if (!r.read(collision.type))
return false;
const std::size_t dims = dimension_count(collision.type);
if (dims == 0)
return r.fail("AttributeData unknown collision type");
char raw_name[32] = {};
if (!r.read_bytes(raw_name, sizeof(raw_name)) ||
!r.read_bytes(collision.position.data(), sizeof(float) * 3) ||
!r.read_bytes(collision.dimensions.data(), sizeof(float) * dims) ||
!r.read_bytes(collision.quaternion.data(), sizeof(float) * 4))
return false;
collision.name = fixed_name(raw_name);
out.collisions.push_back(std::move(collision));
}
out.heights.reserve(height_count);
for (std::uint32_t i = 0; i < height_count; ++i) {
AttributeHeight height;
char raw_name[32] = {};
std::uint32_t vertex_count = 0;
if (!r.read_bytes(raw_name, sizeof(raw_name)) || !r.read(vertex_count))
return false;
if (vertex_count > kMaxHeightVertices)
return r.fail("AttributeData height vertex count is unreasonable");
if (vertex_count > 0 && vertex_count > (std::numeric_limits<std::size_t>::max() / 12))
return r.fail("AttributeData height size overflows");
if (!r.require(static_cast<std::size_t>(vertex_count) * 12))
return false;
height.name = fixed_name(raw_name);
height.vertices.resize(vertex_count);
for (auto &vertex : height.vertices) {
if (!r.read_bytes(vertex.data(), sizeof(float) * 3))
return false;
}
out.heights.push_back(std::move(height));
}
if (r.offset != size)
return r.fail("AttributeData trailing bytes");
return true;
}
bool parse_attribute_data_file(const std::string &path, AttributeData &out,
std::string *err) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
if (err)
*err = "cannot open AttributeData: " + path;
return false;
}
const std::streamoff end = file.tellg();
if (end < 0) {
if (err)
*err = "cannot stat AttributeData: " + path;
return false;
}
std::vector<std::uint8_t> bytes(static_cast<std::size_t>(end));
file.seekg(0, std::ios::beg);
if (!bytes.empty() && !file.read(reinterpret_cast<char *>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) {
if (err)
*err = "cannot read AttributeData: " + path;
return false;
}
return parse_attribute_data(bytes.data(), bytes.size(), out, err);
}
} // namespace fmt