use std::fmt::{self, Write}; use std::fs; use std::path::Path; use anyhow::{Result, anyhow}; use chrono::{DateTime, Utc}; use memflow::prelude::v1::*; use serde_json::json; use formatter::Formatter; use crate::analysis::*; mod buttons; mod formatter; mod interfaces; mod offsets; mod schemas; enum Item<'a> { Buttons(&'a ButtonMap), Interfaces(&'a InterfaceMap), Offsets(&'a OffsetMap), Schemas(&'a SchemaMap), } impl<'a> Item<'a> { fn write(&self, fmt: &mut Formatter<'a>, file_type: &str) -> fmt::Result { match file_type { "cs" => self.write_cs(fmt), "hpp" => self.write_hpp(fmt), "json" => self.write_json(fmt), "rs" => self.write_rs(fmt), _ => unimplemented!(), } } } trait CodeWriter { fn write_cs(&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_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result; } impl<'a> CodeWriter for Item<'a> { fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match self { Item::Buttons(buttons) => buttons.write_cs(fmt), Item::Interfaces(ifaces) => ifaces.write_cs(fmt), Item::Offsets(offsets) => offsets.write_cs(fmt), Item::Schemas(schemas) => schemas.write_cs(fmt), } } fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match self { Item::Buttons(buttons) => buttons.write_hpp(fmt), Item::Interfaces(ifaces) => ifaces.write_hpp(fmt), Item::Offsets(offsets) => offsets.write_hpp(fmt), Item::Schemas(schemas) => schemas.write_hpp(fmt), } } fn write_json(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match self { Item::Buttons(buttons) => buttons.write_json(fmt), Item::Interfaces(ifaces) => ifaces.write_json(fmt), Item::Offsets(offsets) => offsets.write_json(fmt), Item::Schemas(schemas) => schemas.write_json(fmt), } } fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match self { Item::Buttons(buttons) => buttons.write_rs(fmt), Item::Interfaces(ifaces) => ifaces.write_rs(fmt), Item::Offsets(offsets) => offsets.write_rs(fmt), Item::Schemas(schemas) => schemas.write_rs(fmt), } } } pub struct Output<'a> { file_types: &'a [String], indent_size: usize, out_dir: &'a Path, result: &'a AnalysisResult, timestamp: DateTime, } impl<'a> Output<'a> { pub fn new( file_types: &'a [String], indent_size: usize, out_dir: &'a Path, result: &'a AnalysisResult, ) -> Result { fs::create_dir_all(&out_dir)?; Ok(Self { file_types, indent_size, out_dir, result, timestamp: Utc::now(), }) } pub fn dump_all(&self, process: &mut P) -> Result<()> { let items = [ ("buttons", Item::Buttons(&self.result.buttons)), ("interfaces", Item::Interfaces(&self.result.interfaces)), ("offsets", Item::Offsets(&self.result.offsets)), ]; for (file_name, item) in &items { self.dump_item(file_name, item)?; } self.dump_schemas()?; self.dump_info(process)?; Ok(()) } fn dump_info(&self, process: &mut P) -> Result<()> { let file_path = self.out_dir.join("info.json"); let build_number = self .result .offsets .iter() .find_map(|(module_name, offsets)| { let module = process.module_by_name(module_name).ok()?; let offset = offsets.iter().find(|(name, _)| *name == "dwBuildNumber")?.1; process.read::(module.base + offset).data_part().ok() }) .ok_or(anyhow!("failed to read build number"))?; let content = serde_json::to_string_pretty(&json!({ "timestamp": self.timestamp.to_rfc3339(), "build_number": build_number, }))?; fs::write(&file_path, &content)?; Ok(()) } fn dump_item(&self, file_name: &str, item: &Item) -> Result<()> { for file_type in self.file_types { let mut out = String::new(); let mut fmt = Formatter::new(&mut out, self.indent_size); if file_type != "json" { self.write_banner(&mut fmt)?; } item.write(&mut fmt, file_type)?; let file_path = self.out_dir.join(format!("{}.{}", file_name, file_type)); fs::write(&file_path, out)?; } Ok(()) } fn dump_schemas(&self) -> Result<()> { for (module_name, (classes, enums)) in &self.result.schemas { let map = SchemaMap::from([(module_name.clone(), (classes.clone(), enums.clone()))]); self.dump_item(&slugify(&module_name), &Item::Schemas(&map))?; } Ok(()) } fn write_banner(&self, fmt: &mut Formatter<'_>) -> Result<()> { writeln!(fmt, "// Generated using https://github.com/a2x/cs2-dumper")?; writeln!(fmt, "// {}\n", self.timestamp)?; Ok(()) } } #[inline] fn slugify(input: &str) -> String { input.replace(|c: char| !c.is_alphanumeric(), "_") }