rusteal_codegen/
filter.rs1use std::collections::{HashMap, HashSet};
4
5use crate::config::Blocklist;
6use crate::context::CodegenContext;
7use crate::schema::*;
8use crate::type_map;
9
10pub fn apply_filters(ctx: &mut CodegenContext, blocklist: &Blocklist) {
12 let blocked_classes: HashSet<&str> = blocklist.classes.iter().map(|s| s.as_str()).collect();
14 let blocked_structs: HashSet<&str> = blocklist.structs.iter().map(|s| s.as_str()).collect();
15 let blocked_functions: Vec<(String, String)> = blocklist.function_tuples();
16
17 for cls in &blocklist.classes {
19 ctx.classes.remove(cls);
20 }
21
22 let available_types: HashSet<String> = ctx
27 .module_classes
28 .iter()
29 .filter(|(module, _)| ctx.enabled_modules.contains(*module))
30 .flat_map(|(_, classes)| classes.iter().map(|c| c.name.clone()))
31 .filter(|name| !blocked_classes.contains(name.as_str()))
32 .chain(
33 ctx.module_structs
34 .iter()
35 .filter(|(module, _)| ctx.enabled_modules.contains(*module))
36 .flat_map(|(_, structs)| structs.iter().map(|s| s.name.clone()))
37 .filter(|name| !blocked_structs.contains(name.as_str())),
38 )
39 .chain(
40 ctx.module_enums
41 .iter()
42 .filter(|(module, _)| ctx.enabled_modules.contains(*module))
43 .flat_map(|(_, enums)| enums.iter().map(|e| e.name.clone())),
44 )
45 .collect();
46
47 for classes in ctx.module_classes.values_mut() {
48 classes.retain(|c| !blocked_classes.contains(c.name.as_str()));
50
51 for class in classes.iter_mut() {
52 class
54 .props
55 .retain(|p| is_property_exportable(p, &available_types));
56
57 filter_functions(&class.name, &mut class.funcs, &available_types, &blocked_structs, &blocked_functions);
59 }
60 }
61}
62
63fn is_property_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
65 if !type_map::is_supported_type(&prop.prop_type) {
67 return false;
68 }
69
70 if prop.array_dim > 1 {
72 match prop.prop_type.as_str() {
73 "StrProperty" | "NameProperty" | "TextProperty" => return false,
74 _ => {} }
76 }
77
78 if prop.prop_flags & (CPF_NATIVE_ACCESS_PRIVATE | CPF_NATIVE_ACCESS_PROTECTED) != 0
81 && prop.prop_flags & CPF_BLUEPRINT_VISIBLE == 0
82 {
83 return false;
84 }
85
86 if is_delegate_type(&prop.prop_type) {
88 return is_delegate_exportable(prop, available);
89 }
90
91 if let Some(ref cls) = prop.class_name
93 && !available.contains(cls)
94 {
95 return false;
96 }
97 if let Some(ref sn) = prop.struct_name
98 && !available.contains(sn)
99 {
100 return false;
101 }
102 if let Some(ref en) = prop.enum_name
103 && !available.contains(en)
104 {
105 return false;
106 }
107 if let Some(ref iface) = prop.interface_name
108 && !available.contains(iface)
109 {
110 return false;
111 }
112 if prop.prop_type == "ClassProperty"
113 && let Some(ref meta_cls) = prop.meta_class_name
114 && !available.contains(meta_cls)
115 {
116 return false;
117 }
118
119 true
120}
121
122fn is_delegate_type(prop_type: &str) -> bool {
123 matches!(
124 prop_type,
125 "DelegateProperty" | "MulticastInlineDelegateProperty" | "MulticastSparseDelegateProperty"
126 )
127}
128
129fn is_delegate_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
131 let func_info = match &prop.func_info {
132 Some(fi) => fi,
133 None => return false, };
135
136 let params = match func_info.get("params").and_then(|p| p.as_array()) {
138 Some(params) => params,
139 None => return true, };
141
142 for param_value in params {
143 let param_type = match param_value.get("type").and_then(|t| t.as_str()) {
144 Some(t) => t,
145 None => return false,
146 };
147
148 if !type_map::is_supported_type(param_type) {
150 return false;
151 }
152
153 if is_delegate_type(param_type) {
155 return false;
156 }
157 if matches!(param_type, "ArrayProperty" | "MapProperty" | "SetProperty") {
158 return false;
159 }
160
161 if let Some(cls) = param_value.get("class_name").and_then(|v| v.as_str())
163 && !available.contains(cls)
164 {
165 return false;
166 }
167 if let Some(sn) = param_value.get("struct_name").and_then(|v| v.as_str())
168 && !available.contains(sn)
169 {
170 return false;
171 }
172 if let Some(en) = param_value.get("enum_name").and_then(|v| v.as_str())
173 && !available.contains(en)
174 {
175 return false;
176 }
177 }
178
179 true
180}
181
182fn is_container_param_exportable(param: &ParamInfo, available: &HashSet<String>) -> bool {
184 match param.prop_type.as_str() {
185 "ArrayProperty" => {
186 if let Some(ref inner) = param.inner_prop {
187 is_inner_type_exportable(inner, available)
188 } else {
189 false
190 }
191 }
192 "MapProperty" => {
193 let key_ok = param.key_prop.as_ref()
194 .map(|k| is_inner_type_exportable(k, available))
195 .unwrap_or(false);
196 let val_ok = param.value_prop.as_ref()
197 .map(|v| is_inner_type_exportable(v, available))
198 .unwrap_or(false);
199 key_ok && val_ok
200 }
201 "SetProperty" => {
202 if let Some(ref elem) = param.element_prop {
203 is_inner_type_exportable(elem, available)
204 } else {
205 false
206 }
207 }
208 _ => false,
209 }
210}
211
212fn is_inner_type_exportable(inner: &PropertyInfo, available: &HashSet<String>) -> bool {
214 if !type_map::is_supported_type(&inner.prop_type) {
215 return false;
216 }
217 if matches!(inner.prop_type.as_str(), "ArrayProperty" | "MapProperty" | "SetProperty") {
219 return false;
220 }
221 if let Some(ref cls) = inner.class_name
222 && !available.contains(cls)
223 {
224 return false;
225 }
226 if let Some(ref sn) = inner.struct_name
227 && !available.contains(sn)
228 {
229 return false;
230 }
231 if let Some(ref en) = inner.enum_name
232 && !available.contains(en)
233 {
234 return false;
235 }
236 if let Some(ref iface) = inner.interface_name
237 && !available.contains(iface)
238 {
239 return false;
240 }
241 true
242}
243
244fn filter_functions(
246 class_name: &str,
247 funcs: &mut Vec<FunctionInfo>,
248 available: &HashSet<String>,
249 blocked_structs: &HashSet<&str>,
250 blocked_functions: &[(String, String)],
251) {
252 let all_names: HashSet<String> = funcs.iter().map(|f| f.name.clone()).collect();
254
255 funcs.retain(|f| {
257 if blocked_functions.iter().any(|(c, func)| c == class_name && func == &f.name) {
259 return false;
260 }
261
262 if f.func_flags & FUNC_NATIVE == 0 {
264 return false;
265 }
266
267 if f.name.starts_with("K2_") {
269 let base_name = &f.name[3..];
270 if all_names.contains(base_name) {
271 return false;
272 }
273 }
274
275 for param in &f.params {
277 if !type_map::is_supported_type(¶m.prop_type) {
278 return false;
279 }
280 if is_delegate_type(¶m.prop_type) {
282 return false;
283 }
284 if matches!(param.prop_type.as_str(), "ArrayProperty" | "MapProperty" | "SetProperty")
286 && !is_container_param_exportable(param, available)
287 {
288 return false;
289 }
290 if let Some(ref cls) = param.class_name
291 && !available.contains(cls)
292 {
293 return false;
294 }
295 if let Some(ref sn) = param.struct_name
296 && (!available.contains(sn) || blocked_structs.contains(sn.as_str()))
297 {
298 return false;
299 }
300 if let Some(ref en) = param.enum_name
301 && !available.contains(en)
302 {
303 return false;
304 }
305 if let Some(ref iface) = param.interface_name
306 && !available.contains(iface)
307 {
308 return false;
309 }
310 if param.prop_type == "ClassProperty"
311 && let Some(ref meta_cls) = param.meta_class_name
312 && !available.contains(meta_cls)
313 {
314 return false;
315 }
316 }
317
318 true
319 });
320
321 for f in funcs.iter_mut() {
323 f.ue_name = f.name.clone();
324 }
325
326 let mut name_counts: HashMap<String, usize> = HashMap::new();
328 for f in funcs.iter() {
329 *name_counts.entry(f.name.clone()).or_default() += 1;
330 }
331
332 let mut name_indices: HashMap<String, usize> = HashMap::new();
333 for f in funcs.iter_mut() {
334 if let Some(&count) = name_counts.get(&f.name)
335 && count > 1
336 {
337 let idx = name_indices.entry(f.name.clone()).or_insert(0);
338 *idx += 1;
339 f.name = format!("{}_{}", f.name, idx);
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use rusteal_ue_flags::{CPF_BLUEPRINT_READ_ONLY, CPF_BLUEPRINT_VISIBLE};
348
349 fn object_prop(name: &str, flags: u64) -> PropertyInfo {
350 serde_json::from_value(serde_json::json!({
351 "name": name,
352 "type": "ObjectProperty",
353 "prop_flags": flags,
354 "class_name": "CharacterMovementComponent",
355 }))
356 .unwrap()
357 }
358
359 #[test]
360 fn private_properties_only_when_blueprint_can_reach_them() {
361 let available: HashSet<String> = ["CharacterMovementComponent".to_string()].into();
362 let read_only = CPF_BLUEPRINT_VISIBLE | CPF_BLUEPRINT_READ_ONLY;
363
364 assert!(is_property_exportable(&object_prop("Public", 0), &available));
365 assert!(!is_property_exportable(&object_prop("Hidden", CPF_NATIVE_ACCESS_PRIVATE), &available));
366 assert!(!is_property_exportable(&object_prop("Guarded", CPF_NATIVE_ACCESS_PROTECTED), &available));
367 assert!(is_property_exportable(
368 &object_prop("CharacterMovement", CPF_NATIVE_ACCESS_PRIVATE | read_only),
369 &available
370 ));
371 assert!(is_property_exportable(
372 &object_prop("Guarded", CPF_NATIVE_ACCESS_PROTECTED | CPF_BLUEPRINT_VISIBLE),
373 &available
374 ));
375 }
376}