From 9fc7e5afa352218e8a973094855e8d28d48dce59 Mon Sep 17 00:00:00 2001 From: Seemann Date: Mon, 28 Jul 2025 00:08:45 -0400 Subject: [PATCH 1/4] ide parser --- Cargo.lock | 16 ++++-- Cargo.toml | 5 +- src/ide/ffi.rs | 67 +++++++++++++++++++++++ src/ide/mod.rs | 2 + src/ide/model_list.rs | 123 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 6 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 src/ide/ffi.rs create mode 100644 src/ide/mod.rs create mode 100644 src/ide/model_list.rs diff --git a/Cargo.lock b/Cargo.lock index fe7fc84..8e7802b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -420,6 +420,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "gta-ide-parser" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9858affd82e625592bed1089da5a8acaab4240a11fff78efc1cd2c89d99dc58" +dependencies = [ + "nom", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -691,9 +700,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.3.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "minimal-lexical" @@ -949,13 +958,14 @@ dependencies = [ [[package]] name = "sanny_builder_core" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "base64", "cached", "const_format", "ctor", + "gta-ide-parser", "hotwatch", "lazy_static", "lexical-core", diff --git a/Cargo.toml b/Cargo.toml index 312988e..7943f8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sanny_builder_core" -version = "0.4.1" +version = "0.4.2" authors = ["Seemann "] edition = "2021" @@ -33,4 +33,5 @@ normpath = "1.2" lines_lossy = "0.1.0" zip = { git = "https://github.com/x87/zip.git" } const_format = "0.2.32" -cached = "0.50.0" \ No newline at end of file +cached = "0.50.0" +gta-ide-parser = "0.0.4" \ No newline at end of file diff --git a/src/ide/ffi.rs b/src/ide/ffi.rs new file mode 100644 index 0000000..1a9a5dd --- /dev/null +++ b/src/ide/ffi.rs @@ -0,0 +1,67 @@ +use libc::c_char; + +use super::model_list::ModelList; + +use crate::common_ffi::*; + +#[no_mangle] +pub extern "C" fn model_list_new() -> *mut ModelList { + ptr_new(ModelList::new()) +} + +#[no_mangle] +pub unsafe extern "C" fn model_list_free(list: *mut ModelList) { + ptr_free(list) +} + +#[no_mangle] +pub unsafe extern "C" fn model_list_load_from_file(list: *mut ModelList, file_name: PChar) -> bool { + boolclosure!({ + list.as_mut()?.load_from_file(pchar_to_str(file_name)?); + Some(()) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn model_list_get_by_id( + list: *mut ModelList, + id: i32, + out: *mut PChar, +) -> bool { + boolclosure!({ + *out = list.as_mut()?.find_by_id(id)?.as_ptr(); + Some(()) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn model_list_get_by_name( + list: *mut ModelList, + name: PChar, + out_id: *mut i32, + out_type: *mut u8, +) -> bool { + boolclosure!({ + let name = pchar_to_str(name)?; + let model = list.as_mut()?.find_by_name(&name)?; + *out_id = model.id; + *out_type = model.r#type as u8; + Some(()) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn model_list_filter_names( + list: *mut ModelList, + needle: PChar, + dict: *mut crate::dictionary::dictionary_str_by_num::DictStrByNum, +) -> bool { + boolclosure!({ + let needle = pchar_to_str(needle)?; + let results = list.as_mut()?.filter_by_name(&needle); + for (name, id) in results { + dict.as_mut()?.add(id, name); + } + Some(()) + }) +} diff --git a/src/ide/mod.rs b/src/ide/mod.rs new file mode 100644 index 0000000..c7c0a11 --- /dev/null +++ b/src/ide/mod.rs @@ -0,0 +1,2 @@ +pub mod model_list; +pub mod ffi; \ No newline at end of file diff --git a/src/ide/model_list.rs b/src/ide/model_list.rs new file mode 100644 index 0000000..4c3af98 --- /dev/null +++ b/src/ide/model_list.rs @@ -0,0 +1,123 @@ +use ctor::ctor; +use simplelog::*; +use std::{ + collections::HashMap, + ffi::{c_char, CString}, + path::Path, +}; + +#[repr(C)] +#[derive(Copy, Clone, PartialEq)] +pub enum ModelType { + Object, + Vehicle, + Ped, +} + +#[repr(C)] +pub struct Model { + pub id: i32, + pub r#type: ModelType, +} + +pub struct ModelList { + pub _by_id: HashMap, + pub _by_name: HashMap, + pub model_names: Vec, +} + +impl ModelList { + pub fn new() -> Self { + Self { + _by_id: HashMap::new(), + _by_name: HashMap::new(), + model_names: Vec::new(), + } + } + + pub fn load_from_file(&mut self, file_name: &str) { + let Ok(content) = std::fs::read_to_string(file_name) else { + log::error!("Failed to read file: {}", file_name); + return; + }; + let Ok(ide) = gta_ide_parser::parse(&content) else { + log::error!("Failed to parse IDE: {}", file_name); + return; + }; + for (section_name, lines) in ide { + if ["txdp", "path", "2dfx"].contains(§ion_name.as_str()) { + continue; + } + for line in lines { + let Ok(id) = line[0].parse::() else { + log::error!("Failed to parse ID: {} in {}", line[0], file_name); + continue; + }; + let name = line[1].to_string(); + let r#type = match section_name.as_str() { + "objs" | "tobj" | "anim" | "tanm" => ModelType::Object, + "cars" => ModelType::Vehicle, + "peds" => ModelType::Ped, + _ => { + continue; + } + }; + + self.model_names.push(name.to_uppercase()); + self._by_id + .insert(id, CString::new(name.to_uppercase()).unwrap()); + self._by_name + .insert(name.to_ascii_lowercase(), Model { id, r#type }); + } + } + } + + pub fn find_by_id(&self, id: i32) -> Option<&CString> { + self._by_id.get(&id) + } + + pub fn find_by_name(&self, name: &str) -> Option<&Model> { + let needle = &name.to_ascii_lowercase(); + match needle.as_str() { + // old parser did not properly handle missing commas, resulting in broken names + "emperoremperor" => { + return Some(&Model { + id: 585, + r#type: ModelType::Vehicle, + }) + } + "wayfarerwayfarer" => { + return Some(&Model { + id: 586, + r#type: ModelType::Vehicle, + }) + } + "dodododo" => { + return Some(&Model { + id: 593, + r#type: ModelType::Vehicle, + }) + } + _ => self._by_name.get(needle), + } + } + + pub fn filter_by_name(&self, needle: &str) -> Vec<(CString, i32)> { + let needle = needle.to_ascii_uppercase(); + let mut results = Vec::new(); + for name in self.model_names.iter() { + if name.contains(&needle) { + if let Some(model) = self.find_by_name(name) { + if model.r#type == ModelType::Object { + continue; // Skip objects + } + results.push((CString::new(name.clone()).unwrap(), model.id)); + // if results.len() >= 200 { + // break; // Limit results to 200 + // } + } + } + } + results + } +} diff --git a/src/lib.rs b/src/lib.rs index a66a12a..0a01f90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub mod v4; pub mod source_map; pub mod preprocessor; pub mod sanny_update; +pub mod ide; #[ctor] fn main() { @@ -37,5 +38,5 @@ fn main() { std::fs::File::create(cwd.join("core.log")).unwrap(), ); - log::info!("core library loaded"); + log::info!("core library {} loaded", env!("CARGO_PKG_VERSION")); } From 9af0d0e98dd1ac55d60b62a92af6f4e9ce4c041e Mon Sep 17 00:00:00 2001 From: Seemann Date: Mon, 28 Jul 2025 00:09:09 -0400 Subject: [PATCH 2/4] support array arguments in functions --- src/language_service/ffi.rs | 7 +++++++ src/language_service/scanner.rs | 7 ++++++- src/parser/declaration.rs | 20 ++++++++++++++++---- src/parser/interface.rs | 1 + 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/language_service/ffi.rs b/src/language_service/ffi.rs index e6a0a07..76dfdc8 100644 --- a/src/language_service/ffi.rs +++ b/src/language_service/ffi.rs @@ -181,6 +181,13 @@ pub unsafe extern "C" fn language_service_format_function_signature( .map(|param|{ let type_token = token_str(line, ¶m._type); let name_token = param.name.as_ref().map(|name| token_str(line, name)); + let size_token = param.size.as_ref().map(|size| token_str(line, size)); + + let type_token = if let Some(size) = size_token { + format!("{}[{}]", type_token, size) + } else { + type_token.to_string() + }; match name_token { Some(name) => format!("\"{}: {}\"", name, type_token), diff --git a/src/language_service/scanner.rs b/src/language_service/scanner.rs index 52cccbf..f31fd78 100644 --- a/src/language_service/scanner.rs +++ b/src/language_service/scanner.rs @@ -731,13 +731,18 @@ fn function_params_and_return_types(line: &str, signature: &FunctionSignature) - .map(|param| { let type_token = token_str(line, ¶m._type); let name_token = param.name.as_ref().map(|name| token_str(line, name)); + let size_token = param.size.as_ref().map(|size| token_str(line, size)); + let type_token = if let Some(size) = size_token { + format!("{}[{}]", type_token, size) + } else { + type_token.to_string() + }; match name_token { Some(name) => format!("{}: {}", name, type_token), None => format!("{}", type_token), } }) - // .map(|param| [token_str(line, ¶m.name), token_str(line, ¶m._type)].join(": ")) .collect::>() .join(", "); diff --git a/src/parser/declaration.rs b/src/parser/declaration.rs index 5bcee9d..44c5b3e 100644 --- a/src/parser/declaration.rs +++ b/src/parser/declaration.rs @@ -61,22 +61,29 @@ fn function_arguments(s: Span) -> R> { helpers::ws(tag("(")), separated_list0( helpers::ws(tag(",")), - consumed(pair( - opt(terminated( + consumed(tuple(( + opt(delimited( + helpers::ws(opt(tag("..."))), // param names are optional in define function helpers::ws(literal::identifier), helpers::ws(tag(":")), )), helpers::ws(literal::identifier), - )), + opt(delimited( + helpers::ws(tag("[")), + helpers::ws(literal::number), + helpers::ws(tag("]")), + )), + ))), ), helpers::ws(tag(")")), ), |args| { args.into_iter() - .map(|(span, (name, _type))| FunctionParameter { + .map(|(span, (name, _type, size))| FunctionParameter { name, _type, + size, token: Token::from(span, SyntaxKind::LocalVariable), }) .collect() @@ -347,6 +354,7 @@ end"#, len: 3, syntax_kind: SyntaxKind::Identifier }, + size: None, token: Token { start: 14, len: 6, @@ -398,6 +406,7 @@ end"#, len: 3, syntax_kind: SyntaxKind::Identifier }, + size: None, token: Token { start: 14, len: 6, @@ -415,6 +424,7 @@ end"#, len: 6, syntax_kind: SyntaxKind::Identifier }, + size: None, token: Token { start: 22, len: 9, @@ -469,6 +479,7 @@ end"#, len: 3, syntax_kind: SyntaxKind::Identifier }, + size: None, token: Token { start: 14, len: 6, @@ -486,6 +497,7 @@ end"#, len: 6, syntax_kind: SyntaxKind::Identifier }, + size: None, token: Token { start: 22, len: 9, diff --git a/src/parser/interface.rs b/src/parser/interface.rs index a95f374..3e073b8 100644 --- a/src/parser/interface.rs +++ b/src/parser/interface.rs @@ -224,6 +224,7 @@ pub enum FunctionCC { pub struct FunctionParameter { pub name: Option, pub _type: Token, + pub size: Option, pub token: Token, } #[derive(Debug, PartialEq, Clone)] From f93f18a8c4036c76d0e98af737184b089d07d40a Mon Sep 17 00:00:00 2001 From: Seemann Date: Mon, 28 Jul 2025 00:09:45 -0400 Subject: [PATCH 3/4] update log if some enums could not be parsed --- src/namespaces/namespaces.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/namespaces/namespaces.rs b/src/namespaces/namespaces.rs index 31b0371..7d6a0f2 100644 --- a/src/namespaces/namespaces.rs +++ b/src/namespaces/namespaces.rs @@ -150,7 +150,18 @@ impl Namespaces { use crate::namespaces::enum_parser::{parse_enums, EnumItems}; let content = std::fs::read_to_string(file_name).ok()?; - let (_, enums) = parse_enums(&content).ok()?; + let enums = match parse_enums(&content) { + Ok((rest, enums)) => { + if !rest.is_empty() { + log::warn!("Some enums could not be parsed: {rest} in file: {file_name}"); + } + enums + } + Err(e) => { + log::error!("Failed on parsing file {file_name}: {e}"); + return None; + } + }; for e in enums { let mut members = HashMap::new(); match e.items { From 5d133657d59bc8969da8cdfe4cef7af6b37357ed Mon Sep 17 00:00:00 2001 From: Seemann Date: Mon, 28 Jul 2025 20:40:22 -0400 Subject: [PATCH 4/4] add more ide sections to read --- src/ide/model_list.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ide/model_list.rs b/src/ide/model_list.rs index 4c3af98..fc8b912 100644 --- a/src/ide/model_list.rs +++ b/src/ide/model_list.rs @@ -12,6 +12,8 @@ pub enum ModelType { Object, Vehicle, Ped, + Weapon, + Hier, } #[repr(C)] @@ -58,6 +60,8 @@ impl ModelList { "objs" | "tobj" | "anim" | "tanm" => ModelType::Object, "cars" => ModelType::Vehicle, "peds" => ModelType::Ped, + "weap" => ModelType::Weapon, + "hier" => ModelType::Hier, _ => { continue; }