Open Al12rs opened 4 years ago
https://github.com/wrye-bash/wrye-bash/issues/489 has the compatibility table. The version is the second u32 in the file (with the first being the string BSA\0
).
Here's a simple standalone exe to implement this concept. I don't know how to make mo2 plugins, so 🤷
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <new>
#include <string>
#include <type_traits>
#include <boost/filesystem.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/outcome.hpp>
#define SPDLOG_COMPILED_LIB
#include <spdlog/spdlog.h>
namespace archive_checker
{
namespace outcome = BOOST_OUTCOME_V2_NAMESPACE;
enum class error_code : int
{
not_a_file,
invalid_file_type,
file_too_small,
open_failure,
unrecognized_format,
};
}
#if 0 // boost
#include <boost/system/error_code.hpp>
namespace archive_checker
{
namespace error = boost::system;
}
namespace boost
{
namespace system
{
template <>
struct is_error_code_enum<archive_checker::error_code> :
public std::true_type
{
public:
};
}
}
#else // std
#include <system_error>
namespace archive_checker
{
namespace error = std;
}
namespace std
{
template <>
struct is_error_code_enum<archive_checker::error_code> :
public std::true_type
{
public:
};
}
#endif
namespace archive_checker
{
class error_category :
public error::error_category
{
private:
using super = error::error_category;
public:
const char* name() const noexcept final { return "archive_checker::error_code"; }
error::error_condition default_error_condition(int a_code) const noexcept final
{
switch (static_cast<error_code>(a_code)) {
case error_code::not_a_file:
case error_code::invalid_file_type:
return make_error_condition(error::errc::invalid_argument);
case error_code::file_too_small:
case error_code::open_failure:
case error_code::unrecognized_format:
return make_error_condition(error::errc::io_error);
default:
return error::error_condition(a_code, *this);
}
}
std::string message(int a_condition) const final
{
switch (static_cast<error_code>(a_condition)) {
case error_code::not_a_file:
return "file path does not indicate a regular file";
case error_code::invalid_file_type:
return "file path does not contain a known extension";
case error_code::file_too_small:
return "the file is too small to contain the necessary header";
case error_code::open_failure:
return "failed to map the file into memory";
case error_code::unrecognized_format:
return "the file contains an unrecognized format";
default:
return "unknown error code";
}
}
using super::message;
};
inline error::error_code make_error_code(error_code a_error)
{
static const error_category e;
return { static_cast<int>(a_error), e };
}
enum class archive_type
{
tes3,
tes4,
sse,
fo4,
};
namespace tes3
{
struct header_t
{
std::uint32_t version;
};
}
namespace tes4
{
constexpr inline std::array MAGIC{ 'B', 'S', 'A', '\0' };
struct header_t
{
char magic[4];
std::uint32_t version;
};
}
namespace fo4
{
constexpr inline std::array MAGIC{ 'B', 'T', 'D', 'X' };
struct header_t
{
char magic[4];
std::uint32_t version;
};
}
union header_t
{
tes3::header_t tes3;
tes4::header_t tes4;
fo4::header_t fo4;
};
namespace util
{
template <class T>
[[nodiscard]] T start_lifetime_as(const std::byte* a_src) noexcept(std::is_nothrow_copy_constructible_v<T>)
{
std::array<std::byte, sizeof(T)> buf;
return *std::launder(
static_cast<T*>(
std::memcpy(
buf.data(), a_src, buf.size())));
}
template <std::size_t N>
[[nodiscard]] bool equal(const char (&a_lhs)[N], const std::array<char, N>& a_rhs)
{
return std::equal(a_lhs, a_lhs + N, a_rhs.begin(), a_rhs.end());
}
}
[[nodiscard]] inline outcome::result<archive_type, error::error_code> get_archive_type(const boost::filesystem::path& a_path)
{
if (!boost::filesystem::is_regular_file(a_path)) {
return error_code::not_a_file;
}
if (const auto ext = a_path.extension();
ext != ".bsa" && ext != ".ba2") {
return error_code::invalid_file_type;
}
if (const auto size = boost::filesystem::file_size(a_path);
size < sizeof(header_t)) {
return error_code::file_too_small;
}
const boost::iostreams::mapped_file_source file(a_path, sizeof(header_t));
if (!file.is_open()) {
return error_code::open_failure;
}
const auto header =
util::start_lifetime_as<header_t>(
reinterpret_cast<const std::byte*>(file.data()));
if (util::equal(header.fo4.magic, fo4::MAGIC)) {
switch (header.fo4.version) {
case 1:
return archive_type::fo4;
default:
return error_code::unrecognized_format;
}
} else if (util::equal(header.tes4.magic, tes4::MAGIC)) {
switch (header.tes4.version) {
case 103:
case 104:
return archive_type::tes4;
case 105:
return archive_type::sse;
default:
return error_code::unrecognized_format;
}
} else {
switch (header.tes3.version) {
case 256:
return archive_type::tes3;
default:
return error_code::unrecognized_format;
}
}
}
}
int main(int a_argc, char* a_argv[])
{
using namespace archive_checker;
spdlog::set_pattern("[%^%l%$] %v");
if (a_argc <= 1) {
spdlog::info("please pass file paths as arguments");
return EXIT_FAILURE;
}
boost::filesystem::path path;
for (int i = 1; i < a_argc; ++i) {
path = a_argv[i];
const auto result = get_archive_type(path);
if (result) {
switch (result.value()) {
case archive_type::tes3:
spdlog::info("tes3");
break;
case archive_type::tes4:
spdlog::info("tes4");
break;
case archive_type::sse:
spdlog::info("sse");
break;
case archive_type::fo4:
spdlog::info("fo4");
break;
default:
spdlog::error("unhandled type");
break;
}
} else {
spdlog::error(result.error().message());
}
}
#ifdef NDEBUG
std::cin.get();
#endif
return EXIT_SUCCESS;
}
Basically this: https://www.nexusmods.com/site/mods/126