mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
37218e7fe0
As noted in #567, a filesystem path that contains non-ascii will break a lot if using a clang toolchain. Luckily, fmtlib has a lossy utf8 convert when you use it to print a path (after including `fmt/std.h`). The vast majority of this diff is just removing `.string()` from paths inside loggings calls. There are some callsites I _didn't_ touch, mainly the options, because it would be an ABI break to change those to be wide strings and I cbf looking into settings upgrades. There are also some spots (avs mountpath remapping, for example) where the path is guaranteed to be ascii, so I didn't modify them. ImGui doesn't appear to easily support wide strings (I mean, surely it does, but I'm not gonna look too far into it) so I mostly just left those alone too, with a few spots modified to re-use fmtlib's lossy utf8. Some of the changes are basically never gonna be hit IRL, like who would put a file with a non-ascii _extension_ along with their modules? But the diff is (I hope) pretty easy to validate as OK. Testing has been somewhat minimal, I fired up the GCC build of spice2x in a dodgy folder name, got mojibake (running via wine in linux so take that as you will), ran the unmodified clang spice and crashed the same way the reporter did. After modification, I get the exact same mojibake so I assume if the terminal enjoys utf8 it'll display OK. Claude (only used for review) thinks the commit is fine but is annoyed that I use `fmt::detail` in the appdata censoring, which is part of the private API; personally I don't care because it's pretty stable.
307 lines
9.1 KiB
C++
307 lines
9.1 KiB
C++
#include "sigscan.h"
|
|
|
|
#include <format>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "util/logging.h"
|
|
#include "util/memutils.h"
|
|
#include "util/utils.h"
|
|
|
|
intptr_t find_pattern(std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern,
|
|
const char *mask, intptr_t offset, intptr_t usage)
|
|
{
|
|
|
|
// build pattern
|
|
std::vector<std::pair<uint8_t, bool>> pattern_vector;
|
|
size_t mask_size = strlen(mask);
|
|
for (size_t i = 0; i < mask_size; i++) {
|
|
pattern_vector.emplace_back(pattern[i], mask[i] == 'X');
|
|
}
|
|
|
|
// the scan loop
|
|
auto data_begin = data.begin();
|
|
auto cur_usage = 0;
|
|
while (true) {
|
|
|
|
// search for the pattern
|
|
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(),
|
|
[&](uint8_t c, std::pair<uint8_t, bool> pat) {
|
|
return (!pat.second) || c == pat.first;
|
|
});
|
|
|
|
// check for a match
|
|
if (search_result != data.end()) {
|
|
|
|
// return the result if we hit the usage count
|
|
if (cur_usage == usage) {
|
|
return (std::distance(data.begin(), search_result) + base) + offset;
|
|
}
|
|
|
|
// increment the found count
|
|
++cur_usage;
|
|
data_begin = ++search_result;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
intptr_t find_pattern(HMODULE module, const uint8_t *pattern, const char *mask,
|
|
intptr_t offset, intptr_t result_usage)
|
|
{
|
|
// get module information
|
|
MODULEINFO module_info {};
|
|
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
|
|
return 0;
|
|
}
|
|
|
|
auto size = static_cast<size_t>(module_info.SizeOfImage);
|
|
|
|
try {
|
|
|
|
// copy data
|
|
std::vector<uint8_t> data(size);
|
|
memcpy(data.data(), module_info.lpBaseOfDll, size);
|
|
|
|
// find pattern
|
|
return find_pattern(
|
|
data,
|
|
reinterpret_cast<intptr_t>(module_info.lpBaseOfDll),
|
|
pattern,
|
|
mask,
|
|
offset,
|
|
result_usage);
|
|
} catch (const std::bad_alloc &e) {
|
|
log_warning("sigscan", "failed to allocate buffer of size {} for image data", size);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
intptr_t find_pattern(HMODULE module, const std::string &pattern, const char *mask,
|
|
intptr_t offset, intptr_t result_usage)
|
|
{
|
|
std::string pattern_str(pattern);
|
|
auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2);
|
|
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
|
|
log_warning("sigscan", "hex2bin failed");
|
|
return false;
|
|
}
|
|
|
|
return find_pattern(module, pattern_bin.get(), mask, offset, result_usage);
|
|
}
|
|
|
|
///
|
|
|
|
intptr_t find_pattern_from(std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern,
|
|
const char *mask, intptr_t offset, intptr_t usage, intptr_t start_from)
|
|
{
|
|
|
|
// build pattern
|
|
std::vector<std::pair<uint8_t, bool>> pattern_vector;
|
|
size_t mask_size = strlen(mask);
|
|
for (size_t i = 0; i < mask_size; i++) {
|
|
pattern_vector.emplace_back(pattern[i], mask[i] == 'X');
|
|
}
|
|
|
|
// the scan loop
|
|
auto data_begin = data.begin();
|
|
std::advance(data_begin, start_from);
|
|
auto cur_usage = 0;
|
|
while (true) {
|
|
|
|
// search for the pattern
|
|
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(),
|
|
[&](uint8_t c, std::pair<uint8_t, bool> pat) {
|
|
return (!pat.second) || c == pat.first;
|
|
});
|
|
|
|
// check for a match
|
|
if (search_result != data.end()) {
|
|
|
|
// return the result if we hit the usage count
|
|
if (cur_usage == usage) {
|
|
return (std::distance(data.begin(), search_result) + base) + offset;
|
|
}
|
|
|
|
// increment the found count
|
|
++cur_usage;
|
|
data_begin = ++search_result;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
intptr_t find_pattern_from(HMODULE module, const uint8_t *pattern, const char *mask,
|
|
intptr_t offset, intptr_t result_usage, intptr_t start_from)
|
|
{
|
|
// get module information
|
|
MODULEINFO module_info {};
|
|
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
|
|
return 0;
|
|
}
|
|
|
|
auto size = static_cast<size_t>(module_info.SizeOfImage);
|
|
|
|
try {
|
|
|
|
// copy data
|
|
std::vector<uint8_t> data(size);
|
|
memcpy(data.data(), module_info.lpBaseOfDll, size);
|
|
|
|
// find pattern
|
|
return find_pattern_from(
|
|
data,
|
|
reinterpret_cast<intptr_t>(module_info.lpBaseOfDll),
|
|
pattern,
|
|
mask,
|
|
offset,
|
|
result_usage,
|
|
start_from);
|
|
} catch (const std::bad_alloc &e) {
|
|
log_warning("sigscan", "failed to allocate buffer of size {} for image data", size);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
intptr_t find_pattern_from(HMODULE module, const std::string &pattern, const char *mask,
|
|
intptr_t offset, intptr_t result_usage, intptr_t start_from)
|
|
{
|
|
std::string pattern_str(pattern);
|
|
auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2);
|
|
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
|
|
log_warning("sigscan", "hex2bin failed");
|
|
return false;
|
|
}
|
|
|
|
return find_pattern_from(module, pattern_bin.get(), mask, offset, result_usage, start_from);
|
|
}
|
|
|
|
|
|
intptr_t replace_pattern(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t offset,
|
|
intptr_t usage, const uint8_t *replace_data, const char *replace_mask)
|
|
{
|
|
|
|
// find result
|
|
auto result = find_pattern(module, pattern, mask, offset, usage);
|
|
|
|
// check result
|
|
if (!result) {
|
|
return 0;
|
|
}
|
|
|
|
// unprotect memory
|
|
auto replace_mask_len = strlen(replace_mask);
|
|
memutils::VProtectGuard guard((void *) result, replace_mask_len);
|
|
|
|
// replace data
|
|
for (size_t i = 0; i < replace_mask_len; i++) {
|
|
if (replace_mask[i] == 'X') {
|
|
*((unsigned char *) (result + i)) = replace_data[i];
|
|
}
|
|
}
|
|
|
|
// success
|
|
return result;
|
|
}
|
|
|
|
intptr_t replace_pattern(HMODULE module, const std::string &signature,
|
|
const std::string &replacement, intptr_t offset, intptr_t usage)
|
|
{
|
|
// build pattern
|
|
std::string pattern_str(signature);
|
|
strreplace(pattern_str, "??", "00");
|
|
auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2);
|
|
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
|
|
return 0;
|
|
}
|
|
|
|
// build signature mask
|
|
std::string signature_mask;
|
|
signature_mask.reserve(signature.size() / 2);
|
|
for (size_t i = 0; i < signature.length(); i += 2) {
|
|
if (signature[i] == '?') {
|
|
if (signature[i + 1] == '?') {
|
|
signature_mask += '?';
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
signature_mask += 'X';
|
|
}
|
|
}
|
|
|
|
// build replace data
|
|
std::string replace_data_str(replacement);
|
|
strreplace(replace_data_str, "??", "00");
|
|
auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2);
|
|
if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) {
|
|
return 0;
|
|
}
|
|
|
|
// build replace mask
|
|
std::string replace_mask;
|
|
replace_mask.reserve(replacement.size() / 2);
|
|
for (size_t i = 0; i < replacement.length(); i += 2) {
|
|
if (replacement[i] == '?') {
|
|
if (replacement[i + 1] == '?') {
|
|
replace_mask += '?';
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
replace_mask += 'X';
|
|
}
|
|
}
|
|
|
|
// do the replacement
|
|
return replace_pattern(
|
|
module,
|
|
pattern_bin.get(),
|
|
signature_mask.c_str(),
|
|
offset,
|
|
usage,
|
|
replace_data_bin.get(),
|
|
replace_mask.c_str()
|
|
);
|
|
}
|
|
|
|
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) {
|
|
std::ifstream file(dll_path, std::ios::binary);
|
|
if (!file) {
|
|
log_warning("sigscan", "Failed to open file: {}", dll_path);
|
|
return false;
|
|
}
|
|
|
|
// read the DOS header
|
|
IMAGE_DOS_HEADER dos_header;
|
|
file.read(reinterpret_cast<char*>(&dos_header), sizeof(dos_header));
|
|
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) {
|
|
log_warning("sigscan", "Invalid DOS signature: {}", dll_path);
|
|
return false;
|
|
}
|
|
|
|
// move to the NT headers
|
|
file.seekg(dos_header.e_lfanew);
|
|
|
|
// read the NT headers
|
|
IMAGE_NT_HEADERS nt_headers;
|
|
file.read(reinterpret_cast<char*>(&nt_headers), sizeof(nt_headers));
|
|
if (nt_headers.Signature != IMAGE_NT_SIGNATURE) {
|
|
log_warning("sigscan", "Invalid NT signature: {}", dll_path);
|
|
return false;
|
|
}
|
|
|
|
// get the TimeDateStamp and AddressOfEntryPoint from the file header
|
|
*time_date_stamp = nt_headers.FileHeader.TimeDateStamp;
|
|
*address_of_entry_point = nt_headers.OptionalHeader.AddressOfEntryPoint;
|
|
|
|
return true;
|
|
}
|