diff --git a/CommonLibF4/include/REL/Relocation.h b/CommonLibF4/include/REL/Relocation.h index 79ad3ed0..89c7e4b5 100644 --- a/CommonLibF4/include/REL/Relocation.h +++ b/CommonLibF4/include/REL/Relocation.h @@ -161,6 +161,8 @@ namespace REL return func(std::forward(a_first), std::addressof(result), std::forward(a_rest)...); } + + std::optional sha512(std::span a_data); } inline constexpr std::uint8_t NOP = 0x90; @@ -626,6 +628,23 @@ namespace REL if (!_mmap.open(path)) { stl::report_and_fail(fmt::format("failed to open: {}", path)); } + if (version == REL::Version(1, 10, 980)) { + const auto sha = detail::sha512({ _mmap }); + if (!sha) { + stl::report_and_fail(fmt::format("failed to hash: {}", path)); + } + // Address bins are expected to be pre-sorted. This bin was released without being sorted, and will cause lookups to randomly fail. + if (*sha == "2AD60B95388F1B6E77A6F86F17BEB51D043CF95A341E91ECB2E911A393E45FE8156D585D2562F7B14434483D6E6652E2373B91589013507CABAE596C26A343F1"sv) { + stl::report_and_fail(fmt::format( + "The address bin you are using ({}) is corrupted. " + "Please go to the Nexus page for Address Library and redownload the file corresponding to version {}.{}.{}.{}", + path, + version[0], + version[1], + version[2], + version[3])); + } + } _id2offset = std::span{ reinterpret_cast(_mmap.data() + sizeof(std::uint64_t)), *reinterpret_cast(_mmap.data()) diff --git a/CommonLibF4/src/REL/Relocation.cpp b/CommonLibF4/src/REL/Relocation.cpp index 70a64f04..99b9e321 100644 --- a/CommonLibF4/src/REL/Relocation.cpp +++ b/CommonLibF4/src/REL/Relocation.cpp @@ -44,8 +44,93 @@ #include +#include + +#define NT_SUCCESS(a_status) (a_status >= 0) + +#include "F4SE/Logger.h" + namespace REL { + namespace log = F4SE::log; + + namespace detail + { + std::optional sha512(std::span a_data) + { + ::BCRYPT_ALG_HANDLE algorithm; + if (!NT_SUCCESS(::BCryptOpenAlgorithmProvider( + &algorithm, + BCRYPT_SHA512_ALGORITHM, + nullptr, + 0))) { + log::error("failed to open algorithm provider"); + return std::nullopt; + } + const stl::scope_exit delAlgorithm([&]() { + [[maybe_unused]] const auto success = NT_SUCCESS(::BCryptCloseAlgorithmProvider(algorithm, 0)); + assert(success); + }); + + ::BCRYPT_HASH_HANDLE hash; + if (!NT_SUCCESS(::BCryptCreateHash( + algorithm, + &hash, + nullptr, + 0, + nullptr, + 0, + 0))) { + log::error("failed to create hash"); + return std::nullopt; + } + const stl::scope_exit delHash([&]() { + [[maybe_unused]] const auto success = NT_SUCCESS(::BCryptDestroyHash(hash)); + assert(success); + }); + + if (!NT_SUCCESS(::BCryptHashData( + hash, + reinterpret_cast<::PUCHAR>(const_cast(a_data.data())), // does not modify contents of buffer + static_cast<::ULONG>(a_data.size()), + 0))) { + log::error("failed to hash data"); + return std::nullopt; + } + + ::DWORD hashLen = 0; + ::ULONG discard = 0; + if (!NT_SUCCESS(::BCryptGetProperty( + hash, + BCRYPT_HASH_LENGTH, + reinterpret_cast<::PUCHAR>(&hashLen), + sizeof(hashLen), + &discard, + 0))) { + log::error("failed to get property"); + return std::nullopt; + } + std::vector<::UCHAR> buffer(static_cast(hashLen)); + + if (!NT_SUCCESS(::BCryptFinishHash( + hash, + buffer.data(), + static_cast<::ULONG>(buffer.size()), + 0))) { + log::error("failed to finish hash"); + return std::nullopt; + } + + std::string result; + result.reserve(buffer.size() * 2); + for (const auto byte : buffer) { + result += fmt::format("{:02X}", byte); + } + + return { std::move(result) }; + } + } + void Module::load_segments() { auto dosHeader = reinterpret_cast(_base);