1use 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 let unreachable_headers: HashSet<String> = ctx
52 .module_classes
53 .values()
54 .flat_map(|classes| classes.iter().filter(|c| !c.header_public).map(|c| c.name.clone()))
55 .chain(
56 ctx.module_structs
57 .values()
58 .flat_map(|structs| structs.iter().filter(|s| !s.header_public).map(|s| s.name.clone())),
59 )
60 .collect();
61
62 for classes in ctx.module_classes.values_mut() {
63 classes.retain(|c| !blocked_classes.contains(c.name.as_str()));
65
66 for class in classes.iter_mut() {
67 class
69 .props
70 .retain(|p| is_property_exportable(p, &available_types));
71
72 if !class.header_public {
74 class.funcs.clear();
75 }
76 class.funcs.retain(|f| !references_any(f, &unreachable_headers));
77 filter_functions(&class.name, &mut class.funcs, &available_types, &blocked_structs, &blocked_functions);
78 }
79 }
80}
81
82fn references_any(func: &FunctionInfo, types: &HashSet<String>) -> bool {
85 let names = |p: &PropertyInfo| {
86 [p.class_name.clone(), p.meta_class_name.clone(), p.struct_name.clone()]
87 .into_iter()
88 .flatten()
89 .collect::<Vec<_>>()
90 };
91 func.params.iter().any(|param| {
92 let direct = [param.class_name.as_ref(), param.meta_class_name.as_ref(), param.struct_name.as_ref()];
93 let inner = [
94 param.inner_prop.as_deref(),
95 param.key_prop.as_deref(),
96 param.value_prop.as_deref(),
97 param.element_prop.as_deref(),
98 ];
99 direct.into_iter().flatten().any(|name| types.contains(name))
100 || inner.into_iter().flatten().flat_map(names).any(|name| types.contains(&name))
101 })
102}
103
104fn is_property_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
106 if !type_map::is_supported_type(&prop.prop_type) {
108 return false;
109 }
110
111 if prop.array_dim > 1 {
113 match prop.prop_type.as_str() {
114 "StrProperty" | "NameProperty" | "TextProperty" => return false,
115 _ => {} }
117 }
118
119 if prop.prop_flags & (CPF_NATIVE_ACCESS_PRIVATE | CPF_NATIVE_ACCESS_PROTECTED) != 0
125 && prop.prop_flags & (CPF_BLUEPRINT_VISIBLE | CPF_EDIT | CPF_BLUEPRINT_ASSIGNABLE) == 0
126 {
127 return false;
128 }
129
130 if is_delegate_type(&prop.prop_type) {
132 return is_delegate_exportable(prop, available);
133 }
134
135 if let Some(ref cls) = prop.class_name
137 && !available.contains(cls)
138 {
139 return false;
140 }
141 if let Some(ref sn) = prop.struct_name
142 && !available.contains(sn)
143 {
144 return false;
145 }
146 if let Some(ref en) = prop.enum_name
147 && !available.contains(en)
148 {
149 return false;
150 }
151 if let Some(ref iface) = prop.interface_name
152 && !available.contains(iface)
153 {
154 return false;
155 }
156 if prop.prop_type == "ClassProperty"
157 && let Some(ref meta_cls) = prop.meta_class_name
158 && !available.contains(meta_cls)
159 {
160 return false;
161 }
162
163 true
164}
165
166fn is_delegate_type(prop_type: &str) -> bool {
167 matches!(
168 prop_type,
169 "DelegateProperty" | "MulticastInlineDelegateProperty" | "MulticastSparseDelegateProperty"
170 )
171}
172
173fn is_delegate_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
175 let func_info = match &prop.func_info {
176 Some(fi) => fi,
177 None => return false, };
179
180 let params = match func_info.get("params").and_then(|p| p.as_array()) {
182 Some(params) => params,
183 None => return true, };
185
186 for param_value in params {
187 let param_type = match param_value.get("type").and_then(|t| t.as_str()) {
188 Some(t) => t,
189 None => return false,
190 };
191
192 if !type_map::is_supported_type(param_type) {
194 return false;
195 }
196
197 if is_delegate_type(param_type) {
199 return false;
200 }
201 if matches!(param_type, "ArrayProperty" | "MapProperty" | "SetProperty") {
202 return false;
203 }
204
205 if let Some(cls) = param_value.get("class_name").and_then(|v| v.as_str())
207 && !available.contains(cls)
208 {
209 return false;
210 }
211 if let Some(sn) = param_value.get("struct_name").and_then(|v| v.as_str())
212 && !available.contains(sn)
213 {
214 return false;
215 }
216 if let Some(en) = param_value.get("enum_name").and_then(|v| v.as_str())
217 && !available.contains(en)
218 {
219 return false;
220 }
221 }
222
223 true
224}
225
226fn is_container_param_exportable(param: &ParamInfo, available: &HashSet<String>) -> bool {
228 match param.prop_type.as_str() {
229 "ArrayProperty" => {
230 if let Some(ref inner) = param.inner_prop {
231 is_inner_type_exportable(inner, available)
232 } else {
233 false
234 }
235 }
236 "MapProperty" => {
237 let key_ok = param.key_prop.as_ref()
238 .map(|k| is_inner_type_exportable(k, available))
239 .unwrap_or(false);
240 let val_ok = param.value_prop.as_ref()
241 .map(|v| is_inner_type_exportable(v, available))
242 .unwrap_or(false);
243 key_ok && val_ok
244 }
245 "SetProperty" => {
246 if let Some(ref elem) = param.element_prop {
247 is_inner_type_exportable(elem, available)
248 } else {
249 false
250 }
251 }
252 _ => false,
253 }
254}
255
256fn is_inner_type_exportable(inner: &PropertyInfo, available: &HashSet<String>) -> bool {
258 if !type_map::is_supported_type(&inner.prop_type) {
259 return false;
260 }
261 if matches!(inner.prop_type.as_str(), "ArrayProperty" | "MapProperty" | "SetProperty") {
263 return false;
264 }
265 if let Some(ref cls) = inner.class_name
266 && !available.contains(cls)
267 {
268 return false;
269 }
270 if let Some(ref sn) = inner.struct_name
271 && !available.contains(sn)
272 {
273 return false;
274 }
275 if let Some(ref en) = inner.enum_name
276 && !available.contains(en)
277 {
278 return false;
279 }
280 if let Some(ref iface) = inner.interface_name
281 && !available.contains(iface)
282 {
283 return false;
284 }
285 true
286}
287
288fn filter_functions(
290 class_name: &str,
291 funcs: &mut Vec<FunctionInfo>,
292 available: &HashSet<String>,
293 blocked_structs: &HashSet<&str>,
294 blocked_functions: &[(String, String)],
295) {
296 let all_names: HashSet<String> = funcs.iter().map(|f| f.name.clone()).collect();
298
299 funcs.retain(|f| {
301 if blocked_functions.iter().any(|(c, func)| c == class_name && func == &f.name) {
303 return false;
304 }
305
306 if f.func_flags & FUNC_NATIVE == 0 {
308 return false;
309 }
310
311 if f.name.starts_with("K2_") {
313 let base_name = &f.name[3..];
314 if all_names.contains(base_name) {
315 return false;
316 }
317 }
318
319 for param in &f.params {
321 if !type_map::is_supported_type(¶m.prop_type) {
322 return false;
323 }
324 if is_delegate_type(¶m.prop_type) {
326 return false;
327 }
328 if matches!(param.prop_type.as_str(), "ArrayProperty" | "MapProperty" | "SetProperty")
330 && !is_container_param_exportable(param, available)
331 {
332 return false;
333 }
334 if let Some(ref cls) = param.class_name
335 && !available.contains(cls)
336 {
337 return false;
338 }
339 if let Some(ref sn) = param.struct_name
340 && (!available.contains(sn) || blocked_structs.contains(sn.as_str()))
341 {
342 return false;
343 }
344 if let Some(ref en) = param.enum_name
345 && !available.contains(en)
346 {
347 return false;
348 }
349 if let Some(ref iface) = param.interface_name
350 && !available.contains(iface)
351 {
352 return false;
353 }
354 if param.prop_type == "ClassProperty"
355 && let Some(ref meta_cls) = param.meta_class_name
356 && !available.contains(meta_cls)
357 {
358 return false;
359 }
360 }
361
362 true
363 });
364
365 for f in funcs.iter_mut() {
367 f.ue_name = f.name.clone();
368 }
369
370 let mut name_counts: HashMap<String, usize> = HashMap::new();
372 for f in funcs.iter() {
373 *name_counts.entry(f.name.clone()).or_default() += 1;
374 }
375
376 let mut name_indices: HashMap<String, usize> = HashMap::new();
377 for f in funcs.iter_mut() {
378 if let Some(&count) = name_counts.get(&f.name)
379 && count > 1
380 {
381 let idx = name_indices.entry(f.name.clone()).or_insert(0);
382 *idx += 1;
383 f.name = format!("{}_{}", f.name, idx);
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use rusteal_ue_flags::{CPF_BLUEPRINT_READ_ONLY, CPF_BLUEPRINT_VISIBLE};
392
393 fn object_prop(name: &str, flags: u64) -> PropertyInfo {
394 serde_json::from_value(serde_json::json!({
395 "name": name,
396 "type": "ObjectProperty",
397 "prop_flags": flags,
398 "class_name": "CharacterMovementComponent",
399 }))
400 .unwrap()
401 }
402
403 #[test]
404 fn private_properties_only_when_blueprint_can_reach_them() {
405 let available: HashSet<String> = ["CharacterMovementComponent".to_string()].into();
406 let read_only = CPF_BLUEPRINT_VISIBLE | CPF_BLUEPRINT_READ_ONLY;
407
408 assert!(is_property_exportable(&object_prop("Public", 0), &available));
409 assert!(!is_property_exportable(&object_prop("Hidden", CPF_NATIVE_ACCESS_PRIVATE), &available));
410 assert!(!is_property_exportable(&object_prop("Guarded", CPF_NATIVE_ACCESS_PROTECTED), &available));
411 assert!(is_property_exportable(
412 &object_prop("CharacterMovement", CPF_NATIVE_ACCESS_PRIVATE | read_only),
413 &available
414 ));
415 assert!(is_property_exportable(
416 &object_prop("Guarded", CPF_NATIVE_ACCESS_PROTECTED | CPF_BLUEPRINT_VISIBLE),
417 &available
418 ));
419 assert!(is_property_exportable(
420 &object_prop("Editable", CPF_NATIVE_ACCESS_PROTECTED | CPF_EDIT),
421 &available
422 ));
423 assert!(is_property_exportable(
424 &object_prop("Assignable", CPF_NATIVE_ACCESS_PROTECTED | CPF_BLUEPRINT_ASSIGNABLE),
425 &available
426 ));
427 }
428
429 fn function(params: serde_json::Value) -> FunctionInfo {
430 serde_json::from_value(serde_json::json!({
431 "name": "Probe",
432 "func_flags": FUNC_NATIVE,
433 "params": params,
434 }))
435 .unwrap()
436 }
437
438 fn param(fields: serde_json::Value) -> serde_json::Value {
439 let mut param = serde_json::json!({
440 "name": "P", "type": "ObjectProperty", "prop_flags": 0,
441 "enum_name": null, "enum_cpp_name": null, "enum_cpp_form": null,
442 "enum_underlying_type": null, "class_name": null, "meta_class_name": null,
443 "struct_name": null, "interface_name": null, "func_info": null,
444 "inner_prop": null, "key_prop": null, "value_prop": null,
445 "element_prop": null, "default": null,
446 });
447 for (key, value) in fields.as_object().unwrap() {
448 param[key] = value.clone();
449 }
450 param
451 }
452
453 #[test]
454 fn functions_naming_unreachable_headers_drop_out() {
455 let internal: HashSet<String> = ["NiagaraDataInterfaceArrayMesh".to_string()].into();
456 let takes = |fields| function(serde_json::json!([param(fields)]));
457
458 assert!(references_any(&takes(serde_json::json!({"class_name": "NiagaraDataInterfaceArrayMesh"})), &internal));
459 assert!(references_any(
460 &takes(serde_json::json!({
461 "type": "ArrayProperty",
462 "inner_prop": {"name": "P", "type": "ObjectProperty", "prop_flags": 0,
463 "class_name": "NiagaraDataInterfaceArrayMesh"},
464 })),
465 &internal
466 ));
467 assert!(!references_any(&takes(serde_json::json!({"class_name": "NiagaraComponent"})), &internal));
468 assert!(!references_any(&function(serde_json::json!([])), &internal));
469 }
470}