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