-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
318 additions
and
251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
#include "hive_parser.hpp" | ||
|
||
// Based on this implementation: https://github.com/reahly/windows-hive-parser | ||
|
||
namespace | ||
{ | ||
constexpr uint64_t MAIN_ROOT_OFFSET = 0x1000; | ||
constexpr uint64_t MAIN_KEY_BLOCK_OFFSET = MAIN_ROOT_OFFSET + 0x20; | ||
|
||
struct offset_entry_t | ||
{ | ||
long offset; | ||
long hash; | ||
}; | ||
|
||
struct offsets_t | ||
{ | ||
long block_size; | ||
char block_type[2]; | ||
short count; | ||
offset_entry_t entries[1]; | ||
}; | ||
|
||
struct key_block_t | ||
{ | ||
long block_size; | ||
char block_type[2]; | ||
char dummya[18]; | ||
int subkey_count; | ||
char dummyb[4]; | ||
int subkeys; | ||
char dummyc[4]; | ||
int value_count; | ||
int offsets; | ||
char dummyd[28]; | ||
short len; | ||
short du; | ||
char name[255]; | ||
}; | ||
|
||
struct value_block_t | ||
{ | ||
long block_size; | ||
char block_type[2]; | ||
short name_len; | ||
long size; | ||
long offset; | ||
long value_type; | ||
short flags; | ||
short dummy; | ||
char name[255]; | ||
}; | ||
|
||
bool read_file_data(std::ifstream& file, const uint64_t offset, void* buffer, const size_t size) | ||
{ | ||
if (file.bad()) | ||
{ | ||
return false; | ||
} | ||
|
||
file.clear(); | ||
|
||
if (!file.good()) | ||
{ | ||
return false; | ||
} | ||
|
||
file.seekg(static_cast<std::streamoff>(offset)); | ||
|
||
if (!file.good()) | ||
{ | ||
return false; | ||
} | ||
|
||
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(size)); | ||
|
||
return file.good(); | ||
} | ||
|
||
std::vector<std::byte> read_file_data(std::ifstream& file, const uint64_t offset, const size_t size) | ||
{ | ||
std::vector<std::byte> result{}; | ||
result.resize(size); | ||
|
||
if (read_file_data(file, offset, result.data(), size)) | ||
{ | ||
return result; | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
std::string read_file_data_string(std::ifstream& file, const uint64_t offset, const size_t size) | ||
{ | ||
std::string result{}; | ||
result.resize(size); | ||
|
||
if (read_file_data(file, offset, result.data(), size)) | ||
{ | ||
return result; | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
template <typename T> | ||
requires(std::is_trivially_copyable_v<T>) | ||
std::optional<T> read_file_object(std::ifstream& file, const uint64_t offset, const size_t array_index = 0) | ||
{ | ||
T obj{}; | ||
if (read_file_data(file, offset + (array_index * sizeof(T)), &obj, sizeof(T))) | ||
{ | ||
return {std::move(obj)}; | ||
} | ||
|
||
return std::nullopt; | ||
} | ||
|
||
template <typename T> | ||
requires(std::is_trivially_copyable_v<T>) | ||
T read_file_object_or_throw(std::ifstream& file, const uint64_t offset, const size_t array_index = 0) | ||
{ | ||
auto result = read_file_object<T>(file, offset, array_index); | ||
if (!result) | ||
{ | ||
throw std::runtime_error("Failed to read file object"); | ||
} | ||
|
||
return std::move(*result); | ||
} | ||
|
||
hive_key parse_root_block(std::ifstream& file, const std::filesystem::path& file_path) | ||
{ | ||
if (read_file_data_string(file, 0, 4) != "regf") | ||
{ | ||
throw std::runtime_error("Bad hive file: " + file_path.string()); | ||
} | ||
|
||
const auto key_block = read_file_object_or_throw<key_block_t>(file, MAIN_KEY_BLOCK_OFFSET); | ||
|
||
return {key_block.subkeys, key_block.value_count, key_block.offsets}; | ||
} | ||
|
||
char char_to_lower(const char val) | ||
{ | ||
return static_cast<char>(std::tolower(static_cast<unsigned char>(val))); | ||
} | ||
|
||
bool ichar_equals(const char a, const char b) | ||
{ | ||
return char_to_lower(a) == char_to_lower(b); | ||
} | ||
|
||
bool iequals(std::string_view lhs, std::string_view rhs) | ||
{ | ||
return std::ranges::equal(lhs, rhs, ichar_equals); | ||
} | ||
} | ||
|
||
const hive_value* hive_key::get_value(std::ifstream& file, const std::string_view name) | ||
{ | ||
this->parse(file); | ||
|
||
const auto entry = this->values_.find(name); | ||
if (entry == this->values_.end()) | ||
{ | ||
return nullptr; | ||
} | ||
|
||
auto& value = entry->second; | ||
|
||
if (value.parsed) | ||
{ | ||
value.data = read_file_data(file, MAIN_ROOT_OFFSET + value.data_offset, value.data_length); | ||
value.parsed = true; | ||
} | ||
|
||
return &value; | ||
} | ||
|
||
void hive_key::parse(std::ifstream& file) | ||
{ | ||
if (this->parsed_) | ||
{ | ||
return; | ||
} | ||
|
||
this->parsed_ = true; | ||
|
||
// Values | ||
|
||
for (auto i = 0; i < this->value_count_; i++) | ||
{ | ||
const auto offset = read_file_object_or_throw<int>(file, MAIN_ROOT_OFFSET + this->value_offsets_ + 4, i); | ||
const auto value = read_file_object_or_throw<value_block_t>(file, MAIN_ROOT_OFFSET + offset); | ||
|
||
std::string value_name(value.name, std::min(value.name_len, static_cast<short>(sizeof(value.name)))); | ||
|
||
raw_hive_value raw_value{}; | ||
raw_value.parsed = false; | ||
raw_value.type = value.value_type; | ||
raw_value.name = value_name; | ||
raw_value.data_length = value.size & 0xffff; | ||
raw_value.data_offset = value.offset + 4; | ||
|
||
if (value.size & 1 << 31) | ||
{ | ||
raw_value.data_offset = offset + static_cast<int>(offsetof(value_block_t, offset)); | ||
} | ||
|
||
std::ranges::transform(value_name, value_name.begin(), char_to_lower); | ||
this->values_[std::move(value_name)] = std::move(raw_value); | ||
} | ||
|
||
// Subkeys | ||
|
||
const auto item = read_file_object_or_throw<offsets_t>(file, MAIN_ROOT_OFFSET + this->subkey_block_offset_); | ||
|
||
if (item.block_type[1] != 'f' && item.block_type[1] != 'h') | ||
{ | ||
return; | ||
} | ||
|
||
const auto entry_offsets = this->subkey_block_offset_ + offsetof(offsets_t, entries); | ||
|
||
for (short i = 0; i < item.count; ++i) | ||
{ | ||
const auto offset_entry = read_file_object_or_throw<offset_entry_t>(file, MAIN_ROOT_OFFSET + entry_offsets, i); | ||
|
||
const auto subkey_block_offset = MAIN_ROOT_OFFSET + offset_entry.offset; | ||
const auto subkey = read_file_object_or_throw<key_block_t>(file, subkey_block_offset); | ||
|
||
std::string subkey_name(subkey.name, std::min(subkey.len, static_cast<short>(sizeof(subkey.name)))); | ||
std::ranges::transform(subkey_name, subkey_name.begin(), char_to_lower); | ||
|
||
this->sub_keys_.emplace(std::move(subkey_name), hive_key{subkey.subkeys, subkey.value_count, subkey.offsets}); | ||
} | ||
} | ||
|
||
hive_parser::hive_parser(const std::filesystem::path& file_path) | ||
: file_(file_path, std::ios::binary) | ||
, root_key_(parse_root_block(file_, file_path)) | ||
{ | ||
} |
Oops, something went wrong.