1use std::collections::BTreeSet;
2
3use crate::context::{CodegenContext, FuncEntry};
4use crate::schema::*;
5use crate::type_map::{self, ConversionKind, MappedType, ParamDirection};
6
7enum ReturnStrategy {
8 Direct,
9 ObjectRef,
10 SoftWeakObjectRef,
11 Cast(String),
12 StringReturn,
13 TextReturn,
14 StructReturn,
15 FNameReturn,
16 ContainerReturn,
17}
18
19enum PostCallAction {
20 StringOutput(String),
21 TextOutput(String),
22 StructOutput { name: String, struct_cpp: String },
23 ObjectOutput(String),
24 SoftWeakObjectOutput(String),
25 FNameOutput(String),
26 EnumOutput { name: String, ffi_type: String },
27 InOutStructCopyback { name: String, struct_cpp: String },
28 InOutStringCopyback(String),
29 InOutTextCopyback(String),
30}
31
32pub fn generate_wrapper_file(entries: &[&FuncEntry], ctx: &CodegenContext) -> String {
33 let mut out = String::with_capacity(entries.len() * 512 + 256);
34
35 out.push_str("// Auto-generated by rusteal-codegen. Do not edit.\n\n");
36
37 let mut includes = BTreeSet::new();
38 includes.insert("\"RustealApiTable.h\"".to_string());
39
40 let has_container_params = entries
41 .iter()
42 .any(|e| e.func.params.iter().any(is_container_param));
43
44 for entry in entries {
45 if !entry.header.is_empty() {
46 includes.insert(format!("\"{}\"", entry.header));
47 }
48
49 for param in &entry.func.params {
50 collect_param_headers(param, ctx, &mut includes);
51 }
52 }
53
54 if has_container_params {
55 includes.insert("\"UObject/UnrealType.h\"".to_string());
56 }
57
58 let has_fname = entries.iter().any(|e| {
59 e.func
60 .params
61 .iter()
62 .any(|p| matches!(map_param(p).rust_to_ffi, ConversionKind::FName))
63 });
64
65 if has_fname {
66 includes.insert("\"RustealFNameHelper.h\"".to_string());
67 }
68
69 for inc in &includes {
70 out.push_str(&format!("#include {inc}\n"));
71 }
72
73 out.push_str("#include <string>\n");
74 out.push_str("#include <type_traits>\n");
75 out.push('\n');
76
77 out.push_str("#ifdef _MSC_VER\n");
78 out.push_str("#pragma warning(push)\n");
79 out.push_str("#pragma warning(disable: 4996)\n");
80 out.push_str("#endif\n\n");
81
82 out.push_str("#ifndef RUSTEAL_ERROR_CODES_DEFINED\n");
83 out.push_str("#define RUSTEAL_ERROR_CODES_DEFINED\n");
84 out.push_str("static constexpr uint32_t RustealErrorCode_Ok = 0;\n");
85 out.push_str("static constexpr uint32_t RustealErrorCode_ObjectDestroyed = 1;\n");
86 out.push_str("#endif\n\n");
87
88 for entry in entries {
89 generate_wrapper_function(&mut out, entry, ctx);
90 }
91
92 out.push_str("#ifdef _MSC_VER\n");
93 out.push_str("#pragma warning(pop)\n");
94 out.push_str("#endif\n");
95
96 out
97}
98
99pub fn cpp_wrapper_name(prefix: &str, class_name: &str, func_name: &str) -> String {
100 format!("{prefix}Rusteal_{class_name}_{func_name}")
101}
102
103fn generate_wrapper_function(out: &mut String, entry: &FuncEntry, ctx: &CodegenContext) {
104 let func = &entry.func;
105 let class_cpp = &entry.cpp_class_name;
106 let func_name = &entry.func_name;
107
108 let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
109
110 let is_blueprint_native =
111 (func.func_flags & FUNC_BLUEPRINT_EVENT != 0) && (func.func_flags & FUNC_NATIVE != 0);
112
113 let c_func_name = cpp_wrapper_name(&ctx.cpp_prefix, &entry.class_name, func_name);
114
115 let mut inputs = Vec::new();
116 let mut outputs = Vec::new();
117 let mut return_param: Option<&ParamInfo> = None;
118
119 for param in &func.params {
120 let dir = type_map::param_direction(param);
121
122 match dir {
123 ParamDirection::Return => return_param = Some(param),
124 ParamDirection::In | ParamDirection::InOut => inputs.push((param, dir)),
125 ParamDirection::Out => outputs.push(param),
126 }
127 }
128
129 out.push_str(&format!("extern \"C\" uint32_t {c_func_name}(\n"));
130
131 let mut params_str = Vec::new();
132
133 if !is_static {
134 params_str.push(" void* Obj".to_string());
135 }
136
137 for param in &func.params {
138 let dir = type_map::param_direction(param);
139
140 match dir {
141 ParamDirection::In | ParamDirection::InOut => {
142 expand_input_sig(&mut params_str, param, dir);
143 }
144 ParamDirection::Out => {
145 expand_output_sig(&mut params_str, param);
146 }
147 ParamDirection::Return => {
148 expand_return_sig(&mut params_str, param);
149 }
150 }
151 }
152
153 out.push_str(¶ms_str.join(",\n"));
154 out.push_str("\n) {\n");
155
156 if !is_static {
157 out.push_str(&format!(
158 " {class_cpp}* Self = static_cast<{class_cpp}*>(Obj);\n\
159 #if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT\n\
160 \x20 if (!IsValid(Self)) return RustealErrorCode_ObjectDestroyed;\n\
161 #endif\n"
162 ));
163 }
164
165 for param in &func.params {
166 if !is_container_param(param) {
167 continue;
168 }
169
170 let dir = type_map::param_direction(param);
171
172 if dir == ParamDirection::Return {
173 continue;
174 }
175
176 let name = ¶m.name;
177
178 let cpp_type = resolve_container_cpp_type(param, ctx)
179 .unwrap_or_else(|| "void /* ERROR */".to_string());
180
181 let (var_name, base_name, prop_name) = if dir == ParamDirection::Return {
182 (
183 "ReturnValue".to_string(),
184 "OutReturnValue_Base".to_string(),
185 "OutReturnValue_Prop".to_string(),
186 )
187 } else {
188 (name.clone(), format!("{name}_Base"), format!("{name}_Prop"))
189 };
190
191 out.push_str(&format!(
192 " auto& __Container_{var_name} = *reinterpret_cast<{cpp_type}*>(\n\
193 \x20 static_cast<FProperty*>({prop_name})->ContainerPtrToValuePtr<void>({base_name}));\n"
194 ));
195 }
196
197 let mut post_call_actions = Vec::new();
198
199 for param in &outputs {
200 if is_container_param(param) {
201 continue;
202 }
203
204 let mapped = map_param(param);
205 let name = ¶m.name;
206
207 match mapped.ffi_to_rust {
208 ConversionKind::StringUtf8 => {
209 if param.prop_type == "TextProperty" {
210 out.push_str(&format!(" FText __Out{name};\n"));
211 post_call_actions.push(PostCallAction::TextOutput(name.clone()));
212 } else {
213 out.push_str(&format!(" FString __Out{name};\n"));
214 post_call_actions.push(PostCallAction::StringOutput(name.clone()));
215 }
216 }
217 ConversionKind::StructOpaque => {
218 let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
219 out.push_str(&format!(" {struct_cpp} __Out{name};\n"));
220
221 post_call_actions.push(PostCallAction::StructOutput {
222 name: name.clone(),
223 struct_cpp,
224 });
225 }
226 ConversionKind::ObjectRef => match param.prop_type.as_str() {
227 "SoftObjectProperty" => {
228 let bare = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
229 out.push_str(&format!(" TSoftObjectPtr<{bare}> __Out{name};\n"));
230 post_call_actions.push(PostCallAction::SoftWeakObjectOutput(name.clone()));
231 }
232 "WeakObjectProperty" => {
233 let bare = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
234 out.push_str(&format!(" TWeakObjectPtr<{bare}> __Out{name};\n"));
235 post_call_actions.push(PostCallAction::SoftWeakObjectOutput(name.clone()));
236 }
237 _ => {
238 let obj_cpp = resolve_object_cpp_type(ctx, param.class_name.as_deref());
239 out.push_str(&format!(" {obj_cpp} __Out{name} = nullptr;\n"));
240 post_call_actions.push(PostCallAction::ObjectOutput(name.clone()));
241 }
242 },
243 ConversionKind::FName => {
244 out.push_str(&format!(" FName __Out{name};\n"));
245 post_call_actions.push(PostCallAction::FNameOutput(name.clone()));
246 }
247 ConversionKind::EnumCast => {
248 let cpp_enum = resolve_enum_cpp_type(param);
249 let form = param.enum_cpp_form.unwrap_or(2);
250
251 let local_type = match form {
252 0 | 1 => format!("TEnumAsByte<{cpp_enum}>"),
253 _ => cpp_enum,
254 };
255
256 out.push_str(&format!(" {local_type} __Out{name};\n"));
257
258 post_call_actions.push(PostCallAction::EnumOutput {
259 name: name.clone(),
260 ffi_type: enum_ffi_ctype(param.enum_underlying_type.as_deref()),
261 });
262 }
263 _ => {}
264 }
265 }
266
267 for param in &func.params {
268 let dir = type_map::param_direction(param);
269
270 if dir == ParamDirection::InOut {
271 let mapped = map_param(param);
272
273 if mapped.rust_to_ffi == ConversionKind::StructOpaque {
274 let name = ¶m.name;
275 let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
276 out.push_str(&format!(" {struct_cpp} __InOut{name};\n"));
277 out.push_str(&format!(" if ({name}) {{ FMemory::Memcpy(&__InOut{name}, {name}, sizeof({struct_cpp})); }}\n"));
278
279 post_call_actions.push(PostCallAction::InOutStructCopyback {
280 name: name.clone(),
281 struct_cpp,
282 });
283 }
284 }
285 }
286
287 for param in &func.params {
288 let dir = type_map::param_direction(param);
289
290 if dir == ParamDirection::InOut {
291 let mapped = map_param(param);
292
293 if mapped.rust_to_ffi == ConversionKind::StringUtf8 {
294 let name = ¶m.name;
295
296 if param.prop_type == "TextProperty" {
297 out.push_str(&format!(
298 " FText __InOut{name} = FText::FromString(FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str())));\n"
299 ));
300 post_call_actions.push(PostCallAction::InOutTextCopyback(name.clone()));
301 } else {
302 out.push_str(&format!(
303 " FString __InOut{name} = FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str()));\n"
304 ));
305
306 post_call_actions.push(PostCallAction::InOutStringCopyback(name.clone()));
307 }
308 }
309 }
310 }
311
312 let mut call_args = Vec::new();
313
314 for param in &func.params {
315 let dir = type_map::param_direction(param);
316
317 match dir {
318 ParamDirection::Return => {}
319 ParamDirection::In | ParamDirection::InOut => {
320 call_args.push(input_arg_expression(param, dir, ctx));
321 }
322 ParamDirection::Out => {
323 if is_container_param(param) {
324 call_args.push(format!("__Container_{}", param.name));
325 } else {
326 let mapped = map_param(param);
327
328 match mapped.ffi_to_rust {
329 ConversionKind::StringUtf8
330 | ConversionKind::StructOpaque
331 | ConversionKind::ObjectRef
332 | ConversionKind::FName
333 | ConversionKind::EnumCast => {
334 call_args.push(format!("__Out{}", param.name));
335 }
336 _ => {
337 call_args.push(format!("*Out{}", param.name));
338 }
339 }
340 }
341 }
342 }
343 }
344
345 let args = call_args.join(", ");
346 let is_protected = func.func_flags & FUNC_PROTECTED != 0;
347
348 let call_expr = if is_protected && func.interface.is_none() && !is_blueprint_native {
349 out.push_str(&format!(
350 " struct FAccess : {class_cpp} {{ using {class_cpp}::{func_name}; }};\n"
351 ));
352 if is_static {
353 format!("FAccess::{func_name}({args})")
354 } else {
355 format!("(Self->*&FAccess::{func_name})({args})")
356 }
357 } else if is_static {
358 format!("{class_cpp}::{func_name}({args})")
359 } else if let Some(iface) = &func.interface {
360 format!("static_cast<{iface}*>(Self)->{func_name}({args})")
361 } else if is_blueprint_native {
362 out.push_str(&format!(
363 " struct FAccess : {class_cpp} {{ using {class_cpp}::{func_name}_Implementation; }};\n"
364 ));
365
366 format!("(Self->*&FAccess::{func_name}_Implementation)({args})")
367 } else {
368 format!("Self->{func_name}({args})")
369 };
370
371 if let Some(rp) = return_param {
372 let strategy = return_strategy(rp);
373 emit_return(out, &strategy, &call_expr);
374 } else {
375 out.push_str(&format!(" {call_expr};\n"));
376 }
377
378 for action in &post_call_actions {
379 emit_post_call(out, action);
380 }
381
382 out.push_str(" return RustealErrorCode_Ok;\n");
383 out.push_str("}\n\n");
384}
385
386fn expand_input_sig(params: &mut Vec<String>, param: &ParamInfo, dir: ParamDirection) {
387 if is_container_param(param) {
388 let name = ¶m.name;
389 params.push(format!(" void* {name}_Base"));
390 params.push(format!(" void* {name}_Prop"));
391 return;
392 }
393
394 let mapped = map_param(param);
395 let name = ¶m.name;
396
397 match mapped.rust_to_ffi {
398 ConversionKind::StringUtf8 => {
399 params.push(format!(" const char* {name}"));
400 params.push(format!(" uint32_t {name}Len"));
401
402 if dir == ParamDirection::InOut {
403 params.push(format!(" char* Out{name}"));
404 params.push(format!(" uint32_t Out{name}BufLen"));
405 params.push(format!(" uint32_t* Out{name}Len"));
406 }
407 }
408 _ => {
409 let cpp_type = scalar_input_cpp_type(&mapped, param, dir);
410 params.push(format!(" {cpp_type} {name}"));
411 }
412 }
413}
414
415fn expand_output_sig(params: &mut Vec<String>, param: &ParamInfo) {
416 if is_container_param(param) {
417 let name = ¶m.name;
418 params.push(format!(" void* {name}_Base"));
419 params.push(format!(" void* {name}_Prop"));
420 return;
421 }
422
423 let mapped = map_param(param);
424 let name = ¶m.name;
425
426 match mapped.ffi_to_rust {
427 ConversionKind::StringUtf8 => {
428 params.push(format!(" char* Out{name}"));
429 params.push(format!(" uint32_t Out{name}BufLen"));
430 params.push(format!(" uint32_t* Out{name}Len"));
431 }
432 _ => {
433 let cpp_type = scalar_output_cpp_type(&mapped, param);
434 params.push(format!(" {cpp_type} Out{name}"));
435 }
436 }
437}
438
439fn expand_return_sig(params: &mut Vec<String>, param: &ParamInfo) {
440 if is_container_param(param) {
441 params.push(" void* OutReturnValue_Base".to_string());
442 params.push(" void* OutReturnValue_Prop".to_string());
443 return;
444 }
445
446 let mapped = map_param(param);
447
448 match mapped.ffi_to_rust {
449 ConversionKind::StringUtf8 => {
450 params.push(" char* OutBuf".to_string());
451 params.push(" uint32_t BufLen".to_string());
452 params.push(" uint32_t* OutLen".to_string());
453 }
454 _ => {
455 let cpp_type = scalar_output_cpp_type(&mapped, param);
456 params.push(format!(" {cpp_type} OutReturnValue"));
457 }
458 }
459}
460
461fn map_param(param: &ParamInfo) -> MappedType {
462 type_map::map_property_type(
463 ¶m.prop_type,
464 param.class_name.as_deref(),
465 param.struct_name.as_deref(),
466 param.enum_name.as_deref(),
467 param.enum_underlying_type.as_deref(),
468 param.meta_class_name.as_deref(),
469 param.interface_name.as_deref(),
470 )
471}
472
473fn scalar_input_cpp_type(mapped: &MappedType, param: &ParamInfo, dir: ParamDirection) -> String {
474 match mapped.rust_to_ffi {
475 ConversionKind::Identity => mapped.cpp_type.clone(),
476 ConversionKind::ObjectRef => "void*".to_string(),
477 ConversionKind::StringUtf8 => unreachable!("strings handled by expand_input_sig"),
478 ConversionKind::EnumCast => enum_ffi_ctype(param.enum_underlying_type.as_deref()),
479 ConversionKind::IntCast => int_ctype(&mapped.cpp_type),
480 ConversionKind::StructOpaque => {
481 if dir == ParamDirection::InOut {
482 "uint8_t*".to_string()
483 } else {
484 "const uint8_t*".to_string()
485 }
486 }
487 ConversionKind::FName => "uint64_t".to_string(),
488 ConversionKind::ContainerArray
489 | ConversionKind::ContainerMap
490 | ConversionKind::ContainerSet
491 | ConversionKind::Delegate
492 | ConversionKind::MulticastDelegate => {
493 unreachable!("container/delegate types are property-only, never function params")
494 }
495 }
496}
497
498fn scalar_output_cpp_type(mapped: &MappedType, param: &ParamInfo) -> String {
499 match mapped.ffi_to_rust {
500 ConversionKind::Identity => format!("{}*", mapped.cpp_type),
501 ConversionKind::ObjectRef => "void**".to_string(),
502 ConversionKind::StringUtf8 => unreachable!("strings handled by expand_*_sig"),
503 ConversionKind::EnumCast => {
504 format!("{}*", enum_ffi_ctype(param.enum_underlying_type.as_deref()))
505 }
506 ConversionKind::IntCast => format!("{}*", int_ctype(&mapped.cpp_type)),
507 ConversionKind::StructOpaque => "uint8_t*".to_string(),
508 ConversionKind::FName => "uint64_t*".to_string(),
509 ConversionKind::ContainerArray
510 | ConversionKind::ContainerMap
511 | ConversionKind::ContainerSet
512 | ConversionKind::Delegate
513 | ConversionKind::MulticastDelegate => {
514 unreachable!("container/delegate types are property-only, never function params")
515 }
516 }
517}
518
519fn enum_ffi_ctype(underlying: Option<&str>) -> String {
520 match underlying.unwrap_or("uint8") {
521 "uint8" => "uint8_t",
522 "int8" => "int8_t",
523 "uint16" => "uint16_t",
524 "int16" => "int16_t",
525 "uint32" => "uint32_t",
526 "int32" => "int32_t",
527 "uint64" => "uint64_t",
528 "int64" => "int64_t",
529 _ => "uint8_t",
530 }
531 .to_string()
532}
533
534fn int_ctype(cpp_type: &str) -> String {
535 match cpp_type {
536 "uint8" => "uint8_t",
537 "int8" => "int8_t",
538 "uint16" => "uint16_t",
539 "int16" => "int16_t",
540 "uint32" => "uint32_t",
541 "int32" => "int32_t",
542 "uint64" => "uint64_t",
543 "int64" => "int64_t",
544 _ => "int32_t",
545 }
546 .to_string()
547}
548
549fn resolve_struct_opaque_cpp_type(param: &ParamInfo, ctx: &CodegenContext) -> String {
550 match param.prop_type.as_str() {
551 "SoftObjectProperty" => {
552 let cls_cpp = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
553
554 format!("TSoftObjectPtr<{cls_cpp}>")
555 }
556 "WeakObjectProperty" => {
557 let cls_cpp = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
558
559 format!("TWeakObjectPtr<{cls_cpp}>")
560 }
561 _ => {
562 format!("F{}", param.struct_name.as_deref().unwrap_or("Unknown"))
563 }
564 }
565}
566
567fn resolve_object_cpp_type_bare(ctx: &CodegenContext, class_name: Option<&str>) -> String {
568 match class_name {
569 Some(cn) => {
570 if let Some(cls) = ctx.classes.get(cn) {
571 cls.cpp_name.clone()
572 } else {
573 cn.to_string()
574 }
575 }
576 None => "UObject".to_string(),
577 }
578}
579
580fn resolve_object_cpp_type(ctx: &CodegenContext, class_name: Option<&str>) -> String {
581 match class_name {
582 Some(cn) => {
583 if let Some(cls) = ctx.classes.get(cn) {
584 format!("{}*", cls.cpp_name)
585 } else {
586 format!("{cn}*")
587 }
588 }
589 None => "UObject*".to_string(),
590 }
591}
592
593fn input_arg_expression(param: &ParamInfo, dir: ParamDirection, ctx: &CodegenContext) -> String {
594 if is_container_param(param) {
595 return format!("__Container_{}", param.name);
596 }
597
598 let mapped = map_param(param);
599
600 match mapped.rust_to_ffi {
601 ConversionKind::Identity => param.name.clone(),
602 ConversionKind::ObjectRef => {
603 let cpp_type = resolve_object_cpp_type(ctx, param.class_name.as_deref());
604
605 match param.prop_type.as_str() {
606 "SoftObjectProperty" => {
607 let bare_type = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
608
609 format!(
610 "TSoftObjectPtr<{bare_type}>(static_cast<{cpp_type}>({}))",
611 param.name
612 )
613 }
614 "WeakObjectProperty" => {
615 let bare_type = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
616
617 format!(
618 "TWeakObjectPtr<{bare_type}>(static_cast<{cpp_type}>({}))",
619 param.name
620 )
621 }
622 _ => format!("static_cast<{cpp_type}>({})", param.name),
623 }
624 }
625 ConversionKind::StringUtf8 => {
626 if dir == ParamDirection::InOut {
627 format!("__InOut{}", param.name)
628 } else {
629 let fstring_expr = format!(
630 "FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str()))",
631 name = param.name
632 );
633
634 if param.prop_type == "TextProperty" {
635 format!("FText::FromString({fstring_expr})")
636 } else {
637 fstring_expr
638 }
639 }
640 }
641 ConversionKind::EnumCast => {
642 let cpp_enum = resolve_enum_cpp_type(param);
643
644 format!("static_cast<{cpp_enum}>({})", param.name)
645 }
646 ConversionKind::StructOpaque => {
647 if dir == ParamDirection::InOut {
648 format!("__InOut{}", param.name)
649 } else {
650 let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
651
652 format!("*reinterpret_cast<const {struct_cpp}*>({})", param.name)
653 }
654 }
655 ConversionKind::IntCast => param.name.clone(),
656 ConversionKind::FName => {
657 format!("RustealUnpackFName({})", param.name)
658 }
659 ConversionKind::ContainerArray
660 | ConversionKind::ContainerMap
661 | ConversionKind::ContainerSet
662 | ConversionKind::Delegate
663 | ConversionKind::MulticastDelegate => {
664 unreachable!("container/delegate types are property-only, never function params")
665 }
666 }
667}
668
669fn return_strategy(param: &ParamInfo) -> ReturnStrategy {
670 if is_container_param(param) {
671 return ReturnStrategy::ContainerReturn;
672 }
673
674 let mapped = map_param(param);
675
676 match mapped.ffi_to_rust {
677 ConversionKind::Identity | ConversionKind::IntCast => ReturnStrategy::Direct,
678 ConversionKind::ObjectRef => match param.prop_type.as_str() {
679 "SoftObjectProperty" | "WeakObjectProperty" => ReturnStrategy::SoftWeakObjectRef,
680 _ => ReturnStrategy::ObjectRef,
681 },
682 ConversionKind::StringUtf8 => {
683 if param.prop_type == "TextProperty" {
684 ReturnStrategy::TextReturn
685 } else {
686 ReturnStrategy::StringReturn
687 }
688 }
689 ConversionKind::EnumCast => {
690 let ctype = match mapped.rust_ffi_type.as_str() {
691 "u8" => "uint8_t",
692 "i8" => "int8_t",
693 "i32" => "int32_t",
694 _ => "uint8_t",
695 };
696
697 ReturnStrategy::Cast(ctype.to_string())
698 }
699 ConversionKind::StructOpaque => ReturnStrategy::StructReturn,
700 ConversionKind::FName => ReturnStrategy::FNameReturn,
701 ConversionKind::ContainerArray
702 | ConversionKind::ContainerMap
703 | ConversionKind::ContainerSet
704 | ConversionKind::Delegate
705 | ConversionKind::MulticastDelegate => {
706 unreachable!("container/delegate types are property-only, never function return types")
707 }
708 }
709}
710
711fn emit_return(out: &mut String, strategy: &ReturnStrategy, call_expr: &str) {
712 match strategy {
713 ReturnStrategy::Direct => {
714 out.push_str(&format!(" *OutReturnValue = {call_expr};\n"));
715 }
716 ReturnStrategy::ObjectRef => {
717 out.push_str(&format!(
718 " *OutReturnValue = const_cast<void*>(static_cast<const void*>({call_expr}));\n"
719 ));
720 }
721 ReturnStrategy::SoftWeakObjectRef => {
722 out.push_str(&format!(
723 " *OutReturnValue = const_cast<void*>(static_cast<const void*>({call_expr}.Get()));\n"
724 ));
725 }
726 ReturnStrategy::Cast(ctype) => {
727 out.push_str(&format!(
728 " *OutReturnValue = static_cast<{ctype}>({call_expr});\n"
729 ));
730 }
731 ReturnStrategy::StringReturn => {
732 out.push_str(&format!(" FString __RustealResult = {call_expr};\n"));
733
734 out.push_str(
735 " auto __RustealUtf8 = StringCast<ANSICHAR>(*__RustealResult);\n\
736 \x20 int32 __RustealLen = __RustealUtf8.Length();\n\
737 \x20 if (__RustealLen > static_cast<int32>(BufLen)) __RustealLen = static_cast<int32>(BufLen);\n\
738 \x20 FMemory::Memcpy(OutBuf, __RustealUtf8.Get(), __RustealLen);\n\
739 \x20 *OutLen = static_cast<uint32_t>(__RustealLen);\n",
740 );
741 }
742 ReturnStrategy::TextReturn => {
743 out.push_str(&format!(
744 " FString __RustealResult = ({call_expr}).ToString();\n"
745 ));
746
747 out.push_str(
748 " auto __RustealUtf8 = StringCast<ANSICHAR>(*__RustealResult);\n\
749 \x20 int32 __RustealLen = __RustealUtf8.Length();\n\
750 \x20 if (__RustealLen > static_cast<int32>(BufLen)) __RustealLen = static_cast<int32>(BufLen);\n\
751 \x20 FMemory::Memcpy(OutBuf, __RustealUtf8.Get(), __RustealLen);\n\
752 \x20 *OutLen = static_cast<uint32_t>(__RustealLen);\n",
753 );
754 }
755 ReturnStrategy::StructReturn => {
756 out.push_str(&format!(" auto __RustealResult = {call_expr};\n"));
757
758 out.push_str(
759 " FMemory::Memcpy(OutReturnValue, &__RustealResult, sizeof(__RustealResult));\n",
760 );
761 }
762 ReturnStrategy::FNameReturn => {
763 out.push_str(&format!(" FName __RustealResult = {call_expr};\n"));
764
765 out.push_str(" *OutReturnValue = RustealPackFName(__RustealResult);\n");
766 }
767 ReturnStrategy::ContainerReturn => {
768 out.push_str(&format!(
769 " using __RustealReturnType = std::decay_t<decltype({call_expr})>;\n\
770 \x20 *reinterpret_cast<__RustealReturnType*>(\n\
771 \x20 static_cast<FProperty*>(OutReturnValue_Prop)->ContainerPtrToValuePtr<void>(OutReturnValue_Base))\n\
772 \x20 = {call_expr};\n"
773 ));
774 }
775 }
776}
777
778fn wrap_enum_as_byte(name: &str, cpp_form: Option<u32>) -> String {
779 let form = cpp_form.unwrap_or(2);
780
781 match form {
782 0 => format!("TEnumAsByte<{name}>"),
783 1 => format!("TEnumAsByte<{name}::Type>"),
784 _ => name.to_string(),
785 }
786}
787
788fn resolve_enum_cpp_type(param: &ParamInfo) -> String {
789 let name = param
790 .enum_cpp_name
791 .as_deref()
792 .or(param.enum_name.as_deref())
793 .unwrap_or("uint8");
794
795 let form = param.enum_cpp_form.unwrap_or(2);
796
797 if form == 1 {
798 format!("{name}::Type")
799 } else {
800 name.to_string()
801 }
802}
803
804fn emit_post_call(out: &mut String, action: &PostCallAction) {
805 match action {
806 PostCallAction::StringOutput(name) => {
807 out.push_str(&format!(
808 " {{\n\
809 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__Out{name});\n\
810 \x20 int32 __Len = __Utf8.Length();\n\
811 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
812 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
813 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
814 \x20 }}\n"
815 ));
816 }
817 PostCallAction::TextOutput(name) => {
818 out.push_str(&format!(
819 " {{\n\
820 \x20 FString __Str = __Out{name}.ToString();\n\
821 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__Str);\n\
822 \x20 int32 __Len = __Utf8.Length();\n\
823 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
824 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
825 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
826 \x20 }}\n"
827 ));
828 }
829 PostCallAction::StructOutput { name, struct_cpp } => {
830 out.push_str(&format!(
831 " if (Out{name}) {{ FMemory::Memcpy(Out{name}, &__Out{name}, sizeof({struct_cpp})); }}\n"
832 ));
833 }
834 PostCallAction::ObjectOutput(name) => {
835 out.push_str(&format!(
836 " *Out{name} = static_cast<void*>(__Out{name});\n"
837 ));
838 }
839 PostCallAction::SoftWeakObjectOutput(name) => {
840 out.push_str(&format!(
841 " *Out{name} = static_cast<void*>(__Out{name}.Get());\n"
842 ));
843 }
844 PostCallAction::FNameOutput(name) => {
845 out.push_str(&format!(
846 " *Out{name} = RustealPackFName(__Out{name});\n"
847 ));
848 }
849 PostCallAction::EnumOutput { name, ffi_type } => {
850 out.push_str(&format!(
851 " *Out{name} = static_cast<{ffi_type}>(__Out{name});\n"
852 ));
853 }
854 PostCallAction::InOutStructCopyback { name, struct_cpp } => {
855 out.push_str(&format!(
856 " if ({name}) {{ FMemory::Memcpy({name}, &__InOut{name}, sizeof({struct_cpp})); }}\n"
857 ));
858 }
859 PostCallAction::InOutStringCopyback(name) => {
860 out.push_str(&format!(
861 " {{\n\
862 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__InOut{name});\n\
863 \x20 int32 __Len = __Utf8.Length();\n\
864 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
865 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
866 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
867 \x20 }}\n"
868 ));
869 }
870 PostCallAction::InOutTextCopyback(name) => {
871 out.push_str(&format!(
872 " {{\n\
873 \x20 FString __Str = __InOut{name}.ToString();\n\
874 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__Str);\n\
875 \x20 int32 __Len = __Utf8.Length();\n\
876 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
877 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
878 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
879 \x20 }}\n"
880 ));
881 }
882 }
883}
884
885fn is_container_param(param: &ParamInfo) -> bool {
886 matches!(
887 param.prop_type.as_str(),
888 "ArrayProperty" | "MapProperty" | "SetProperty"
889 )
890}
891
892fn resolve_inner_cpp_type(inner: &PropertyInfo, ctx: &CodegenContext) -> String {
893 match inner.prop_type.as_str() {
894 "BoolProperty" => "bool".to_string(),
895 "Int8Property" => "int8".to_string(),
896 "ByteProperty" => {
897 if let Some(ref en) = inner.enum_name {
898 let name = inner.enum_cpp_name.as_deref().unwrap_or(en.as_str());
899
900 wrap_enum_as_byte(name, inner.enum_cpp_form)
901 } else {
902 "uint8".to_string()
903 }
904 }
905 "Int16Property" => "int16".to_string(),
906 "UInt16Property" => "uint16".to_string(),
907 "IntProperty" => "int32".to_string(),
908 "UInt32Property" => "uint32".to_string(),
909 "Int64Property" => "int64".to_string(),
910 "UInt64Property" => "uint64".to_string(),
911 "FloatProperty" => "float".to_string(),
912 "DoubleProperty" => "double".to_string(),
913 "StrProperty" => "FString".to_string(),
914 "TextProperty" => "FText".to_string(),
915 "NameProperty" => "FName".to_string(),
916 "ObjectProperty" => resolve_object_cpp_type(ctx, inner.class_name.as_deref()),
917 "SoftObjectProperty" => {
918 let cls_cpp = resolve_object_cpp_type_bare(ctx, inner.class_name.as_deref());
919
920 format!("TSoftObjectPtr<{cls_cpp}>")
921 }
922 "WeakObjectProperty" => {
923 let cls_cpp = resolve_object_cpp_type_bare(ctx, inner.class_name.as_deref());
924
925 format!("TWeakObjectPtr<{cls_cpp}>")
926 }
927 "ClassProperty" => match inner.meta_class_name.as_deref() {
928 Some(meta) => {
929 let meta_cpp = resolve_object_cpp_type_bare(ctx, Some(meta));
930
931 format!("TSubclassOf<{meta_cpp}>")
932 }
933 None => "UClass*".to_string(),
934 },
935 "InterfaceProperty" => resolve_object_cpp_type(ctx, inner.interface_name.as_deref()),
936 "EnumProperty" => {
937 let name = inner
938 .enum_cpp_name
939 .as_deref()
940 .or(inner.enum_name.as_deref())
941 .unwrap_or("uint8");
942
943 wrap_enum_as_byte(name, inner.enum_cpp_form)
944 }
945 "StructProperty" => {
946 if let Some(ref sn) = inner.struct_name {
947 if let Some(si) = ctx.structs.get(sn.as_str()) {
948 si.cpp_name.clone()
949 } else {
950 format!("F{sn}")
951 }
952 } else {
953 "uint8".to_string()
954 }
955 }
956 _ => "uint8".to_string(),
957 }
958}
959
960fn resolve_container_cpp_type(param: &ParamInfo, ctx: &CodegenContext) -> Option<String> {
961 match param.prop_type.as_str() {
962 "ArrayProperty" => {
963 let inner = param.inner_prop.as_ref()?;
964 let elem = resolve_inner_cpp_type(inner, ctx);
965
966 Some(format!("TArray<{elem}>"))
967 }
968 "MapProperty" => {
969 let key = param.key_prop.as_ref()?;
970 let val = param.value_prop.as_ref()?;
971 let kt = resolve_inner_cpp_type(key, ctx);
972 let vt = resolve_inner_cpp_type(val, ctx);
973
974 Some(format!("TMap<{kt}, {vt}>"))
975 }
976 "SetProperty" => {
977 let elem = param.element_prop.as_ref()?;
978 let et = resolve_inner_cpp_type(elem, ctx);
979
980 Some(format!("TSet<{et}>"))
981 }
982 _ => None,
983 }
984}
985
986fn collect_param_headers(param: &ParamInfo, ctx: &CodegenContext, includes: &mut BTreeSet<String>) {
987 for cls_name in [
988 param.class_name.as_deref(),
989 param.meta_class_name.as_deref(),
990 ]
991 .into_iter()
992 .flatten()
993 {
994 if let Some(cls) = ctx.classes.get(cls_name)
995 && !cls.header.is_empty()
996 {
997 includes.insert(format!("\"{}\"", cls.header));
998 }
999 }
1000
1001 if let Some(sn) = param.struct_name.as_deref() {
1002 include_struct_header(sn, ctx, includes);
1003 }
1004
1005 for inner in [
1006 param.inner_prop.as_deref(),
1007 param.key_prop.as_deref(),
1008 param.value_prop.as_deref(),
1009 param.element_prop.as_deref(),
1010 ]
1011 .into_iter()
1012 .flatten()
1013 {
1014 for cls_name in [
1015 inner.class_name.as_deref(),
1016 inner.meta_class_name.as_deref(),
1017 ]
1018 .into_iter()
1019 .flatten()
1020 {
1021 if let Some(cls) = ctx.classes.get(cls_name)
1022 && !cls.header.is_empty()
1023 {
1024 includes.insert(format!("\"{}\"", cls.header));
1025 }
1026 }
1027
1028 if let Some(sn) = inner.struct_name.as_deref() {
1029 include_struct_header(sn, ctx, includes);
1030 }
1031 }
1032}
1033
1034fn include_struct_header(struct_name: &str, ctx: &CodegenContext, includes: &mut BTreeSet<String>) {
1035 if let Some(st) = ctx.structs.get(struct_name)
1036 && !st.header.is_empty()
1037 {
1038 includes.insert(format!("\"{}\"", st.header));
1039 }
1040}