Implemented reader. Appears to work, but further testing is required.
This commit is contained in:
@@ -2,8 +2,104 @@
|
|||||||
|
|
||||||
using namespace esxr;
|
using namespace esxr;
|
||||||
|
|
||||||
|
Node read_unknown_record(std::istream &in);
|
||||||
|
|
||||||
[[nodiscard]] Header esxr::read_header(std::istream &in)
|
[[nodiscard]] Header esxr::read_header(std::istream &in)
|
||||||
{
|
{
|
||||||
return read_header_impl<directly_readable>(in);
|
return read_header_impl<directly_readable>(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t data_size(const RecordHeader& header) noexcept
|
||||||
|
{
|
||||||
|
return header.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t data_size(const GroupHeader& header) noexcept
|
||||||
|
{
|
||||||
|
return header.size - header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::streamsize data_ssize(const RecordHeader& header) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::streamsize>(data_size(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::streamsize data_ssize(const GroupHeader& header) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::streamsize>(data_size(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t node_size(const RecordNode& node) noexcept
|
||||||
|
{
|
||||||
|
return node.header.size + header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t node_size(const GroupNode& node) noexcept
|
||||||
|
{
|
||||||
|
return node.header.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::streamsize node_ssize(const RecordNode& node) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::streamsize>(node_size(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::streamsize node_ssize(const GroupNode& node) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::streamsize>(node_size(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::streamsize node_ssize(const Node& node)
|
||||||
|
{
|
||||||
|
return std::visit(utility::overloaded{
|
||||||
|
[](const GroupNode &node) { return node_ssize(node); },
|
||||||
|
[](const RecordNode &node) { return node_ssize(node); },
|
||||||
|
}, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node read_record(RecordHeader header, std::istream &in)
|
||||||
|
{
|
||||||
|
std::vector<char> data(data_size(header));
|
||||||
|
in.read(data.data(), data_ssize(header));
|
||||||
|
return { RecordNode{ header, std::move(data) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
Node read_group(GroupHeader header, std::istream &in)
|
||||||
|
{
|
||||||
|
std::vector<Node> children{};
|
||||||
|
auto remaining = data_ssize(header);
|
||||||
|
while (remaining > 0) {
|
||||||
|
children.emplace_back(read_unknown_record(in));
|
||||||
|
remaining -= node_ssize(children.back());
|
||||||
|
}
|
||||||
|
if (remaining < 0)
|
||||||
|
throw std::runtime_error("Read past end of group data.");
|
||||||
|
return { GroupNode{ header, std::move(children) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
Node read_unknown_record(std::istream &in)
|
||||||
|
{
|
||||||
|
auto header_variant = read_header(in);
|
||||||
|
struct visitor {
|
||||||
|
std::istream *in_ptr;
|
||||||
|
Node operator()(RecordHeader h) {
|
||||||
|
return read_record(h, *in_ptr);
|
||||||
|
}
|
||||||
|
Node operator()(GroupHeader h) {
|
||||||
|
return read_group(h, *in_ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return std::visit(visitor{ &in }, std::move(header_variant));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] RootNode esxr::read_esx(std::istream &in, std::size_t file_size)
|
||||||
|
{
|
||||||
|
RootNode root{};
|
||||||
|
auto remaining = static_cast<std::streamsize>(file_size);
|
||||||
|
while (remaining != 0) {
|
||||||
|
root.children.emplace_back(read_unknown_record(in));
|
||||||
|
remaining -= node_ssize(root.children.back());
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ namespace esxr {
|
|||||||
// Forward declarations
|
// Forward declarations
|
||||||
//
|
//
|
||||||
|
|
||||||
class GroupNode;
|
struct RecordNode;
|
||||||
class RecordNode;
|
struct GroupNode;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Enumerations
|
// Enumerations
|
||||||
@@ -135,16 +135,18 @@ struct RecordHeader {
|
|||||||
using Node = std::variant<GroupNode, RecordNode>;
|
using Node = std::variant<GroupNode, RecordNode>;
|
||||||
using Header = std::variant<RecordHeader, GroupHeader>;
|
using Header = std::variant<RecordHeader, GroupHeader>;
|
||||||
|
|
||||||
class GroupNode {
|
struct RootNode {
|
||||||
private:
|
std::vector<Node> children;
|
||||||
GroupHeader m_header;
|
|
||||||
std::vector<Node> m_children;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class RecordNode {
|
struct GroupNode {
|
||||||
private:
|
GroupHeader header;
|
||||||
RecordHeader m_header;
|
std::vector<Node> children;
|
||||||
std::vector<std::byte> data;
|
};
|
||||||
|
|
||||||
|
struct RecordNode {
|
||||||
|
RecordHeader header;
|
||||||
|
std::vector<char> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -159,6 +161,7 @@ private:
|
|||||||
[[nodiscard]] std::optional<std::string_view> refr_flag_to_description(RefrFlag refr_flag) noexcept;
|
[[nodiscard]] std::optional<std::string_view> refr_flag_to_description(RefrFlag refr_flag) noexcept;
|
||||||
|
|
||||||
[[nodiscard]] Header read_header(std::istream &in);
|
[[nodiscard]] Header read_header(std::istream &in);
|
||||||
|
[[nodiscard]] RootNode read_esx(std::istream &in, std::size_t file_size);
|
||||||
|
|
||||||
// Convert a compatible C string to a FourCC (e.g. "LITR")
|
// Convert a compatible C string to a FourCC (e.g. "LITR")
|
||||||
static consteval FourCC fourcc_from_cstr(const char(&a)[5]) noexcept
|
static consteval FourCC fourcc_from_cstr(const char(&a)[5]) noexcept
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ namespace esxr {
|
|||||||
//
|
//
|
||||||
|
|
||||||
static constexpr auto esx_endianness = std::endian::little;
|
static constexpr auto esx_endianness = std::endian::little;
|
||||||
static constexpr std::streamsize header_size = 24;
|
static constexpr std::size_t header_size = 24;
|
||||||
static constexpr bool same_endianness = std::endian::native == esx_endianness;
|
|
||||||
|
|
||||||
template <typename T, size_t N>
|
template <typename T, size_t N>
|
||||||
concept packed = sizeof(T) == N;
|
concept packed = sizeof(T) == N;
|
||||||
@@ -19,8 +18,11 @@ concept packed = sizeof(T) == N;
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
concept packed_header = packed<T, static_cast<std::size_t>(header_size)>;
|
concept packed_header = packed<T, static_cast<std::size_t>(header_size)>;
|
||||||
|
|
||||||
|
template <typename T, std::endian e>
|
||||||
|
concept directly_serializable = (std::endian::native == e) && std::is_trivially_copyable_v<T> && std::is_standard_layout_v<T>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept directly_readable_header = packed_header<T> && same_endianness;
|
concept directly_readable_header = packed_header<T> && directly_serializable<T, esx_endianness>;
|
||||||
|
|
||||||
constexpr bool directly_readable = directly_readable_header<UnknownHeader> && directly_readable_header<GroupHeader> && directly_readable_header<RecordHeader>;
|
constexpr bool directly_readable = directly_readable_header<UnknownHeader> && directly_readable_header<GroupHeader> && directly_readable_header<RecordHeader>;
|
||||||
|
|
||||||
|
|||||||
@@ -4,36 +4,28 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
static constexpr auto esm_name = "Skyrim.esm";
|
static constexpr auto esx_name = "Skyrim.esm";
|
||||||
|
|
||||||
/* Propagated Exceptions:
|
|
||||||
* - std::bad_alloc
|
[[nodiscard]] std::filesystem::path esm_path(void)
|
||||||
* - std::filesystem::filesystem_error
|
|
||||||
* - std::ios_base::failure
|
|
||||||
*/
|
|
||||||
[[nodiscard]] static std::ifstream open_esm(void)
|
|
||||||
{
|
{
|
||||||
std::filesystem::path cwd = std::filesystem::current_path();
|
auto cwd = std::filesystem::current_path();
|
||||||
auto esm_path = cwd / esm_name;
|
return cwd / esx_name;
|
||||||
auto esm_fs = std::ifstream(esm_path, std::ios::binary);
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static std::ifstream open_esx(std::filesystem::path path)
|
||||||
|
{
|
||||||
|
auto esm_fs = std::ifstream(path, std::ios::binary);
|
||||||
if (esm_fs.fail())
|
if (esm_fs.fail())
|
||||||
throw std::ios_base::failure("Could not open the esm file for reading.");
|
throw std::ios_base::failure("Could not open the esm file for reading.");
|
||||||
return esm_fs;
|
return esm_fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_type(esxr::FourCC fcc)
|
|
||||||
{
|
|
||||||
auto type = esxr::fourcc_to_record_type(fcc).value();
|
|
||||||
auto name = esxr::record_type_to_name(type);
|
|
||||||
std::cout << name.value() << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
auto esm_fs = open_esm();
|
auto path = esm_path();
|
||||||
auto header_variant = esxr::read_header(esm_fs);
|
auto size = std::filesystem::file_size(path);
|
||||||
std::visit(utility::overloaded {
|
auto stream = open_esx(path);
|
||||||
[](const esxr::RecordHeader &h) { print_type(h.type); },
|
auto root = esxr::read_esx(stream, size);
|
||||||
[](const esxr::GroupHeader& h) { print_type(h.grup); },
|
return 0;
|
||||||
}, header_variant);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user