Skip to content

Commit

Permalink
Cleanup registry parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
momo5502 committed Nov 3, 2024
1 parent 17db05a commit 7c21325
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 251 deletions.
244 changes: 244 additions & 0 deletions src/windows-emulator/registry/hive_parser.cpp
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))
{
}
Loading

0 comments on commit 7c21325

Please sign in to comment.