Skip to content
 
 

Latest commit

 

History

570 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cs2-dumper

Platform: Windows x86-64 only. Linux, macOS, and 32-bit Windows builds are intentionally unsupported.

Counter-Strike 2 外部 offset / schema / SDK dumper。单 exe、默认全量输出。

本仓库是若干公开 GitHub 项目的融合:分析管线来自 a2x/cs2-dumper,消费端 include-tree SDK 来自 scros22/cs2-universal-offsets,离线 LoadLibrary、注入注册 schema、syscall 读内存等来自下面列出的其它 dumper。借鉴的东西都可以在 GitHub 找到,完整出处见 Credits

默认走 memflow 外部读内存。它只 dump,不包含作弊功能。

Build

需要 Rust 1.88+(edition 2024;slice::as_chunks 等 API 从 1.88 稳定)。仓库根目录:

cargo build --release

产物只有一个 target/release/cs2-dumper.exe-c shade 用的 payload 在编译时嵌进这个 exe,不需要再带 DLL。

Benches

微基准打的是 dump 真正会走的公开函数(小端整数加载、模块名 intern、上一轮 patterns.json 缓存查找、IDA matcher、标识符 slugify),不是另写一套。不 attach cs2.exe

cargo bench --features bench --bench hot_paths

FakeMemory 只在 cfg(test)--features bench 下编译,默认 exe 不含它。--features bench 是必须的:[[bench]] 标了 required-features,不加这个 feature 时 cargo 会跳过该 bench。

Usage

cs2-dumper.exe

不传参数就会跑完全部阶段,写出扁平多语言文件、C++ include-tree,以及全局命名空间 sdk/

如不希望回退扫描所有盘符,可设置环境变量 CS2_DUMPER_SKIP_DRIVE_SCAN=1;这不会影响注册表、Steam VDF 和常见路径探测。

  1. 本机有 cs2.exe:attach 后 dump 活进程。
  2. 没有游戏进程、且未指定 memflow 插件(-c pcileech / kvm / winio):从注册表 / libraryfolders.vdf / 本地盘自动找 steamapps/common/Counter-Strike*,把 schema DLL LoadLibrarydumper 自己的进程 再 dump(不是注入 cs2.exe)。插件连接器上找不到 cs2.exe 会直接失败,不会误 dump 本机 LoadLibrary 镜像。

可选参数:

参数 说明
-o, --output <DIR> 输出目录,默认 output
-v / -vv 更详细的日志
-c, --connector <name> 内存后端,见下表
--guess-structs 额外写出 structs.hpp:按字段间距猜测未知类型大小。偏移会显式 pad;sizeof 仍可能错。默认关闭
--no-sound 关闭 Windows Beep() 进度提示音
-h / -V 帮助 / 版本

日志默认写到 <output>/cs2-dumper.log。终端默认 Warn(sanitize 丢弃、写失败等会显示);-v 提到 Info,-vv Debug。文件始终记 Info 及以上。输出目录一开始就会创建;日志文件写失败不会中断 dump。

Backends (-c)

名字 何时用
(默认 / native memflow-native attach。游戏没开就自动走 LoadLibrary
syscall cs2.exe,本机 Hell's Gate 风格 NtReadVirtualMemory stub(Windows)
shade 把内嵌 payload 注入活 cs2.exe,对已加载模块调用 InstallSchemaBindings,再 dump(Windows)
pcileech / kvm / winio memflow 插件。例如 cs2-dumper.exe -c pcileech -a :device=FPGA

syscallshade 和 memflow 插件都要求目标上有活的 cs2.exe。DMA 连接器通常需要管理员 / root。

manifest.jsonbackend 字段会记下这次跑的是 native / syscall / shade / loadlib。LoadLibrary dump 不会把未初始化的 dwBuildNumber(常见 0 / 0xFFFFFFFF)写成引擎版本;game/csgo/steam.infPatchVersion / ClientVersion 写进 steam_inf

Output

每次运行默认全量写出三套消费端产物,互不替代:

  • 扁平多语言文件(和 a2x/cs2-dumper 兼容):offsets.*buttons.*interfaces.*、各 <module>_dll.*
  • C++ include-treescros22/cs2-universal-offsets 那一套):cs2.hpp 单头 amalgamation + macros.hpp + schemas/ + impl/entity_system.hpp
  • 全局命名空间 SDKsdk/sdk.hpp + sdk/sdk_types.hpp + sdk/modules/ + sdk/classes/

写出 sdk/ 时控制台会打印生成的头文件名。根目录还会再写一份 entity_system.hpp(和 impl/entity_system.hpp 并存)。

<output>/
├── manifest.json
├── info.json
├── cs2.hpp                          # C++ 单头 amalgamation
├── macros.hpp                       # SCHEMA_FIELD + engine types + auto forwards
├── entity_system.hpp                # 根目录 entity helper(消费 offsets_merged.hpp)
├── offsets.{cs,hpp,json,rs,zig}     # canonical dwXxx
├── offsets_merged.{hpp,json}        # canonical + pattern + interface RVA
├── buttons.*  interfaces.*
├── <module>_dll.{cs,hpp,json,rs,zig}
├── patterns.{json,hpp,md,...}
├── vtables.{json,hpp,cs}            # hpp 还会拷到 offsets/vtables.hpp
├── schema_index.json
├── schema_index.diff.json
├── sdk/
│   ├── sdk.hpp                      # 全局命名空间 umbrella
│   ├── sdk_types.hpp
│   ├── sdk_enums.hpp
│   ├── sdk_classes.hpp
│   ├── modules.hpp
│   ├── modules/<module>.hpp
│   └── classes/<module>/<Class>.hpp
├── schemas/
│   ├── schemas.json
│   └── <module>_dll.hpp             # 带命名空间的 typed class
├── offsets/offsets.hpp
├── offsets/vtables.hpp
├── patterns/patterns.hpp
├── interfaces/interfaces.hpp        # 可调用 vtable 结构 + ASLR-safe getter
├── impl/entity_system.hpp
├── engine/
│   ├── engine_structs.json
│   ├── ccsgoinput.h
│   ├── cusercmd.h
│   └── cviewsetup.h
├── netvars/
├── convars/
├── protobufs/
├── entities/
├── weapons/
├── gameevents/
└── verified_features.json

活进程还能写出 convars、entities、weapons、game events、netvars、protobufs。LoadLibrary 模式没有这些运行时对象,schema / pattern / interface 仍然有。

schema_index.json 里 enum 的 size 是 schema 存储宽度(字节),成员个数是 members

你要干什么 用哪个文件
C++ 一个 include 全要 cs2.hpp
C++ 全局命名空间 SDK sdk/sdk.hpp
C++ typed schema class macros.hpp + schemas/client_dll.hpp
C++ 走 entity list impl/entity_system.hpp 或根目录 entity_system.hpp
任意语言只要常量 offsets.hpp / <module>_dll.json
更新后对 diff manifest.json + *.diff.json + patterns.repair.patch.json
按 vtable index hook vtables.hpp / vtables.json
schema 标志(vtable / abstract / scope) schema_index.jsonflags,以及 schemas/*.hpp 注释
dump 时没加载的模块 manifest.json missing_schema_modules

怎么用生成的 SDK

C++ 把 dump 目录加进 include path,然后:

my-project/
├── src/
└── vendor/cs2-dumper/          # 拷一份 <output>/
    ├── cs2.hpp
    ├── macros.hpp
    ├── sdk/
    ├── schemas/
    ├── impl/
    └── ...

include-tree(推荐单头):

#include <cs2.hpp>

static_assert(CS2_BUILD != 0, "dump must record a live build number");

int health(void* pawn_ptr) {
    auto* pawn = reinterpret_cast<client::C_CSPlayerPawn*>(pawn_ptr);
    return pawn->m_iHealth();
}

全局命名空间 SDK:

#include <sdk/sdk.hpp>

cs2.hpp 会拉上 macros.hpp、各模块 schema(编辑器模块会跳过)、merged offsets、typed interfaces、buttons、protobufs、patterns、entity helper、engine 结构。缺的可选报告用 __has_include 挡住,照样能编。sdk/sdk.hpp 是另一套独立头,给要 sdk:: 全局命名空间的人用,默认每次 dump 都会写出。

Entity helper 在 impl/entity_system.hpp(根目录 entity_system.hpp 也有一份),走 offsets:: 常量,不写死数字:

auto* local = CGameEntitySystem::GetLocalPlayer();
auto* identity = CGameEntitySystem::GetIdentityByIndex(1);

Interface 单例是 ASLR-safe 的,自己把模块基址传进去:

auto* input = ifc::inputsystem::get_InputSystemVersion001(module_base);
input->SetRelativeMouseMode(false);
void* slot = ifc::detail::vtable_slot(input, 76);

C# / Rust / Zig / JSON 用扁平多语言文件(offsets.jsonclient_dll.cspatterns.rs …)。include-tree 和 sdk/ 只给 C++。

游戏更新后:对着新 build 再跑一遍 dumper,看 manifest.jsonbuild_numberpattern_summary),再看 patterns.diff.json / schema_index.diff.json / interfaces.diff.json。特征漂了会写出 patterns.repair.json 和可直接喂回去的 patterns.repair.patch.json。然后用新的 cs2.hpp 重编消费端。

Schema

Source 2 自带运行时元数据,不用 PDB 也能还原 class / enum / netvar / metadata。

引擎启动时建 CSchemaSystem 单例。每个用 schema 的模块注册一个 CSchemaSystemTypeScope(一 DLL 一 scope),scope 里是 CUtlTSHash,装着该模块的 CSchemaClassBinding / CSchemaEnumBinding

每个 class 有:名字、可选父类、字段(名字 / 类型字符串 / 实例内偏移)、编译期 metadata。最常用的两条是 MNetworkVarNames(name, type)MNetworkChangeCallback(name)。enum 同样是名字 + 对齐 + (name, value) 列表。

Walker 在 src/source2/

  1. pelite 扫 PE,用特征定位 schema 系统(特征在 src/analysis/offsets.rs)。
  2. memflow 顺着 CUtlTSHash 枚举每个 scope 的 class / enum。
  3. src/analysis/schemas.rs 拍扁成 emitter 用的 Class / Enum

三套写出:

// 扁平常量(任意语言)
namespace cs2_dumper::schemas::client_dll::C_CSPlayerPawn {
    constexpr std::ptrdiff_t m_iHealth = 0x344;
}

// include-tree typed class(cs2.hpp / schemas/)
namespace cs2::sdk::client_dll {
    class C_CSPlayerPawn : public C_BasePlayerPawn {
    public:
        SCHEMA_FIELD(std::int32_t, m_iHealth, 0x344)
    };
}

// 全局命名空间 SDK(sdk/sdk.hpp)
#include <sdk/sdk.hpp>

SCHEMA_FIELDmacros.hpp,展开成 this + offset 的 typed 访问器。带 MNetworkVarNames 的字段还会单独写到 netvars/

相关代码:src/source2/src/analysis/schemas.rssrc/output/schemas.rssrc/output/sdk.rssrc/output/sdk_classes.rssrc/output/netvars.rs

Patterns

特征是 IDA 风格字节串,空格分隔,? / ?? 是通配,也支持半字节通配(4??A)。扫描器基于 pelite,只扫指定模块的指定 PE 节(代码走 .text,字符串走 .rdata)。活进程读失败时会回退到磁盘 PE,先按 SizeOfImage 映射成 RVA 布局再扫,不会把 PointerToRawData 当成 VirtualAddress

匹配地址是 pattern 第一个字节 的 RVA。真正要的值往往在后面的 lea / call / mov 里,所以每条特征带一个 resolver:

resolve 干什么
raw / None 匹配地址就是结果(函数头,直接 hook)
rel32 E8/E9 的 signed disp32。rel_off 是 disp 相对 pattern 起点的字节下标,通常是 1
riprel 48 8D 0D disp32 这类 RIP-relative。48 8D 0Drel_off3
stringref 先在 .rdata 找字符串,再合成 lea rXX, [rip+str].text 里做 riprel

rel32 / riprel 算法一样:

disp = i32::from_le_bytes(text[match_rva + rel_off ..][..4])
RVA  = match_rva + rel_off + 4 + disp

内置库在 src/patterns/database.rs。每条扫完记 match 次数;一条挂了不会把整次 dump 带崩。上次的 patterns.json 会先在缓存的 match_rva 上复验,没漂就跳过全模块扫描。

写出 patterns.{json,hpp,md,cs,rs,zig}。漂了的特征会尝试放宽通配,结果进 patterns.repair.json;其中唯一命中的子集写成 patterns.repair.patch.json

加一条特征

  1. 在 IDA / Ghidra / Binary Ninja 里找到函数。
  2. 抽 12–20 个跨更新还在的字节:函数序言、唯一常量加载、有名字的 import。别用大段 mov reg, reg,也别把每补丁都变的立即数写死。
  3. 会变的位移 / 寄存器编码改成 ?? 或半字节通配。
  4. 选 resolver:序言用 NoneE8/E9Rel32 { rel_off: 1 }48 8D 0DRipRel { rel_off: 3 }rel_off 差 1 会得到看起来像那么回事但错的 RVA。
  5. 追加到 src/patterns/database.rsCS2_PATTERNS
Pattern {
    name: "MyNewThing",
    module: "client.dll",
    needle: "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC ?",
    resolve: NONE,
    extra_off: 0,
    prototype: "",
},
  1. cargo build --release,再跑一次 dumper。看 patterns.jsonfoundmatches(必须是 1)、rva。拿 RVA 回反汇编器对一下。

Vtables

对每个解析到的 interface,跟实例指针的第一个 qword 拿到 vftable,再当连续函数指针数组往下走,直到槽位不落在任何已加载模块镜像里。写出 vtables.{json,hpp,cs},hpp 再拷到 include-tree 的 offsets/vtables.hpp

没有 PDB。多数槽是 method_<N>,直接用下标。额外会拿槽的 RVA 去对 pattern 库,对上了就用特征名(例如 update_global_vars)。MSVC /GR 下还能顺着 vtable [-1]RTTICompleteObjectLocator 解出 C++ 类名。

vtable 布局比函数体字节稳得多:槽位下标只有 Valve 给虚函数增删改序才会变。

#include <cs2.hpp>

using CreateMove_t = bool(__thiscall*)(void* self, int slot, float ft, bool active);

void hook_create_move(void* iface) {
    auto** vt = *reinterpret_cast<void***>(iface);
    auto* fn = vt[cs2::vtables::client_dll::Source2Client002::method_25];
    install_hook(fn, &your_create_move_hook);
}

vtables.jsonvtable_module 是 vftable 字节所在的 DLL(实现有时在兄弟模块里)。Walker 在 src/analysis/vtables.rs,写出在 src/output/vtables.rs

Layout

路径 做什么
src/main.rs CLI、日志、选后端、attach
src/dump.rs 分析 → 特征 → 对象级恢复 → 写出全部产物
src/analysis/ schema / offset / interface / button / vtable / entity / convar
src/analysis/module_data.rs 模块镜像读取;一次 dump 内缓存,避免反复读 client.dll
src/analysis/read.rs 远程 C 字符串读取,walker 共用
src/patterns/ 内置特征库、扫描、repair
src/output/ 扁平文件、include-tree、sdk/、manifest
src/output/cpp_types.rs schema → C++ 原语 / 存储宽度,两套 SDK 共用
src/memory/ native / syscall / shade / LoadLibrary 自附加
src/loadlib.rs 游戏没开时对本进程 LoadLibrary
shade-payload/ -c shade 注入 DLL,编译期嵌进主 exe

Tests

cargo test --workspace

需要活 cs2.exe 的检查:

cargo test -- --ignored --nocapture

Credits

下面这些都是公开 GitHub 仓库。本项目只取各自强项,拼进一条管线。

Dumpers this tree is based on

仓库 拿了什么
a2x/cs2-dumper 主体:memflow attach、schema / interface / button / offset 扫描、多语言输出、pattern 修复与动态恢复
scros22/cs2-universal-offsets 带命名空间的 C++ include-tree、SCHEMA_FIELD、自动前向声明、typed entity helper、engine 结构布局
bt629414/cs2-best-dumper 额外 dwCreateMove / dwParticleManager / dwClientMode / dwPVSManager / dwVPhys2World、磁盘 PE 回退、可选 syscall RPM(-c syscall)、--guess-structs
arisuwine/shade-dumper 完整 SCHEMA_CF1_* / SCHEMA_EF_* 标签、vtable slot dump、可选注入后调用 InstallSchemaBindings-c shade
xsip/cs2-schema-dumper-no-process 游戏没开时:找 Steam 安装、对本进程 LoadLibrary schema DLL 再 dump

Libraries and related Source 2 tools

仓库 角色
memflow/memflow 跨平台内存读写框架(默认后端)
memflow/memflow-native Windows 本机 attach
CasualX/pelite PE 解析与特征扫描
am0nsec/HellsGate -c syscall:从 ntdll stub 抽 SSN 再调 NtReadVirtualMemory
neverlosecc/source2gen Source 2 schema → C++ SDK 的常见写法(SCHEMA_FIELD 这一路)
GAMMACASE/Source2SchemaDumper 服务端 schema dump(CS2 / Dota 2 / Deadlock)
sneakyevil/CS2-SchemaDumper 早期外部 schema walker
ValveResourceFormat/DumpSource2 离线 schema / convar dump(GameTracking 在用)
a2x/cs2-analyzer a2x 的离线分析器(WIP)

License

MIT(LICENSE)。上游项目保留各自许可证;本仓库的分析与输出代码按 MIT 发布。

About

Counter-Strike: 2 Offset Dumper

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages