Complete remaining client gap features
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user