1use std::collections::{HashMap, HashSet};
2
3use crate::config::Blocklist;
4use crate::context::CodegenContext;
5use crate::schema::*;
6use crate::type_map;
7
8pub fn apply_filters(ctx: &mut CodegenContext, blocklist: &Blocklist, editor: bool) {
9 let blocked_classes: HashSet<&str> = blocklist.classes.iter().map(|s| s.as_str()).collect();
10 let blocked_structs: HashSet<&str> = blocklist.structs.iter().map(|s| s.as_str()).collect();
11 let blocked_functions: Vec<(String, String)> = blocklist.function_tuples();
12
13 for cls in &blocklist.classes {
14 ctx.classes.remove(cls);
15 }
16
17 let available_types: HashSet<String> = ctx
18 .module_classes
19 .iter()
20 .filter(|(module, _)| ctx.enabled_modules.contains(*module))
21 .flat_map(|(_, classes)| classes.iter().map(|c| c.name.clone()))
22 .filter(|name| !blocked_classes.contains(name.as_str()))
23 .chain(
24 ctx.module_structs
25 .iter()
26 .filter(|(module, _)| ctx.enabled_modules.contains(*module))
27 .flat_map(|(_, structs)| structs.iter().map(|s| s.name.clone()))
28 .filter(|name| !blocked_structs.contains(name.as_str())),
29 )
30 .chain(
31 ctx.module_enums
32 .iter()
33 .filter(|(module, _)| ctx.enabled_modules.contains(*module))
34 .flat_map(|(_, enums)| enums.iter().map(|e| e.name.clone())),
35 )
36 .collect();
37
38 let unreachable_headers: HashSet<String> = ctx
39 .module_classes
40 .values()
41 .flat_map(|classes| {
42 classes
43 .iter()
44 .filter(|c| !c.header_public)
45 .map(|c| c.name.clone())
46 })
47 .chain(ctx.module_structs.values().flat_map(|structs| {
48 structs
49 .iter()
50 .filter(|s| !s.header_public)
51 .map(|s| s.name.clone())
52 }))
53 .collect();
54
55 for classes in ctx.module_classes.values_mut() {
56 classes.retain(|c| !blocked_classes.contains(c.name.as_str()));
57
58 for class in classes.iter_mut() {
59 class
60 .props
61 .retain(|p| is_property_exportable(p, &available_types));
62
63 if !class.header_public {
64 class.funcs.clear();
65 }
66
67 class
68 .funcs
69 .retain(|f| !references_any(f, &unreachable_headers));
70
71 filter_functions(
72 &class.name,
73 &mut class.funcs,
74 &available_types,
75 &blocked_structs,
76 &blocked_functions,
77 editor,
78 );
79 }
80 }
81}
82
83fn references_any(func: &FunctionInfo, types: &HashSet<String>) -> bool {
84 let names = |p: &PropertyInfo| {
85 [
86 p.class_name.clone(),
87 p.meta_class_name.clone(),
88 p.struct_name.clone(),
89 ]
90 .into_iter()
91 .flatten()
92 .collect::<Vec<_>>()
93 };
94
95 func.params.iter().any(|param| {
96 let direct = [
97 param.class_name.as_ref(),
98 param.meta_class_name.as_ref(),
99 param.struct_name.as_ref(),
100 ];
101
102 let inner = [
103 param.inner_prop.as_deref(),
104 param.key_prop.as_deref(),
105 param.value_prop.as_deref(),
106 param.element_prop.as_deref(),
107 ];
108
109 direct
110 .into_iter()
111 .flatten()
112 .any(|name| types.contains(name))
113 || inner
114 .into_iter()
115 .flatten()
116 .flat_map(names)
117 .any(|name| types.contains(&name))
118 })
119}
120
121fn is_property_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
122 if !type_map::is_supported_type(&prop.prop_type) {
123 return false;
124 }
125
126 if prop.array_dim > 1 {
127 match prop.prop_type.as_str() {
128 "StrProperty" | "NameProperty" | "TextProperty" => return false,
129 _ => {}
130 }
131 }
132
133 if prop.prop_flags & (CPF_NATIVE_ACCESS_PRIVATE | CPF_NATIVE_ACCESS_PROTECTED) != 0
134 && prop.prop_flags & (CPF_BLUEPRINT_VISIBLE | CPF_EDIT | CPF_BLUEPRINT_ASSIGNABLE) == 0
135 {
136 return false;
137 }
138
139 if is_delegate_type(&prop.prop_type) {
140 return is_delegate_exportable(prop, available);
141 }
142
143 if let Some(ref cls) = prop.class_name
144 && !available.contains(cls)
145 {
146 return false;
147 }
148
149 if let Some(ref sn) = prop.struct_name
150 && !available.contains(sn)
151 {
152 return false;
153 }
154
155 if let Some(ref en) = prop.enum_name
156 && !available.contains(en)
157 {
158 return false;
159 }
160
161 if let Some(ref iface) = prop.interface_name
162 && !available.contains(iface)
163 {
164 return false;
165 }
166
167 if prop.prop_type == "ClassProperty"
168 && let Some(ref meta_cls) = prop.meta_class_name
169 && !available.contains(meta_cls)
170 {
171 return false;
172 }
173
174 true
175}
176
177fn is_delegate_type(prop_type: &str) -> bool {
178 matches!(
179 prop_type,
180 "DelegateProperty" | "MulticastInlineDelegateProperty" | "MulticastSparseDelegateProperty"
181 )
182}
183
184fn is_delegate_exportable(prop: &PropertyInfo, available: &HashSet<String>) -> bool {
185 let func_info = match &prop.func_info {
186 Some(fi) => fi,
187 None => return false,
188 };
189
190 let params = match func_info.get("params").and_then(|p| p.as_array()) {
191 Some(params) => params,
192 None => return true,
193 };
194
195 for param_value in params {
196 let param_type = match param_value.get("type").and_then(|t| t.as_str()) {
197 Some(t) => t,
198 None => return false,
199 };
200
201 if !type_map::is_supported_type(param_type) {
202 return false;
203 }
204
205 if is_delegate_type(param_type) {
206 return false;
207 }
208
209 if matches!(param_type, "ArrayProperty" | "MapProperty" | "SetProperty") {
210 return false;
211 }
212
213 if let Some(cls) = param_value.get("class_name").and_then(|v| v.as_str())
214 && !available.contains(cls)
215 {
216 return false;
217 }
218
219 if let Some(sn) = param_value.get("struct_name").and_then(|v| v.as_str())
220 && !available.contains(sn)
221 {
222 return false;
223 }
224
225 if let Some(en) = param_value.get("enum_name").and_then(|v| v.as_str())
226 && !available.contains(en)
227 {
228 return false;
229 }
230 }
231
232 true
233}
234
235fn is_container_param_exportable(param: &ParamInfo, available: &HashSet<String>) -> bool {
236 match param.prop_type.as_str() {
237 "ArrayProperty" => {
238 if let Some(ref inner) = param.inner_prop {
239 is_inner_type_exportable(inner, available)
240 } else {
241 false
242 }
243 }
244 "MapProperty" => {
245 let key_ok = param
246 .key_prop
247 .as_ref()
248 .map(|k| is_inner_type_exportable(k, available))
249 .unwrap_or(false);
250
251 let val_ok = param
252 .value_prop
253 .as_ref()
254 .map(|v| is_inner_type_exportable(v, available))
255 .unwrap_or(false);
256
257 key_ok && val_ok
258 }
259 "SetProperty" => {
260 if let Some(ref elem) = param.element_prop {
261 is_inner_type_exportable(elem, available)
262 } else {
263 false
264 }
265 }
266 _ => false,
267 }
268}
269
270fn is_inner_type_exportable(inner: &PropertyInfo, available: &HashSet<String>) -> bool {
271 if !type_map::is_supported_type(&inner.prop_type) {
272 return false;
273 }
274
275 if matches!(
276 inner.prop_type.as_str(),
277 "ArrayProperty" | "MapProperty" | "SetProperty"
278 ) {
279 return false;
280 }
281
282 if let Some(ref cls) = inner.class_name
283 && !available.contains(cls)
284 {
285 return false;
286 }
287
288 if let Some(ref sn) = inner.struct_name
289 && !available.contains(sn)
290 {
291 return false;
292 }
293
294 if let Some(ref en) = inner.enum_name
295 && !available.contains(en)
296 {
297 return false;
298 }
299
300 if let Some(ref iface) = inner.interface_name
301 && !available.contains(iface)
302 {
303 return false;
304 }
305
306 true
307}
308
309fn filter_functions(
310 class_name: &str,
311 funcs: &mut Vec<FunctionInfo>,
312 available: &HashSet<String>,
313 blocked_structs: &HashSet<&str>,
314 blocked_functions: &[(String, String)],
315 editor: bool,
316) {
317 let all_names: HashSet<String> = funcs.iter().map(|f| f.name.clone()).collect();
318
319 funcs.retain(|f| {
320 if blocked_functions
321 .iter()
322 .any(|(c, func)| c == class_name && func == &f.name)
323 {
324 return false;
325 }
326
327 if f.func_flags & FUNC_EDITOR_ONLY != 0 && !editor {
328 return false;
329 }
330
331 if f.func_flags & FUNC_NATIVE == 0 {
332 return false;
333 }
334
335 if f.name.starts_with("K2_") {
336 let base_name = &f.name[3..];
337
338 if all_names.contains(base_name) {
339 return false;
340 }
341 }
342
343 for param in &f.params {
344 if !type_map::is_supported_type(¶m.prop_type) {
345 return false;
346 }
347
348 if is_delegate_type(¶m.prop_type) {
349 return false;
350 }
351
352 if matches!(
353 param.prop_type.as_str(),
354 "ArrayProperty" | "MapProperty" | "SetProperty"
355 ) && !is_container_param_exportable(param, available)
356 {
357 return false;
358 }
359
360 if let Some(ref cls) = param.class_name
361 && !available.contains(cls)
362 {
363 return false;
364 }
365
366 if let Some(ref sn) = param.struct_name
367 && (!available.contains(sn) || blocked_structs.contains(sn.as_str()))
368 {
369 return false;
370 }
371
372 if let Some(ref en) = param.enum_name
373 && !available.contains(en)
374 {
375 return false;
376 }
377
378 if let Some(ref iface) = param.interface_name
379 && !available.contains(iface)
380 {
381 return false;
382 }
383
384 if param.prop_type == "ClassProperty"
385 && let Some(ref meta_cls) = param.meta_class_name
386 && !available.contains(meta_cls)
387 {
388 return false;
389 }
390 }
391
392 true
393 });
394
395 for f in funcs.iter_mut() {
396 f.ue_name = f.name.clone();
397 }
398
399 let mut name_counts: HashMap<String, usize> = HashMap::new();
400
401 for f in funcs.iter() {
402 *name_counts.entry(f.name.clone()).or_default() += 1;
403 }
404
405 let mut name_indices: HashMap<String, usize> = HashMap::new();
406
407 for f in funcs.iter_mut() {
408 if let Some(&count) = name_counts.get(&f.name)
409 && count > 1
410 {
411 let idx = name_indices.entry(f.name.clone()).or_insert(0);
412 *idx += 1;
413 f.name = format!("{}_{}", f.name, idx);
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use rusteal_ue_flags::{CPF_BLUEPRINT_READ_ONLY, CPF_BLUEPRINT_VISIBLE};
422
423 fn object_prop(name: &str, flags: u64) -> PropertyInfo {
424 serde_json::from_value(serde_json::json!({
425 "name": name,
426 "type": "ObjectProperty",
427 "prop_flags": flags,
428 "class_name": "CharacterMovementComponent",
429 }))
430 .unwrap()
431 }
432
433 #[test]
434 fn private_properties_only_when_blueprint_can_reach_them() {
435 let available: HashSet<String> = ["CharacterMovementComponent".to_string()].into();
436 let read_only = CPF_BLUEPRINT_VISIBLE | CPF_BLUEPRINT_READ_ONLY;
437
438 assert!(is_property_exportable(
439 &object_prop("Public", 0),
440 &available
441 ));
442
443 assert!(!is_property_exportable(
444 &object_prop("Hidden", CPF_NATIVE_ACCESS_PRIVATE),
445 &available
446 ));
447
448 assert!(!is_property_exportable(
449 &object_prop("Guarded", CPF_NATIVE_ACCESS_PROTECTED),
450 &available
451 ));
452
453 assert!(is_property_exportable(
454 &object_prop("CharacterMovement", CPF_NATIVE_ACCESS_PRIVATE | read_only),
455 &available
456 ));
457
458 assert!(is_property_exportable(
459 &object_prop(
460 "Guarded",
461 CPF_NATIVE_ACCESS_PROTECTED | CPF_BLUEPRINT_VISIBLE
462 ),
463 &available
464 ));
465
466 assert!(is_property_exportable(
467 &object_prop("Editable", CPF_NATIVE_ACCESS_PROTECTED | CPF_EDIT),
468 &available
469 ));
470
471 assert!(is_property_exportable(
472 &object_prop(
473 "Assignable",
474 CPF_NATIVE_ACCESS_PROTECTED | CPF_BLUEPRINT_ASSIGNABLE
475 ),
476 &available
477 ));
478 }
479
480 fn function(params: serde_json::Value) -> FunctionInfo {
481 serde_json::from_value(serde_json::json!({
482 "name": "Probe",
483 "func_flags": FUNC_NATIVE,
484 "params": params,
485 }))
486 .unwrap()
487 }
488
489 fn param(fields: serde_json::Value) -> serde_json::Value {
490 let mut param = serde_json::json!({
491 "name": "P", "type": "ObjectProperty", "prop_flags": 0,
492 "enum_name": null, "enum_cpp_name": null, "enum_cpp_form": null,
493 "enum_underlying_type": null, "class_name": null, "meta_class_name": null,
494 "struct_name": null, "interface_name": null, "func_info": null,
495 "inner_prop": null, "key_prop": null, "value_prop": null,
496 "element_prop": null, "default": null,
497 });
498
499 for (key, value) in fields.as_object().unwrap() {
500 param[key] = value.clone();
501 }
502
503 param
504 }
505
506 #[test]
507 fn functions_naming_unreachable_headers_drop_out() {
508 let internal: HashSet<String> = ["NiagaraDataInterfaceArrayMesh".to_string()].into();
509 let takes = |fields| function(serde_json::json!([param(fields)]));
510
511 assert!(references_any(
512 &takes(serde_json::json!({"class_name": "NiagaraDataInterfaceArrayMesh"})),
513 &internal
514 ));
515
516 assert!(references_any(
517 &takes(serde_json::json!({
518 "type": "ArrayProperty",
519 "inner_prop": {"name": "P", "type": "ObjectProperty", "prop_flags": 0,
520 "class_name": "NiagaraDataInterfaceArrayMesh"},
521 })),
522 &internal
523 ));
524
525 assert!(!references_any(
526 &takes(serde_json::json!({"class_name": "NiagaraComponent"})),
527 &internal
528 ));
529
530 assert!(!references_any(&function(serde_json::json!([])), &internal));
531 }
532}