Implemented reader. Appears to work, but further testing is required.

This commit is contained in:
2022-10-27 19:43:29 +11:00
parent 8a3be3b67f
commit e3c1389791
4 changed files with 129 additions and 36 deletions

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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>;

View File

@@ -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);
} }