il2cpp_dumper 0.4.0

A blazing fast and reliable il2cpp dumper cross platfrom.
Documentation
pub static CPP_RESERVED_KEYWORDS: &[&str] = &[
    "klass", "monitor", "register", "_cs", "auto", "friend", "template",
    "flat", "default", "_ds", "interrupt", "unsigned", "signed", "asm",
    "if", "case", "break", "continue", "do", "new", "_", "short",
    "union", "class", "namespace", "volatile", "const", "extern",
    "static", "struct", "typedef", "enum", "return", "switch",
    "goto", "void", "for", "while", "else", "sizeof", "this",
    "public", "private", "protected", "operator", "true", "false",
    "nullptr", "method",
];

pub static CPP_RESERVED_SPECIAL: &[&str] = &["inline", "near", "far"];

pub static LIBC_RESERVED_NAMES: &[&str] = &[
    "__sF", "__sE", "__sS", "__stdin", "__stdout", "__stderr",
    "__cleanup", "__progname", "__environ", "errno",
    "stdin", "stdout", "stderr",
];

#[derive(Debug, Clone, Copy, Default)]
pub struct NameSanitizerOptions {
    pub allow_dollar: bool,
    pub avoid_double_underscore_prefix: bool,
}

pub fn sanitize_cpp_identifier(name: &str, opts: NameSanitizerOptions) -> String {
    if CPP_RESERVED_KEYWORDS.contains(&name) {
        return format!("_{name}");
    }
    if CPP_RESERVED_SPECIAL.contains(&name) {
        return format!("_{name}_");
    }
    let first = name.chars().next();
    if first.map(|c| c.is_ascii_digit()).unwrap_or(false) {
        return format!("_{name}");
    }

    let mut result = String::with_capacity(name.len());
    for c in name.chars() {
        if c.is_ascii_alphanumeric() || c == '_' || (opts.allow_dollar && c == '$') {
            result.push(c);
        } else {
            result.push('_');
        }
    }

    if opts.avoid_double_underscore_prefix && result.starts_with("__") {
        result = format!("f{result}");
    }

    if LIBC_RESERVED_NAMES.contains(&result.as_str()) {
        result = format!("_{result}_");
    }

    result
}

pub fn sanitize_mangled_identifier_chars(id: &str) -> String {
    let mut out = String::with_capacity(id.len());
    for c in id.chars() {
        if c.is_ascii_alphanumeric() || c == '_' {
            out.push(c);
        } else {
            out.push('_');
        }
    }
    out
}

pub fn escape_string(s: &str) -> String {
    let mut result = String::with_capacity(s.len());
    for ch in s.chars() {
        match ch {
            '\\' => result.push_str("\\\\"),
            '"' => result.push_str("\\\""),
            '\n' => result.push_str("\\n"),
            '\r' => result.push_str("\\r"),
            '\t' => result.push_str("\\t"),
            '\0' => result.push_str("\\0"),
            c if c.is_control() => {
                result.push_str(&format!("\\x{:02X}", c as u32));
            }
            c => result.push(c),
        }
    }
    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_escape_string() {
        assert_eq!(escape_string("hello"), "hello");
        assert_eq!(escape_string("he\"llo"), "he\\\"llo");
        assert_eq!(escape_string("line\nnew"), "line\\nnew");
        assert_eq!(escape_string("tab\there"), "tab\\there");
        assert_eq!(escape_string("back\\slash"), "back\\\\slash");
    }
}