166 lines
5.5 KiB
C++
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
|