Merge pull request #552 from hagz0r/feat/zig-output-support

Add Zig output support
This commit is contained in:
a2x
2026-03-12 16:50:10 +10:00
committed by GitHub
7 changed files with 301 additions and 6 deletions

View File

@@ -36,7 +36,7 @@ Linux or as an administrator on Windows.
- `-c, --connector <connector>`: The name of the memflow connector to use.
- `-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`.
- `-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`.

View File

@@ -36,7 +36,12 @@ struct Args {
connector_args: Option<String>,
/// 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>,
/// The number of spaces to use per indentation level.

View File

@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use std::fmt::{self, Write};
use super::{ButtonMap, CodeWriter, Formatter};
use super::{ButtonMap, CodeWriter, Formatter, zig_ident};
impl CodeWriter for ButtonMap {
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(())
})
})
}
}

View File

@@ -3,7 +3,7 @@ use std::fmt::{self, Write};
use heck::{AsPascalCase, AsSnakeCase};
use super::{CodeWriter, Formatter, InterfaceMap, slugify};
use super::{CodeWriter, Formatter, InterfaceMap, slugify, zig_ident};
impl CodeWriter for InterfaceMap {
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(())
})
})
}
}

View File

@@ -34,6 +34,7 @@ impl<'a> Item<'a> {
"hpp" => self.write_hpp(fmt),
"json" => self.write_json(fmt),
"rs" => self.write_rs(fmt),
"zig" => self.write_zig(fmt),
_ => unimplemented!(),
}
}
@@ -44,6 +45,7 @@ trait CodeWriter {
fn write_hpp(&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_zig(&self, fmt: &mut Formatter<'_>) -> fmt::Result;
}
impl<'a> CodeWriter for Item<'a> {
@@ -82,6 +84,15 @@ impl<'a> CodeWriter for Item<'a> {
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> {
@@ -193,3 +204,85 @@ impl<'a> Output<'a> {
fn slugify(input: &str) -> String {
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"
)
}

View File

@@ -2,7 +2,7 @@ use std::fmt::{self, Write};
use heck::{AsPascalCase, AsSnakeCase};
use super::{CodeWriter, Formatter, OffsetMap, slugify};
use super::{CodeWriter, Formatter, OffsetMap, slugify, zig_ident};
impl CodeWriter for OffsetMap {
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(())
})
})
}
}

View File

@@ -5,7 +5,7 @@ use heck::{AsPascalCase, AsSnakeCase};
use serde_json::json;
use super::{CodeWriter, Formatter, SchemaMap, slugify};
use super::{CodeWriter, Formatter, SchemaMap, slugify, zig_ident};
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 {
@@ -416,3 +521,19 @@ fn write_metadata(fmt: &mut Formatter<'_>, metadata: &[ClassMetadata]) -> fmt::R
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)
}