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