altv_internal_sdk 15.0.0-dev.46

An internal crate for alt:V module. Not intended for direct use.
Documentation
use std::fs;

fn main() {
    let out_dir = std::env::var("OUT_DIR").unwrap();
    generate_cpp_to_rust_bindings(&out_dir);
    build_rust();
}

fn generate_cpp_to_rust_bindings(out_dir: &str) {
    let hash = get_sdk_hash();
    write_sdk_hash(hash, out_dir);

    generate_rust_enum_from_cpp(
        "BaseObjectType",
        "u8",
        "cpp-sdk/objects/IBaseObject.h",
        "enum class Type : uint8_t",
        "base_object_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "EventType",
        "u16",
        "cpp-sdk/events/CEvent.h",
        "enum class Type : uint16_t",
        "event_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "MValueType",
        "u8",
        "cpp-sdk/types/MValue.h",
        "enum class Type : uint8_t",
        "mvalue_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "ColShapeType",
        "u8",
        "cpp-sdk/script-objects/IColShape.h",
        "enum class ColShapeType : uint8_t",
        "col_shape_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "BlipType",
        "u8",
        "cpp-sdk/script-objects/IBlip.h",
        "enum class BlipType",
        "blip_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "MarkerType",
        "u32",
        "cpp-sdk/script-objects/IMarker.h",
        "enum class MarkerType",
        "marker_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "PlayerBodyPart",
        "i8",
        "cpp-sdk/events/CWeaponDamageEvent.h",
        "enum class BodyPart : int8_t",
        "player_body_part.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "PlayerConnectDeniedReason",
        "u8",
        "cpp-sdk/events/CPlayerConnectDeniedEvent.h",
        "enum Reason: uint8_t",
        "player_connect_denied_reason.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "ExplosionType",
        "i8",
        "cpp-sdk/events/CExplosionEvent.h",
        "enum class ExplosionType : int8_t",
        "explosion_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "VehicleModelType",
        "u8",
        "cpp-sdk/types/VehicleModelInfo.h",
        "enum class Type : uint8_t",
        "vehicle_model_type.rs",
        out_dir,
    );

    generate_rust_enum_from_cpp(
        "ConfigValueType",
        "u8",
        "cpp-sdk/deps/ConfigBase.h",
        "enum class Type : uint8_t",
        "config_value_type.rs",
        out_dir,
    );
}

fn build_rust() {
    let path = std::path::PathBuf::from("src");

    let mut build = autocxx_build::Builder::new("src/lib.rs", [&path])
        .extra_clang_args(&["-std=c++20"])
        .build()
        .unwrap();

    let flags = if cfg!(target_os = "windows") {
        ["/std:c++20"]
    } else if cfg!(target_os = "linux") {
        ["-std=c++2a"]
    } else {
        panic!("unsupported target_os");
    };

    for flag in flags {
        build.flag(flag);
    }

    build.compile("altv_sdk");
}

fn generate_rust_enum_from_cpp(
    enum_name: &str,
    enum_type: &str,
    path: &str,
    str_to_find: &str,
    write_to: &str,
    out_dir: &str,
) {
    let content = String::from_utf8(fs::read(path).unwrap()).unwrap();
    let idx = content.find(str_to_find).unwrap();

    let mut open_brace = false;
    let mut start_idx = 0;
    let mut end_idx = 0;
    let chars = content.get(idx..).unwrap().as_bytes();

    for (idx, char) in chars.iter().enumerate() {
        if open_brace {
            if *char == b'}' {
                end_idx = idx - 1;
                break;
            }

            continue;
        }

        if *char == b'{' {
            open_brace = true;
            start_idx = idx + 1;
        }
    }

    if !open_brace {
        panic!("cannot find open brace of {str_to_find:?}")
    }

    let mut try_from_variants = vec![];
    let mut result_string = String::from_utf8_lossy(&chars[start_idx..=end_idx])
        .to_string()
        .split('\n')
        .filter_map(|val| {
            val.get(2..)
                .map(|v| v.trim())
                .and_then(|v| if v.is_empty() { None } else { Some(v) })
        })
        .map(|v| {
            let pascal_case_variant = upper_to_pascal_case(v);

            if !pascal_case_variant.starts_with("//") {
                let v = pascal_case_variant.clone();
                let v = v.split(',').next().unwrap().to_string();
                let v = v.split(' ').next().unwrap().to_string();
                try_from_variants.push(v);
            }

            format!("    {}", pascal_case_variant)
        })
        .collect::<Vec<String>>()
        .join("\n");

    if result_string.ends_with(',') {
        result_string.remove(result_string.len() - 1);
    }

    let try_from_variants = try_from_variants
        .into_iter()
        .map(|v| format!("    v if v == Self::{v} as {enum_type} => Self::{v},"))
        .collect::<Vec<String>>()
        .join("\n");

    fs::write(
        format!("{out_dir}/{write_to}"),
        format!(
            "// auto-generated from build.rs\n\n\
            #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
            pub enum {enum_name} {{\n\
                {result_string},\n\
            }}\n\
            \n\
            impl TryFrom<{enum_type}> for {enum_name} {{\n\
                type Error = ();\n\
                fn try_from(v: {enum_type}) -> Result<Self, Self::Error> {{\n\
                    Ok(match v {{\n\
                        {try_from_variants}\
                        _ => return Err(()),\n\
                    }})\n\
                }}\n\
            }}\n\
            ",
        ),
    )
    .unwrap();
}

fn upper_to_pascal_case(s: &str) -> String {
    let mut result = String::new();
    let mut chars = s.chars();
    result.push(chars.next().unwrap().to_ascii_uppercase());

    let mut next_char_upper = false;
    for c in chars {
        if next_char_upper {
            next_char_upper = false;
            result.push(c.to_ascii_uppercase());
            continue;
        }

        if c == '_' {
            next_char_upper = true;
            continue;
        }

        result.push(c.to_ascii_lowercase());
    }
    result
}

fn get_sdk_hash() -> String {
    const SDH_HASH_STRING_START: &str = "ALT_SDK_VERSION \"";

    let content = fs::read("cpp-sdk-version.h").unwrap();
    let content = String::from_utf8_lossy(&content[..]).to_string();

    let idx = content.find(SDH_HASH_STRING_START).unwrap();
    let start_of_hash = idx + SDH_HASH_STRING_START.len();
    let end_of_hash = start_of_hash + 7;

    content[start_of_hash..end_of_hash].to_string()
}

fn write_sdk_hash(hash: String, out_dir: &str) {
    fs::write(
        format!("{out_dir}/cpp_sdk_version.rs"),
        format!("pub const ALT_SDK_VERSION: &[u8; 8usize] = b\"{hash}\\0\";"),
    )
    .unwrap();
}