mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-10-07 16:30:01 +08:00
Initial commit
This commit is contained in:
108
src/main.cpp
Normal file
108
src/main.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
void generate_header_for_type_scope(const sdk::CSchemaSystemTypeScope* type_scope) {
|
||||
if (type_scope == nullptr)
|
||||
return;
|
||||
|
||||
const std::string output_file_name = std::format("generated/{}.hpp", type_scope->get_module_name());
|
||||
|
||||
std::fstream output(output_file_name, std::ios::out);
|
||||
|
||||
output << "#pragma once\n\n#include <cstddef>\n\n";
|
||||
|
||||
for (const sdk::CSchemaType_DeclaredClass* declared_class : type_scope->get_declared_classes()) {
|
||||
if (declared_class == nullptr)
|
||||
continue;
|
||||
|
||||
const sdk::CSchemaClassInfo* class_info = type_scope->find_declared_class(declared_class->get_class_name());
|
||||
|
||||
if (class_info == nullptr)
|
||||
continue;
|
||||
|
||||
output << "namespace " << declared_class->get_class_name() << " {\n";
|
||||
|
||||
for (const sdk::SchemaClassFieldData_t* field : class_info->get_fields()) {
|
||||
if (field == nullptr)
|
||||
continue;
|
||||
|
||||
output << " constexpr std::ptrdiff_t " << field->get_name() << " = 0x" << std::hex << field->get_offset() << ";\n";
|
||||
}
|
||||
|
||||
output << "}\n\n";
|
||||
|
||||
spdlog::info(" > generated header file for {}", declared_class->get_class_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void generate_json_for_type_scope(const sdk::CSchemaSystemTypeScope* type_scope) {
|
||||
if (type_scope == nullptr)
|
||||
return;
|
||||
|
||||
const std::string output_file_name = std::format("generated/{}.json", type_scope->get_module_name());
|
||||
|
||||
std::fstream output(output_file_name, std::ios::out);
|
||||
|
||||
nlohmann::json json;
|
||||
|
||||
for (const sdk::CSchemaType_DeclaredClass* declared_class : type_scope->get_declared_classes()) {
|
||||
if (declared_class == nullptr)
|
||||
continue;
|
||||
|
||||
const sdk::CSchemaClassInfo* class_info = type_scope->find_declared_class(declared_class->get_class_name());
|
||||
|
||||
if (class_info == nullptr)
|
||||
continue;
|
||||
|
||||
for (const sdk::SchemaClassFieldData_t* field : class_info->get_fields()) {
|
||||
if (field == nullptr)
|
||||
continue;
|
||||
|
||||
json[declared_class->get_class_name()][field->get_name()] = field->get_offset();
|
||||
}
|
||||
|
||||
spdlog::info(" > generated json file for {}", declared_class->get_class_name().c_str());
|
||||
}
|
||||
|
||||
output << json.dump(4);
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!std::filesystem::exists("generated"))
|
||||
std::filesystem::create_directory("generated");
|
||||
|
||||
if (!process::attach("cs2.exe")) {
|
||||
spdlog::error("failed to attach to process.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
spdlog::info("attached to process!");
|
||||
|
||||
const auto schema_system = sdk::CSchemaSystem::get();
|
||||
|
||||
if (schema_system == nullptr) {
|
||||
spdlog::error("failed to get schema system.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
spdlog::info("schema system: {:#x}", reinterpret_cast<std::uint64_t>(schema_system));
|
||||
|
||||
for (const sdk::CSchemaSystemTypeScope* type_scope : schema_system->get_type_scopes()) {
|
||||
spdlog::info("generating files for {}...", type_scope->get_module_name().c_str());
|
||||
|
||||
generate_header_for_type_scope(type_scope);
|
||||
generate_json_for_type_scope(type_scope);
|
||||
}
|
||||
|
||||
spdlog::info("done!");
|
||||
|
||||
return 0;
|
||||
}
|
164
src/process.cpp
Normal file
164
src/process.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "process.hpp"
|
||||
#include "base/safe_handle.hpp"
|
||||
|
||||
#include <charconv>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <TlHelp32.h>
|
||||
|
||||
namespace process {
|
||||
std::uint32_t process_id = 0;
|
||||
|
||||
base::SafeHandle process_handle;
|
||||
|
||||
namespace detail {
|
||||
std::optional<std::uint32_t> get_process_id_by_name(const std::string_view process_name) noexcept {
|
||||
const base::SafeHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
|
||||
|
||||
if (snapshot.get() == INVALID_HANDLE_VALUE)
|
||||
return std::nullopt;
|
||||
|
||||
PROCESSENTRY32 process_entry = {};
|
||||
|
||||
process_entry.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
for (Process32First(snapshot.get(), &process_entry); Process32Next(snapshot.get(), &process_entry);) {
|
||||
if (std::string_view(process_entry.szExeFile) == process_name)
|
||||
return process_entry.th32ProcessID;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool attach(const std::string_view process_name) {
|
||||
process_id = detail::get_process_id_by_name(process_name).value_or(0);
|
||||
|
||||
if (process_id == 0)
|
||||
return false;
|
||||
|
||||
process_handle = base::SafeHandle(OpenProcess(PROCESS_ALL_ACCESS, 0, process_id));
|
||||
|
||||
return process_handle.get() != nullptr;
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t> find_pattern(const std::string_view module_name, const std::string_view pattern) noexcept {
|
||||
constexpr auto pattern_to_bytes = [](const std::string_view pattern) -> std::vector<std::int32_t> {
|
||||
std::vector<std::int32_t> bytes;
|
||||
|
||||
for (std::size_t i = 0; i < pattern.size(); ++i) {
|
||||
if (pattern[i] == ' ')
|
||||
continue;
|
||||
|
||||
if (pattern[i] == '?') {
|
||||
bytes.push_back(-1);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 < pattern.size()) {
|
||||
std::int32_t value = 0;
|
||||
|
||||
if (const auto [ptr, ec] = std::from_chars(pattern.data() + i, pattern.data() + i + 2, value, 16); ec == std::errc())
|
||||
bytes.push_back(value);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const std::optional<std::uintptr_t> module_base = get_module_base(module_name);
|
||||
|
||||
if (!module_base.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
const auto headers = std::make_unique<std::uint8_t[]>(0x1000);
|
||||
|
||||
if (!read_memory(module_base.value(), headers.get(), 0x1000))
|
||||
return std::nullopt;
|
||||
|
||||
const auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(headers.get());
|
||||
|
||||
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
|
||||
return std::nullopt;
|
||||
|
||||
const auto nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(headers.get() + dos_header->e_lfanew);
|
||||
|
||||
if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
|
||||
return std::nullopt;
|
||||
|
||||
const std::uint32_t module_size = nt_headers->OptionalHeader.SizeOfImage;
|
||||
|
||||
const auto module_data = std::make_unique<std::uint8_t[]>(module_size);
|
||||
|
||||
if (!read_memory(module_base.value(), module_data.get(), module_size))
|
||||
return std::nullopt;
|
||||
|
||||
const std::vector<std::int32_t> pattern_bytes = pattern_to_bytes(pattern);
|
||||
|
||||
for (std::size_t i = 0; i < module_size - pattern.size(); ++i) {
|
||||
bool found = true;
|
||||
|
||||
for (std::size_t j = 0; j < pattern_bytes.size(); ++j) {
|
||||
if (module_data[i + j] != pattern_bytes[j] && pattern_bytes[j] != -1) {
|
||||
found = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
return module_base.value() + i;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t> get_module_base(const std::string_view module_name) noexcept {
|
||||
const base::SafeHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process_id));
|
||||
|
||||
if (snapshot.get() == INVALID_HANDLE_VALUE)
|
||||
return std::nullopt;
|
||||
|
||||
MODULEENTRY32 module_entry = {};
|
||||
|
||||
module_entry.dwSize = sizeof(MODULEENTRY32);
|
||||
|
||||
for (Module32First(snapshot.get(), &module_entry); Module32Next(snapshot.get(), &module_entry);) {
|
||||
if (_stricmp(module_entry.szModule, module_name.data()) == 0)
|
||||
return reinterpret_cast<std::uintptr_t>(module_entry.modBaseAddr);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t> resolve_rip_relative_address(const std::uintptr_t address) noexcept {
|
||||
const auto displacement = read_memory<std::int32_t>(address + 0x3);
|
||||
|
||||
return address + displacement + 0x7;
|
||||
}
|
||||
|
||||
bool read_memory(const std::uintptr_t address, void* buffer, const std::size_t size) noexcept {
|
||||
return ReadProcessMemory(process_handle.get(), reinterpret_cast<LPCVOID>(address), buffer, size, nullptr);
|
||||
}
|
||||
|
||||
bool write_memory(const std::uintptr_t address, const void* buffer, const std::size_t size) noexcept {
|
||||
return WriteProcessMemory(process_handle.get(), reinterpret_cast<LPVOID>(address), buffer, size, nullptr);
|
||||
}
|
||||
|
||||
std::string read_string(const std::uintptr_t address, const std::size_t length) noexcept {
|
||||
std::vector<char> buffer(length);
|
||||
|
||||
if (!read_memory(address, buffer.data(), length))
|
||||
return {};
|
||||
|
||||
if (const auto it = std::ranges::find(buffer, '\0'); it != buffer.end())
|
||||
buffer.resize(std::distance(buffer.begin(), it));
|
||||
|
||||
return { buffer.begin(), buffer.end() };
|
||||
}
|
||||
}
|
11
src/sdk/c_schema_class_field_data.cpp
Normal file
11
src/sdk/c_schema_class_field_data.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
std::string SchemaClassFieldData_t::get_name() const noexcept {
|
||||
return process::read_string(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this)), 64);
|
||||
}
|
||||
|
||||
std::uint16_t SchemaClassFieldData_t::get_offset() const noexcept {
|
||||
return process::read_memory<std::uint16_t>(reinterpret_cast<std::uint64_t>(this) + 0x10);
|
||||
}
|
||||
}
|
22
src/sdk/c_schema_class_info.cpp
Normal file
22
src/sdk/c_schema_class_info.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
std::uint16_t CSchemaClassInfo::get_fields_count() const noexcept {
|
||||
return process::read_memory<std::uint16_t>(reinterpret_cast<std::uint64_t>(this) + 0x1C);
|
||||
}
|
||||
|
||||
std::vector<SchemaClassFieldData_t*> CSchemaClassInfo::get_fields() const noexcept {
|
||||
std::vector<SchemaClassFieldData_t*> fields;
|
||||
|
||||
for (std::size_t i = 0; i < get_fields_count(); ++i) {
|
||||
const auto field = reinterpret_cast<SchemaClassFieldData_t*>(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x28) + (i * 0x20));
|
||||
|
||||
if (field == nullptr)
|
||||
continue;
|
||||
|
||||
fields.push_back(field);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
28
src/sdk/c_schema_system.cpp
Normal file
28
src/sdk/c_schema_system.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "sdk/c_schema_system.hpp"
|
||||
#include "process.hpp"
|
||||
|
||||
namespace sdk {
|
||||
CSchemaSystem* CSchemaSystem::get() noexcept {
|
||||
std::optional<std::uintptr_t> address = process::find_pattern("schemasystem.dll", "48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 83 EC 28");
|
||||
|
||||
if (!address.has_value())
|
||||
return nullptr;
|
||||
|
||||
address = process::resolve_rip_relative_address(address.value());
|
||||
|
||||
if (!address.has_value())
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<CSchemaSystem*>(address.value());
|
||||
}
|
||||
|
||||
std::vector<CSchemaSystemTypeScope*> CSchemaSystem::get_type_scopes() const noexcept {
|
||||
std::vector<CSchemaSystemTypeScope*> type_scopes;
|
||||
|
||||
type_scopes.resize(process::read_memory<std::uint32_t>(reinterpret_cast<std::uint64_t>(this) + 0x190));
|
||||
|
||||
process::read_memory(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x198), type_scopes.data(), type_scopes.size() * sizeof(std::uint64_t));
|
||||
|
||||
return type_scopes;
|
||||
}
|
||||
}
|
97
src/sdk/c_schema_system_type_scope.cpp
Normal file
97
src/sdk/c_schema_system_type_scope.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
#include "utility/murmur_hash.hpp"
|
||||
|
||||
namespace sdk {
|
||||
CSchemaClassInfo* CSchemaSystemTypeScope::find_declared_class(const std::string_view class_name) const noexcept {
|
||||
const std::uint32_t hash = utility::murmur_hash2(class_name.data(), class_name.length(), 0xBAADFEED);
|
||||
|
||||
const std::int32_t hash_transform1 = static_cast<std::uint8_t>(hash >> 0x10) - 0x2D6 + 0x21 * (0x21 * static_cast<std::uint8_t>(hash) + static_cast<std::uint8_t>(hash >> 0x8));
|
||||
const std::int32_t hash_transform2 = 0x21 * hash_transform1 + static_cast<std::uint8_t>(hash >> 0x18);
|
||||
|
||||
const auto hash_part = static_cast<std::uint32_t>(static_cast<std::uint8_t>(hash >> 0x18));
|
||||
|
||||
const std::int32_t hash_transform4 = (hash_transform2 ^ (hash_transform2 >> 0x10)) ^ (static_cast<std::uint16_t>(hash_transform2 ^ (hash_part >> 0x10)) >> 0x8);
|
||||
|
||||
const auto get_class_info = [&](const std::uint64_t address) -> CSchemaClassInfo* {
|
||||
const std::uint64_t class_info_address = address + 0x558 + static_cast<std::uint64_t>(0x28) * static_cast<std::uint8_t>(hash_transform4);
|
||||
|
||||
const auto initial_address = process::read_memory<std::uint64_t>(class_info_address + 0x58);
|
||||
|
||||
std::uint64_t class_address = initial_address;
|
||||
|
||||
if (initial_address != 0) {
|
||||
while (process::read_memory<std::uint32_t>(class_address) != hash) {
|
||||
class_address = process::read_memory<std::uint64_t>(class_address + 0x8);
|
||||
|
||||
if (class_address == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (class_address == 0) {
|
||||
const auto secondary_address = process::read_memory<std::uint64_t>(class_info_address + 0x60);
|
||||
|
||||
std::uint64_t final_address = 0;
|
||||
|
||||
if (secondary_address != 0 && secondary_address != initial_address) {
|
||||
auto current_address = process::read_memory<std::uint64_t>(class_info_address + 0x60);
|
||||
|
||||
const auto starting_address = process::read_memory<std::uint64_t>(class_info_address + 0x58);
|
||||
|
||||
if (current_address != starting_address) {
|
||||
while (process::read_memory<std::uint32_t>(current_address) != hash) {
|
||||
current_address = process::read_memory<std::uint64_t>(current_address + 0x8);
|
||||
|
||||
if (current_address == starting_address)
|
||||
break;
|
||||
}
|
||||
|
||||
final_address = current_address;
|
||||
}
|
||||
}
|
||||
|
||||
class_address = final_address;
|
||||
}
|
||||
|
||||
return reinterpret_cast<CSchemaClassInfo*>(class_address);
|
||||
};
|
||||
|
||||
const CSchemaClassInfo* class_info = get_class_info(reinterpret_cast<std::uint64_t>(this));
|
||||
|
||||
if (class_info != nullptr)
|
||||
return reinterpret_cast<CSchemaClassInfo*>(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(class_info) + 0x10));
|
||||
|
||||
const auto secondary_class_info = process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x108);
|
||||
|
||||
return secondary_class_info != 0 ? reinterpret_cast<CSchemaClassInfo*>(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(get_class_info(secondary_class_info)) + 0x10)) : nullptr;
|
||||
}
|
||||
|
||||
std::vector<CSchemaType_DeclaredClass*> CSchemaSystemTypeScope::get_declared_classes() const noexcept {
|
||||
std::vector<CSchemaType_DeclaredClass*> classes;
|
||||
|
||||
const std::uint64_t base = reinterpret_cast<std::uint64_t>(this) + 0x558;
|
||||
|
||||
const auto block_size = process::read_memory<std::uint32_t>(base + 0x4);
|
||||
const auto count = process::read_memory<std::uint32_t>(base + 0x10);
|
||||
const auto unallocated_data = process::read_memory<std::uint64_t>(base + 0x18 + 0x18);
|
||||
|
||||
std::int32_t index = 0;
|
||||
|
||||
for (std::uint64_t element = unallocated_data; element; element = process::read_memory<std::uint64_t>(element)) {
|
||||
for (std::int32_t i = 0; i < block_size && i != count; ++i) {
|
||||
classes.push_back(reinterpret_cast<CSchemaType_DeclaredClass*>(process::read_memory<std::uint64_t>(element + 0x20 + (i * 0x18))));
|
||||
|
||||
++index;
|
||||
|
||||
if (index >= count)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
std::string CSchemaSystemTypeScope::get_module_name() const noexcept {
|
||||
return process::read_string(reinterpret_cast<std::uint64_t>(this) + 0x8, 256);
|
||||
}
|
||||
}
|
7
src/sdk/c_schema_type_declared_class.cpp
Normal file
7
src/sdk/c_schema_type_declared_class.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
std::string CSchemaType_DeclaredClass::get_class_name() const noexcept {
|
||||
return process::read_string(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x8), 64);
|
||||
}
|
||||
}
|
53
src/utility/murmur_hash.cpp
Normal file
53
src/utility/murmur_hash.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "utility/murmur_hash.hpp"
|
||||
|
||||
namespace utility {
|
||||
std::uint32_t murmur_hash2(const void* key, std::uint32_t length, const std::uint32_t seed) {
|
||||
constexpr auto get_block = [](const std::uint32_t* ptr) -> std::uint32_t {
|
||||
const auto bytes = reinterpret_cast<const std::uint8_t*>(ptr);
|
||||
|
||||
return static_cast<std::uint32_t>(bytes[0]) | static_cast<std::uint32_t>(bytes[1]) << 0x8 | static_cast<std::uint32_t>(bytes[2]) << 0x10 | static_cast<std::uint32_t>(bytes[3]) << 0x18;
|
||||
};
|
||||
|
||||
constexpr std::uint32_t HASH_CONSTANT = 0x5BD1E995;
|
||||
|
||||
std::uint32_t hash = seed ^ length;
|
||||
|
||||
auto data = static_cast<const std::uint8_t*>(key);
|
||||
|
||||
while (length >= 4) {
|
||||
std::uint32_t block = get_block(reinterpret_cast<const std::uint32_t*>(data));
|
||||
|
||||
block *= HASH_CONSTANT;
|
||||
block ^= block >> 0x18;
|
||||
block *= HASH_CONSTANT;
|
||||
|
||||
hash *= HASH_CONSTANT;
|
||||
hash ^= block;
|
||||
|
||||
data += 0x4;
|
||||
length -= 0x4;
|
||||
}
|
||||
|
||||
switch (length) {
|
||||
case 3:
|
||||
hash ^= data[2] << 0x10;
|
||||
|
||||
case 2:
|
||||
hash ^= data[1] << 0x8;
|
||||
|
||||
case 1: {
|
||||
hash ^= data[0];
|
||||
hash *= HASH_CONSTANT;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
hash ^= hash >> 0xD;
|
||||
hash *= HASH_CONSTANT;
|
||||
hash ^= hash >> 0xF;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user