200 lines
6.4 KiB
C++

#include "harmony_signatures.hpp"
#include "sigscan.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace toon_boom_module::harmony {
namespace {
std::size_t count_forward(const std::byte* p, const std::byte* end, std::uint8_t value) {
std::size_t n = 0;
while (p < end && static_cast<std::uint8_t>(*p) == value) {
++n;
++p;
}
return n;
}
std::size_t count_backward(const std::byte* begin, const std::byte* p, std::uint8_t value) {
std::size_t n = 0;
while (p > begin && static_cast<std::uint8_t>(p[-1]) == value) {
++n;
--p;
}
return n;
}
bool looks_like_function_boundary(const std::byte* text_begin,
std::size_t text_size,
const std::byte* match_addr,
std::size_t pattern_size) {
const auto* text_end = text_begin + text_size;
if (match_addr < text_begin || match_addr + pattern_size > text_end) return false;
// For HarmonyPremium's SCR_ScriptRuntime_getEngine thunk, IDA shows:
// 48 8B 01 48 8B 40 28 C3 CC CC CC ...
// So: require a run of int3 padding immediately after the ret.
constexpr std::size_t kMinCcAfter = 4;
const auto* after = match_addr + pattern_size;
if (after >= text_end) return false;
const auto cc_after = count_forward(after, text_end, 0xCC);
if (cc_after < kMinCcAfter) return false;
// Optional: also prefer that the match is preceded by at least one 0xCC,
// unless it happens to be at the start of the section.
if (match_addr != text_begin) {
const auto cc_before = count_backward(text_begin, match_addr, 0xCC);
if (cc_before == 0) return false;
}
return true;
}
struct FunctionRange {
std::uintptr_t begin{};
std::uintptr_t end{};
};
std::optional<FunctionRange> function_range_from_unwind(HMODULE target_module, std::uintptr_t addr) {
if (!target_module) return std::nullopt;
DWORD64 image_base = 0;
const auto* rf = ::RtlLookupFunctionEntry(static_cast<DWORD64>(addr), &image_base, nullptr);
if (!rf || image_base == 0) return std::nullopt;
// BeginAddress/EndAddress are RVAs from image_base.
const auto begin = static_cast<std::uintptr_t>(image_base + rf->BeginAddress);
const auto end = static_cast<std::uintptr_t>(image_base + rf->EndAddress);
if (begin >= end) return std::nullopt;
return FunctionRange{begin, end};
}
} // namespace
std::optional<std::uintptr_t> find_SCR_ScriptRuntime_getEngine(HMODULE target_module) {
// Exact bytes from IDA at HarmonyPremium.exe:0x14082BCD0:
// 48 8B 01 48 8B 40 28 C3
constexpr std::string_view kPattern = "48 8B 01 48 8B 40 28 C3";
auto text = toon_boom_module::sigscan::get_pe_section(target_module, ".text");
if (!text) return std::nullopt;
const auto pat = toon_boom_module::sigscan::parse_ida_pattern(kPattern);
auto matches = toon_boom_module::sigscan::find_all(*text, pat);
if (matches.empty()) return std::nullopt;
// Filter for plausible function boundaries to reduce collisions with other
// identical byte sequences embedded in the middle of code.
std::vector<const std::byte*> filtered;
filtered.reserve(matches.size());
for (const auto* m : matches) {
if (looks_like_function_boundary(text->begin, text->size, m, pat.bytes.size())) {
filtered.push_back(m);
}
}
if (filtered.size() != 1) return std::nullopt;
return reinterpret_cast<std::uintptr_t>(filtered[0]);
}
std::optional<std::uintptr_t> find_SCR_ScriptManager_ctor(HMODULE target_module) {
// This is a mid-function signature extracted from HarmonyPremium.exe around:
// QString("___scriptManager___"); defineGlobalQObject(...)
// QString("include"); defineGlobalFunction(QS_include)
// QString("require"); defineGlobalFunction(QS_require)
//
// RIP-relative displacements and call targets are wildcarded.
//
// Source bytes were pulled from IDA around 0x14081FEE0.
constexpr std::string_view kPattern =
"48 8B 18 "
"48 8D 15 ?? ?? ?? ?? "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"90 "
"4C 8B C6 "
"48 8D 54 24 30 "
"48 8B CB "
"E8 ?? ?? ?? ?? "
"90 "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"48 8B 46 20 "
"48 8B 18 "
"48 8D 15 ?? ?? ?? ?? "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"90 "
"4C 8D 05 ?? ?? ?? ?? "
"48 8D 54 24 30 "
"48 8B CB "
"E8 ?? ?? ?? ?? "
"90 "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"48 8B 46 20 "
"48 8B 18 "
"48 8D 15 ?? ?? ?? ?? "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ?? "
"90 "
"4C 8D 05 ?? ?? ?? ?? "
"48 8D 54 24 30 "
"48 8B CB "
"E8 ?? ?? ?? ?? "
"90 "
"48 8D 4C 24 30 "
"FF 15 ?? ?? ?? ??";
auto text = toon_boom_module::sigscan::get_pe_section(target_module, ".text");
if (!text) return std::nullopt;
const auto pat = toon_boom_module::sigscan::parse_ida_pattern(kPattern);
auto hits = toon_boom_module::sigscan::find_all(*text, pat);
if (hits.empty()) return std::nullopt;
// Convert each hit to its containing function start via unwind info, and
// keep only plausible ctor-sized functions (~0x280 in the analyzed build).
constexpr std::size_t kMinSize = 0x200;
constexpr std::size_t kMaxSize = 0x400;
std::vector<std::uintptr_t> candidates;
candidates.reserve(hits.size());
for (const auto* hit : hits) {
const auto hit_addr = reinterpret_cast<std::uintptr_t>(hit);
auto fr = function_range_from_unwind(target_module, hit_addr);
if (!fr) continue;
const auto size = static_cast<std::size_t>(fr->end - fr->begin);
if (size < kMinSize || size > kMaxSize) continue;
// Ensure the function is inside .text.
const auto text_begin = reinterpret_cast<std::uintptr_t>(text->begin);
const auto text_end = text_begin + text->size;
if (fr->begin < text_begin || fr->end > text_end) continue;
candidates.push_back(fr->begin);
}
if (candidates.empty()) return std::nullopt;
std::sort(candidates.begin(), candidates.end());
candidates.erase(std::unique(candidates.begin(), candidates.end()), candidates.end());
if (candidates.size() != 1) return std::nullopt;
return candidates[0];
}
} // namespace toon_boom_module::harmony