mirror of
https://github.com/a2x/cs2-dumper.git
synced 2026-03-14 13:17:24 +08:00
Merge pull request #552 from hagz0r/feat/zig-output-support
Add Zig output support
This commit is contained in:
@@ -36,7 +36,7 @@ Linux or as an administrator on Windows.
|
|||||||
|
|
||||||
- `-c, --connector <connector>`: The name of the memflow connector to use.
|
- `-c, --connector <connector>`: The name of the memflow connector to use.
|
||||||
- `-a, --connector-args <connector-args>`: Additional arguments to pass to the memflow connector.
|
- `-a, --connector-args <connector-args>`: Additional arguments to pass to the memflow connector.
|
||||||
- `-f, --file-types <file-types>`: The types of files to generate. Default: `cs`, `hpp`, `json`, `rs`.
|
- `-f, --file-types <file-types>`: The types of files to generate. Default: `cs`, `hpp`, `json`, `rs`, `zig`.
|
||||||
- `-i, --indent-size <indent-size>`: The number of spaces to use per indentation level. Default: `4`.
|
- `-i, --indent-size <indent-size>`: The number of spaces to use per indentation level. Default: `4`.
|
||||||
- `-o, --output <output>`: The output directory to write the generated files to. Default: `output`.
|
- `-o, --output <output>`: The output directory to write the generated files to. Default: `output`.
|
||||||
- `-p, --process-name <process-name>`: The name of the game process. Default: `cs2.exe`.
|
- `-p, --process-name <process-name>`: The name of the game process. Default: `cs2.exe`.
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ struct Args {
|
|||||||
connector_args: Option<String>,
|
connector_args: Option<String>,
|
||||||
|
|
||||||
/// The types of files to generate.
|
/// The types of files to generate.
|
||||||
#[arg(short, long, value_delimiter = ',', default_values = ["cs", "hpp", "json", "rs"])]
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
value_delimiter = ',',
|
||||||
|
default_values = ["cs", "hpp", "json", "rs", "zig"]
|
||||||
|
)]
|
||||||
file_types: Vec<String>,
|
file_types: Vec<String>,
|
||||||
|
|
||||||
/// The number of spaces to use per indentation level.
|
/// The number of spaces to use per indentation level.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
use super::{ButtonMap, CodeWriter, Formatter};
|
use super::{ButtonMap, CodeWriter, Formatter, zig_ident};
|
||||||
|
|
||||||
impl CodeWriter for ButtonMap {
|
impl CodeWriter for ButtonMap {
|
||||||
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
@@ -67,4 +67,18 @@ impl CodeWriter for ButtonMap {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.block("pub const cs2_dumper = struct", true, |fmt| {
|
||||||
|
writeln!(fmt, "// Module: client.dll")?;
|
||||||
|
|
||||||
|
fmt.block("pub const buttons = struct", true, |fmt| {
|
||||||
|
for (name, value) in self {
|
||||||
|
writeln!(fmt, "pub const {}: usize = {:#X};", zig_ident(name), value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt::{self, Write};
|
|||||||
|
|
||||||
use heck::{AsPascalCase, AsSnakeCase};
|
use heck::{AsPascalCase, AsSnakeCase};
|
||||||
|
|
||||||
use super::{CodeWriter, Formatter, InterfaceMap, slugify};
|
use super::{CodeWriter, Formatter, InterfaceMap, slugify, zig_ident};
|
||||||
|
|
||||||
impl CodeWriter for InterfaceMap {
|
impl CodeWriter for InterfaceMap {
|
||||||
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
@@ -103,4 +103,35 @@ impl CodeWriter for InterfaceMap {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.block("pub const cs2_dumper = struct", true, |fmt| {
|
||||||
|
fmt.block("pub const interfaces = struct", true, |fmt| {
|
||||||
|
for (module_name, ifaces) in self {
|
||||||
|
writeln!(fmt, "// Module: {}", module_name)?;
|
||||||
|
|
||||||
|
let module_name = zig_ident(&AsSnakeCase(slugify(module_name)).to_string());
|
||||||
|
|
||||||
|
fmt.block(
|
||||||
|
&format!("pub const {} = struct", module_name),
|
||||||
|
true,
|
||||||
|
|fmt| {
|
||||||
|
for (name, value) in ifaces {
|
||||||
|
writeln!(
|
||||||
|
fmt,
|
||||||
|
"pub const {}: usize = {:#X};",
|
||||||
|
zig_ident(name),
|
||||||
|
value
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ impl<'a> Item<'a> {
|
|||||||
"hpp" => self.write_hpp(fmt),
|
"hpp" => self.write_hpp(fmt),
|
||||||
"json" => self.write_json(fmt),
|
"json" => self.write_json(fmt),
|
||||||
"rs" => self.write_rs(fmt),
|
"rs" => self.write_rs(fmt),
|
||||||
|
"zig" => self.write_zig(fmt),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,7 @@ trait CodeWriter {
|
|||||||
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
||||||
fn write_json(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
fn write_json(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
||||||
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
||||||
|
fn write_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CodeWriter for Item<'a> {
|
impl<'a> CodeWriter for Item<'a> {
|
||||||
@@ -82,6 +84,15 @@ impl<'a> CodeWriter for Item<'a> {
|
|||||||
Item::Schemas(schemas) => schemas.write_rs(fmt),
|
Item::Schemas(schemas) => schemas.write_rs(fmt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Item::Buttons(buttons) => buttons.write_zig(fmt),
|
||||||
|
Item::Interfaces(ifaces) => ifaces.write_zig(fmt),
|
||||||
|
Item::Offsets(offsets) => offsets.write_zig(fmt),
|
||||||
|
Item::Schemas(schemas) => schemas.write_zig(fmt),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Output<'a> {
|
pub struct Output<'a> {
|
||||||
@@ -193,3 +204,85 @@ impl<'a> Output<'a> {
|
|||||||
fn slugify(input: &str) -> String {
|
fn slugify(input: &str) -> String {
|
||||||
input.replace(|c: char| !c.is_alphanumeric(), "_")
|
input.replace(|c: char| !c.is_alphanumeric(), "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn zig_ident(input: &str) -> String {
|
||||||
|
if is_zig_identifier(input) && !is_zig_keyword(input) {
|
||||||
|
input.to_string()
|
||||||
|
} else {
|
||||||
|
let escaped = input.replace('\\', "\\\\").replace('"', "\\\"");
|
||||||
|
|
||||||
|
format!("@\"{}\"", escaped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_zig_identifier(input: &str) -> bool {
|
||||||
|
let mut chars = input.chars();
|
||||||
|
|
||||||
|
match chars.next() {
|
||||||
|
Some(c) if c == '_' || c.is_ascii_alphabetic() => {}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_zig_keyword(input: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
input,
|
||||||
|
"addrspace"
|
||||||
|
| "align"
|
||||||
|
| "allowzero"
|
||||||
|
| "and"
|
||||||
|
| "anyframe"
|
||||||
|
| "anytype"
|
||||||
|
| "asm"
|
||||||
|
| "async"
|
||||||
|
| "await"
|
||||||
|
| "break"
|
||||||
|
| "callconv"
|
||||||
|
| "catch"
|
||||||
|
| "comptime"
|
||||||
|
| "const"
|
||||||
|
| "continue"
|
||||||
|
| "defer"
|
||||||
|
| "else"
|
||||||
|
| "enum"
|
||||||
|
| "errdefer"
|
||||||
|
| "error"
|
||||||
|
| "export"
|
||||||
|
| "extern"
|
||||||
|
| "false"
|
||||||
|
| "fn"
|
||||||
|
| "for"
|
||||||
|
| "if"
|
||||||
|
| "inline"
|
||||||
|
| "linksection"
|
||||||
|
| "noalias"
|
||||||
|
| "noinline"
|
||||||
|
| "nosuspend"
|
||||||
|
| "null"
|
||||||
|
| "opaque"
|
||||||
|
| "or"
|
||||||
|
| "orelse"
|
||||||
|
| "packed"
|
||||||
|
| "pub"
|
||||||
|
| "resume"
|
||||||
|
| "return"
|
||||||
|
| "struct"
|
||||||
|
| "suspend"
|
||||||
|
| "switch"
|
||||||
|
| "test"
|
||||||
|
| "threadlocal"
|
||||||
|
| "true"
|
||||||
|
| "try"
|
||||||
|
| "union"
|
||||||
|
| "unreachable"
|
||||||
|
| "usingnamespace"
|
||||||
|
| "var"
|
||||||
|
| "volatile"
|
||||||
|
| "while"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::fmt::{self, Write};
|
|||||||
|
|
||||||
use heck::{AsPascalCase, AsSnakeCase};
|
use heck::{AsPascalCase, AsSnakeCase};
|
||||||
|
|
||||||
use super::{CodeWriter, Formatter, OffsetMap, slugify};
|
use super::{CodeWriter, Formatter, OffsetMap, slugify, zig_ident};
|
||||||
|
|
||||||
impl CodeWriter for OffsetMap {
|
impl CodeWriter for OffsetMap {
|
||||||
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
@@ -84,4 +84,35 @@ impl CodeWriter for OffsetMap {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.block("pub const cs2_dumper = struct", true, |fmt| {
|
||||||
|
fmt.block("pub const offsets = struct", true, |fmt| {
|
||||||
|
for (module_name, offsets) in self {
|
||||||
|
writeln!(fmt, "// Module: {}", module_name)?;
|
||||||
|
|
||||||
|
let module_name = zig_ident(&AsSnakeCase(slugify(module_name)).to_string());
|
||||||
|
|
||||||
|
fmt.block(
|
||||||
|
&format!("pub const {} = struct", module_name),
|
||||||
|
true,
|
||||||
|
|fmt| {
|
||||||
|
for (name, value) in offsets {
|
||||||
|
writeln!(
|
||||||
|
fmt,
|
||||||
|
"pub const {}: usize = {:#X};",
|
||||||
|
zig_ident(name),
|
||||||
|
value
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use heck::{AsPascalCase, AsSnakeCase};
|
|||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use super::{CodeWriter, Formatter, SchemaMap, slugify};
|
use super::{CodeWriter, Formatter, SchemaMap, slugify, zig_ident};
|
||||||
|
|
||||||
use crate::analysis::ClassMetadata;
|
use crate::analysis::ClassMetadata;
|
||||||
|
|
||||||
@@ -390,6 +390,111 @@ impl CodeWriter for SchemaMap {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.block("pub const cs2_dumper = struct", true, |fmt| {
|
||||||
|
fmt.block("pub const schemas = struct", true, |fmt| {
|
||||||
|
for (module_name, (classes, enums)) in self {
|
||||||
|
writeln!(fmt, "// Module: {}", module_name)?;
|
||||||
|
writeln!(fmt, "// Class count: {}", classes.len())?;
|
||||||
|
writeln!(fmt, "// Enum count: {}", enums.len())?;
|
||||||
|
|
||||||
|
let module_name = zig_ident(&AsSnakeCase(slugify(module_name)).to_string());
|
||||||
|
|
||||||
|
fmt.block(
|
||||||
|
&format!("pub const {} = struct", module_name),
|
||||||
|
true,
|
||||||
|
|fmt| {
|
||||||
|
for enum_ in enums {
|
||||||
|
let type_name = match enum_.alignment {
|
||||||
|
1 => "u8",
|
||||||
|
2 => "u16",
|
||||||
|
4 => "u32",
|
||||||
|
8 => "u64",
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
writeln!(fmt, "// Alignment: {}", enum_.alignment)?;
|
||||||
|
writeln!(fmt, "// Member count: {}", enum_.size)?;
|
||||||
|
|
||||||
|
let enum_name = zig_ident(&slugify(&enum_.name));
|
||||||
|
|
||||||
|
fmt.block(
|
||||||
|
&format!("pub const {} = enum({})", enum_name, type_name),
|
||||||
|
true,
|
||||||
|
|fmt| {
|
||||||
|
let mut used_values = HashSet::new();
|
||||||
|
|
||||||
|
let members = enum_
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.filter_map(|member| {
|
||||||
|
// Skip duplicate values.
|
||||||
|
if !used_values.insert(member.value) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatted_value = format_zig_enum_member_value(
|
||||||
|
member.value,
|
||||||
|
type_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(format!(
|
||||||
|
"{} = {}",
|
||||||
|
zig_ident(&member.name),
|
||||||
|
formatted_value
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",\n");
|
||||||
|
|
||||||
|
writeln!(fmt, "{}", members)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for class in classes {
|
||||||
|
let parent_name = class
|
||||||
|
.parent_name
|
||||||
|
.as_deref()
|
||||||
|
.map(slugify)
|
||||||
|
.unwrap_or("None".to_string());
|
||||||
|
|
||||||
|
writeln!(fmt, "// Parent: {}", parent_name)?;
|
||||||
|
writeln!(fmt, "// Field count: {}", class.fields.len())?;
|
||||||
|
|
||||||
|
write_metadata(fmt, &class.metadata)?;
|
||||||
|
|
||||||
|
let class_name = zig_ident(&slugify(&class.name));
|
||||||
|
|
||||||
|
fmt.block(
|
||||||
|
&format!("pub const {} = struct", class_name),
|
||||||
|
true,
|
||||||
|
|fmt| {
|
||||||
|
for field in &class.fields {
|
||||||
|
writeln!(
|
||||||
|
fmt,
|
||||||
|
"pub const {}: usize = {:#X}; // {}",
|
||||||
|
zig_ident(&field.name),
|
||||||
|
field.offset,
|
||||||
|
field.type_name
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_metadata(fmt: &mut Formatter<'_>, metadata: &[ClassMetadata]) -> fmt::Result {
|
fn write_metadata(fmt: &mut Formatter<'_>, metadata: &[ClassMetadata]) -> fmt::Result {
|
||||||
@@ -416,3 +521,19 @@ fn write_metadata(fmt: &mut Formatter<'_>, metadata: &[ClassMetadata]) -> fmt::R
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_zig_enum_member_value(value: i64, type_name: &str) -> String {
|
||||||
|
if value >= 0 {
|
||||||
|
return format!("{:#X}", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapped_value = match type_name {
|
||||||
|
"u8" => value as u8 as u64,
|
||||||
|
"u16" => value as u16 as u64,
|
||||||
|
"u32" => value as u32 as u64,
|
||||||
|
"u64" => value as u64,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{:#X}", wrapped_value)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user