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