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 is_protected = func.func_flags & FUNC_PROTECTED != 0;
370 let call_expr = if is_protected && func.interface.is_none() && !is_blueprint_native {
371 out.push_str(&format!(
374 " struct FAccess : {class_cpp} {{ using {class_cpp}::{func_name}; }};\n"
375 ));
376 if is_static {
377 format!("FAccess::{func_name}({args})")
378 } else {
379 format!("(Self->*&FAccess::{func_name})({args})")
380 }
381 } else if is_static {
382 format!("{class_cpp}::{func_name}({args})")
383 } else if let Some(iface) = &func.interface {
384 format!("static_cast<{iface}*>(Self)->{func_name}({args})")
387 } else if is_blueprint_native {
388 out.push_str(&format!(
394 " struct FAccess : {class_cpp} {{ using {class_cpp}::{func_name}_Implementation; }};\n"
395 ));
396 format!("(Self->*&FAccess::{func_name}_Implementation)({args})")
397 } else {
398 format!("Self->{func_name}({args})")
399 };
400
401 if let Some(rp) = return_param {
403 let strategy = return_strategy(rp);
404 emit_return(out, &strategy, &call_expr);
405 } else {
406 out.push_str(&format!(" {call_expr};\n"));
407 }
408
409 for action in &post_call_actions {
411 emit_post_call(out, action);
412 }
413
414 out.push_str(" return RustealErrorCode_Ok;\n");
415 out.push_str("}\n\n");
416}
417
418fn expand_input_sig(params: &mut Vec<String>, param: &ParamInfo, dir: ParamDirection) {
423 if is_container_param(param) {
424 let name = ¶m.name;
425 params.push(format!(" void* {name}_Base"));
426 params.push(format!(" void* {name}_Prop"));
427 return;
428 }
429 let mapped = map_param(param);
430 let name = ¶m.name;
431 match mapped.rust_to_ffi {
432 ConversionKind::StringUtf8 => {
433 params.push(format!(" const char* {name}"));
434 params.push(format!(" uint32_t {name}Len"));
435 if dir == ParamDirection::InOut {
437 params.push(format!(" char* Out{name}"));
438 params.push(format!(" uint32_t Out{name}BufLen"));
439 params.push(format!(" uint32_t* Out{name}Len"));
440 }
441 }
442 _ => {
443 let cpp_type = scalar_input_cpp_type(&mapped, param, dir);
444 params.push(format!(" {cpp_type} {name}"));
445 }
446 }
447}
448
449fn expand_output_sig(params: &mut Vec<String>, param: &ParamInfo) {
450 if is_container_param(param) {
451 let name = ¶m.name;
452 params.push(format!(" void* {name}_Base"));
453 params.push(format!(" void* {name}_Prop"));
454 return;
455 }
456 let mapped = map_param(param);
457 let name = ¶m.name;
458 match mapped.ffi_to_rust {
459 ConversionKind::StringUtf8 => {
460 params.push(format!(" char* Out{name}"));
461 params.push(format!(" uint32_t Out{name}BufLen"));
462 params.push(format!(" uint32_t* Out{name}Len"));
463 }
464 _ => {
465 let cpp_type = scalar_output_cpp_type(&mapped, param);
466 params.push(format!(" {cpp_type} Out{name}"));
467 }
468 }
469}
470
471fn expand_return_sig(params: &mut Vec<String>, param: &ParamInfo) {
472 if is_container_param(param) {
473 params.push(" void* OutReturnValue_Base".to_string());
474 params.push(" void* OutReturnValue_Prop".to_string());
475 return;
476 }
477 let mapped = map_param(param);
478 match mapped.ffi_to_rust {
479 ConversionKind::StringUtf8 => {
480 params.push(" char* OutBuf".to_string());
481 params.push(" uint32_t BufLen".to_string());
482 params.push(" uint32_t* OutLen".to_string());
483 }
484 _ => {
485 let cpp_type = scalar_output_cpp_type(&mapped, param);
486 params.push(format!(" {cpp_type} OutReturnValue"));
487 }
488 }
489}
490
491fn map_param(param: &ParamInfo) -> MappedType {
497 type_map::map_property_type(
498 ¶m.prop_type,
499 param.class_name.as_deref(),
500 param.struct_name.as_deref(),
501 param.enum_name.as_deref(),
502 param.enum_underlying_type.as_deref(),
503 param.meta_class_name.as_deref(),
504 param.interface_name.as_deref(),
505 )
506}
507
508fn scalar_input_cpp_type(mapped: &MappedType, param: &ParamInfo, dir: ParamDirection) -> String {
510 match mapped.rust_to_ffi {
511 ConversionKind::Identity => mapped.cpp_type.clone(),
512 ConversionKind::ObjectRef => "void*".to_string(),
513 ConversionKind::StringUtf8 => unreachable!("strings handled by expand_input_sig"),
514 ConversionKind::EnumCast => enum_ffi_ctype(param.enum_underlying_type.as_deref()),
515 ConversionKind::IntCast => int_ctype(&mapped.cpp_type),
516 ConversionKind::StructOpaque => {
517 if dir == ParamDirection::InOut {
518 "uint8_t*".to_string() } else {
520 "const uint8_t*".to_string()
521 }
522 }
523 ConversionKind::FName => "uint64_t".to_string(),
524 ConversionKind::ContainerArray | ConversionKind::ContainerMap | ConversionKind::ContainerSet
525 | ConversionKind::Delegate | ConversionKind::MulticastDelegate =>
526 unreachable!("container/delegate types are property-only, never function params"),
527 }
528}
529
530fn scalar_output_cpp_type(mapped: &MappedType, param: &ParamInfo) -> String {
532 match mapped.ffi_to_rust {
533 ConversionKind::Identity => format!("{}*", mapped.cpp_type),
534 ConversionKind::ObjectRef => "void**".to_string(),
535 ConversionKind::StringUtf8 => unreachable!("strings handled by expand_*_sig"),
536 ConversionKind::EnumCast => {
537 format!("{}*", enum_ffi_ctype(param.enum_underlying_type.as_deref()))
538 }
539 ConversionKind::IntCast => format!("{}*", int_ctype(&mapped.cpp_type)),
540 ConversionKind::StructOpaque => "uint8_t*".to_string(),
541 ConversionKind::FName => "uint64_t*".to_string(),
542 ConversionKind::ContainerArray | ConversionKind::ContainerMap | ConversionKind::ContainerSet
543 | ConversionKind::Delegate | ConversionKind::MulticastDelegate =>
544 unreachable!("container/delegate types are property-only, never function params"),
545 }
546}
547
548fn enum_ffi_ctype(underlying: Option<&str>) -> String {
549 match underlying.unwrap_or("uint8") {
550 "uint8" => "uint8_t",
551 "int8" => "int8_t",
552 "uint16" => "uint16_t",
553 "int16" => "int16_t",
554 "uint32" => "uint32_t",
555 "int32" => "int32_t",
556 "uint64" => "uint64_t",
557 "int64" => "int64_t",
558 _ => "uint8_t",
559 }
560 .to_string()
561}
562
563fn int_ctype(cpp_type: &str) -> String {
564 match cpp_type {
565 "uint8" => "uint8_t",
566 "int8" => "int8_t",
567 "uint16" => "uint16_t",
568 "int16" => "int16_t",
569 "uint32" => "uint32_t",
570 "int32" => "int32_t",
571 "uint64" => "uint64_t",
572 "int64" => "int64_t",
573 _ => "int32_t",
574 }
575 .to_string()
576}
577
578fn resolve_struct_opaque_cpp_type(param: &ParamInfo, ctx: &CodegenContext) -> String {
581 match param.prop_type.as_str() {
582 "SoftObjectProperty" => {
583 let cls_cpp = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
584 format!("TSoftObjectPtr<{cls_cpp}>")
585 }
586 "WeakObjectProperty" => {
587 let cls_cpp = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
588 format!("TWeakObjectPtr<{cls_cpp}>")
589 }
590 _ => {
591 format!("F{}", param.struct_name.as_deref().unwrap_or("Unknown"))
592 }
593 }
594}
595
596fn resolve_object_cpp_type_bare(ctx: &CodegenContext, class_name: Option<&str>) -> String {
598 match class_name {
599 Some(cn) => {
600 if let Some(cls) = ctx.classes.get(cn) {
601 cls.cpp_name.clone()
602 } else {
603 cn.to_string()
604 }
605 }
606 None => "UObject".to_string(),
607 }
608}
609
610fn resolve_object_cpp_type(ctx: &CodegenContext, class_name: Option<&str>) -> String {
612 match class_name {
613 Some(cn) => {
614 if let Some(cls) = ctx.classes.get(cn) {
615 format!("{}*", cls.cpp_name)
616 } else {
617 format!("{cn}*")
619 }
620 }
621 None => "UObject*".to_string(),
622 }
623}
624
625fn input_arg_expression(param: &ParamInfo, dir: ParamDirection, ctx: &CodegenContext) -> String {
631 if is_container_param(param) {
632 return format!("__Container_{}", param.name);
633 }
634 let mapped = map_param(param);
635
636 match mapped.rust_to_ffi {
637 ConversionKind::Identity => param.name.clone(),
638 ConversionKind::ObjectRef => {
639 let cpp_type = resolve_object_cpp_type(ctx, param.class_name.as_deref());
640 match param.prop_type.as_str() {
641 "SoftObjectProperty" => {
643 let bare_type = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
644 format!("TSoftObjectPtr<{bare_type}>(static_cast<{cpp_type}>({}))", param.name)
645 }
646 "WeakObjectProperty" => {
647 let bare_type = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
648 format!("TWeakObjectPtr<{bare_type}>(static_cast<{cpp_type}>({}))", param.name)
649 }
650 _ => format!("static_cast<{cpp_type}>({})", param.name),
651 }
652 }
653 ConversionKind::StringUtf8 => {
654 if dir == ParamDirection::InOut {
655 format!("__InOut{}", param.name)
657 } else {
658 let fstring_expr = format!(
659 "FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str()))",
660 name = param.name
661 );
662 if param.prop_type == "TextProperty" {
663 format!("FText::FromString({fstring_expr})")
664 } else {
665 fstring_expr
666 }
667 }
668 }
669 ConversionKind::EnumCast => {
670 let cpp_enum = resolve_enum_cpp_type(param);
671 format!("static_cast<{cpp_enum}>({})", param.name)
672 }
673 ConversionKind::StructOpaque => {
674 if dir == ParamDirection::InOut {
675 format!("__InOut{}", param.name)
677 } else {
678 let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
679 format!(
680 "*reinterpret_cast<const {struct_cpp}*>({})",
681 param.name
682 )
683 }
684 }
685 ConversionKind::IntCast => param.name.clone(), ConversionKind::FName => {
687 format!("RustealUnpackFName({})", param.name)
688 }
689 ConversionKind::ContainerArray | ConversionKind::ContainerMap | ConversionKind::ContainerSet
690 | ConversionKind::Delegate | ConversionKind::MulticastDelegate =>
691 unreachable!("container/delegate types are property-only, never function params"),
692 }
693}
694
695fn return_strategy(param: &ParamInfo) -> ReturnStrategy {
700 if is_container_param(param) {
701 return ReturnStrategy::ContainerReturn;
702 }
703 let mapped = map_param(param);
704
705 match mapped.ffi_to_rust {
706 ConversionKind::Identity | ConversionKind::IntCast => ReturnStrategy::Direct,
707 ConversionKind::ObjectRef => {
708 match param.prop_type.as_str() {
711 "SoftObjectProperty" | "WeakObjectProperty" => ReturnStrategy::SoftWeakObjectRef,
712 _ => ReturnStrategy::ObjectRef,
713 }
714 }
715 ConversionKind::StringUtf8 => {
716 if param.prop_type == "TextProperty" {
717 ReturnStrategy::TextReturn
718 } else {
719 ReturnStrategy::StringReturn
720 }
721 }
722 ConversionKind::EnumCast => {
723 let ctype = match mapped.rust_ffi_type.as_str() {
724 "u8" => "uint8_t",
725 "i8" => "int8_t",
726 "i32" => "int32_t",
727 _ => "uint8_t",
728 };
729 ReturnStrategy::Cast(ctype.to_string())
730 }
731 ConversionKind::StructOpaque => ReturnStrategy::StructReturn,
732 ConversionKind::FName => ReturnStrategy::FNameReturn,
733 ConversionKind::ContainerArray | ConversionKind::ContainerMap | ConversionKind::ContainerSet
734 | ConversionKind::Delegate | ConversionKind::MulticastDelegate =>
735 unreachable!("container/delegate types are property-only, never function return types"),
736 }
737}
738
739fn emit_return(out: &mut String, strategy: &ReturnStrategy, call_expr: &str) {
740 match strategy {
741 ReturnStrategy::Direct => {
742 out.push_str(&format!(" *OutReturnValue = {call_expr};\n"));
743 }
744 ReturnStrategy::ObjectRef => {
745 out.push_str(&format!(
747 " *OutReturnValue = const_cast<void*>(static_cast<const void*>({call_expr}));\n"
748 ));
749 }
750 ReturnStrategy::SoftWeakObjectRef => {
751 out.push_str(&format!(
754 " *OutReturnValue = const_cast<void*>(static_cast<const void*>({call_expr}.Get()));\n"
755 ));
756 }
757 ReturnStrategy::Cast(ctype) => {
758 out.push_str(&format!(
759 " *OutReturnValue = static_cast<{ctype}>({call_expr});\n"
760 ));
761 }
762 ReturnStrategy::StringReturn => {
763 out.push_str(&format!(" FString __RustealResult = {call_expr};\n"));
764 out.push_str(
765 " auto __RustealUtf8 = StringCast<ANSICHAR>(*__RustealResult);\n\
766 \x20 int32 __RustealLen = __RustealUtf8.Length();\n\
767 \x20 if (__RustealLen > static_cast<int32>(BufLen)) __RustealLen = static_cast<int32>(BufLen);\n\
768 \x20 FMemory::Memcpy(OutBuf, __RustealUtf8.Get(), __RustealLen);\n\
769 \x20 *OutLen = static_cast<uint32_t>(__RustealLen);\n",
770 );
771 }
772 ReturnStrategy::TextReturn => {
773 out.push_str(&format!(" FString __RustealResult = ({call_expr}).ToString();\n"));
774 out.push_str(
775 " auto __RustealUtf8 = StringCast<ANSICHAR>(*__RustealResult);\n\
776 \x20 int32 __RustealLen = __RustealUtf8.Length();\n\
777 \x20 if (__RustealLen > static_cast<int32>(BufLen)) __RustealLen = static_cast<int32>(BufLen);\n\
778 \x20 FMemory::Memcpy(OutBuf, __RustealUtf8.Get(), __RustealLen);\n\
779 \x20 *OutLen = static_cast<uint32_t>(__RustealLen);\n",
780 );
781 }
782 ReturnStrategy::StructReturn => {
783 out.push_str(&format!(" auto __RustealResult = {call_expr};\n"));
784 out.push_str(
785 " FMemory::Memcpy(OutReturnValue, &__RustealResult, sizeof(__RustealResult));\n",
786 );
787 }
788 ReturnStrategy::FNameReturn => {
789 out.push_str(&format!(" FName __RustealResult = {call_expr};\n"));
790 out.push_str(
791 " *OutReturnValue = RustealPackFName(__RustealResult);\n",
792 );
793 }
794 ReturnStrategy::ContainerReturn => {
795 out.push_str(&format!(
800 " using __RustealReturnType = std::decay_t<decltype({call_expr})>;\n\
801 \x20 *reinterpret_cast<__RustealReturnType*>(\n\
802 \x20 static_cast<FProperty*>(OutReturnValue_Prop)->ContainerPtrToValuePtr<void>(OutReturnValue_Base))\n\
803 \x20 = {call_expr};\n"
804 ));
805 }
806 }
807}
808
809fn wrap_enum_as_byte(name: &str, cpp_form: Option<u32>) -> String {
814 let form = cpp_form.unwrap_or(2);
815 match form {
816 0 => format!("TEnumAsByte<{name}>"),
817 1 => format!("TEnumAsByte<{name}::Type>"),
818 _ => name.to_string(),
819 }
820}
821
822fn resolve_enum_cpp_type(param: &ParamInfo) -> String {
826 let name = param
827 .enum_cpp_name
828 .as_deref()
829 .or(param.enum_name.as_deref())
830 .unwrap_or("uint8");
831 let form = param.enum_cpp_form.unwrap_or(2);
832 if form == 1 {
833 format!("{name}::Type")
835 } else {
836 name.to_string()
838 }
839}
840
841fn emit_post_call(out: &mut String, action: &PostCallAction) {
842 match action {
843 PostCallAction::StringOutput(name) => {
844 out.push_str(&format!(
845 " {{\n\
846 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__Out{name});\n\
847 \x20 int32 __Len = __Utf8.Length();\n\
848 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
849 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
850 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
851 \x20 }}\n"
852 ));
853 }
854 PostCallAction::TextOutput(name) => {
855 out.push_str(&format!(
857 " {{\n\
858 \x20 FString __Str = __Out{name}.ToString();\n\
859 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__Str);\n\
860 \x20 int32 __Len = __Utf8.Length();\n\
861 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
862 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
863 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
864 \x20 }}\n"
865 ));
866 }
867 PostCallAction::StructOutput { name, struct_cpp } => {
868 out.push_str(&format!(
869 " if (Out{name}) {{ FMemory::Memcpy(Out{name}, &__Out{name}, sizeof({struct_cpp})); }}\n"
870 ));
871 }
872 PostCallAction::ObjectOutput(name) => {
873 out.push_str(&format!(
874 " *Out{name} = static_cast<void*>(__Out{name});\n"
875 ));
876 }
877 PostCallAction::SoftWeakObjectOutput(name) => {
878 out.push_str(&format!(
879 " *Out{name} = static_cast<void*>(__Out{name}.Get());\n"
880 ));
881 }
882 PostCallAction::FNameOutput(name) => {
883 out.push_str(&format!(
884 " *Out{name} = RustealPackFName(__Out{name});\n"
885 ));
886 }
887 PostCallAction::EnumOutput { name, ffi_type } => {
888 out.push_str(&format!(
889 " *Out{name} = static_cast<{ffi_type}>(__Out{name});\n"
890 ));
891 }
892 PostCallAction::InOutStructCopyback { name, struct_cpp } => {
893 out.push_str(&format!(
894 " if ({name}) {{ FMemory::Memcpy({name}, &__InOut{name}, sizeof({struct_cpp})); }}\n"
895 ));
896 }
897 PostCallAction::InOutStringCopyback(name) => {
898 out.push_str(&format!(
899 " {{\n\
900 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__InOut{name});\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 PostCallAction::InOutTextCopyback(name) => {
909 out.push_str(&format!(
910 " {{\n\
911 \x20 FString __Str = __InOut{name}.ToString();\n\
912 \x20 auto __Utf8 = StringCast<ANSICHAR>(*__Str);\n\
913 \x20 int32 __Len = __Utf8.Length();\n\
914 \x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
915 \x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
916 \x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
917 \x20 }}\n"
918 ));
919 }
920 }
921}
922
923fn is_container_param(param: &ParamInfo) -> bool {
928 matches!(
929 param.prop_type.as_str(),
930 "ArrayProperty" | "MapProperty" | "SetProperty"
931 )
932}
933
934fn resolve_inner_cpp_type(inner: &PropertyInfo, ctx: &CodegenContext) -> String {
936 match inner.prop_type.as_str() {
937 "BoolProperty" => "bool".to_string(),
938 "Int8Property" => "int8".to_string(),
939 "ByteProperty" => {
940 if let Some(ref en) = inner.enum_name {
941 let name = inner
942 .enum_cpp_name
943 .as_deref()
944 .unwrap_or(en.as_str());
945 wrap_enum_as_byte(name, inner.enum_cpp_form)
946 } else {
947 "uint8".to_string()
948 }
949 }
950 "Int16Property" => "int16".to_string(),
951 "UInt16Property" => "uint16".to_string(),
952 "IntProperty" => "int32".to_string(),
953 "UInt32Property" => "uint32".to_string(),
954 "Int64Property" => "int64".to_string(),
955 "UInt64Property" => "uint64".to_string(),
956 "FloatProperty" => "float".to_string(),
957 "DoubleProperty" => "double".to_string(),
958 "StrProperty" => "FString".to_string(),
959 "TextProperty" => "FText".to_string(),
960 "NameProperty" => "FName".to_string(),
961 "ObjectProperty" => {
962 resolve_object_cpp_type(ctx, inner.class_name.as_deref())
963 }
964 "SoftObjectProperty" => {
965 let cls_cpp = resolve_object_cpp_type_bare(ctx, inner.class_name.as_deref());
966 format!("TSoftObjectPtr<{cls_cpp}>")
967 }
968 "WeakObjectProperty" => {
969 let cls_cpp = resolve_object_cpp_type_bare(ctx, inner.class_name.as_deref());
970 format!("TWeakObjectPtr<{cls_cpp}>")
971 }
972 "ClassProperty" => {
973 match inner.meta_class_name.as_deref() {
977 Some(meta) => {
978 let meta_cpp = resolve_object_cpp_type_bare(ctx, Some(meta));
979 format!("TSubclassOf<{meta_cpp}>")
980 }
981 None => "UClass*".to_string(),
982 }
983 }
984 "InterfaceProperty" => {
985 resolve_object_cpp_type(ctx, inner.interface_name.as_deref())
986 }
987 "EnumProperty" => {
988 let name = inner
989 .enum_cpp_name
990 .as_deref()
991 .or(inner.enum_name.as_deref())
992 .unwrap_or("uint8");
993 wrap_enum_as_byte(name, inner.enum_cpp_form)
994 }
995 "StructProperty" => {
996 if let Some(ref sn) = inner.struct_name {
997 if let Some(si) = ctx.structs.get(sn.as_str()) {
998 si.cpp_name.clone()
999 } else {
1000 format!("F{sn}")
1001 }
1002 } else {
1003 "uint8".to_string()
1004 }
1005 }
1006 _ => "uint8".to_string(),
1007 }
1008}
1009
1010fn resolve_container_cpp_type(param: &ParamInfo, ctx: &CodegenContext) -> Option<String> {
1012 match param.prop_type.as_str() {
1013 "ArrayProperty" => {
1014 let inner = param.inner_prop.as_ref()?;
1015 let elem = resolve_inner_cpp_type(inner, ctx);
1016 Some(format!("TArray<{elem}>"))
1017 }
1018 "MapProperty" => {
1019 let key = param.key_prop.as_ref()?;
1020 let val = param.value_prop.as_ref()?;
1021 let kt = resolve_inner_cpp_type(key, ctx);
1022 let vt = resolve_inner_cpp_type(val, ctx);
1023 Some(format!("TMap<{kt}, {vt}>"))
1024 }
1025 "SetProperty" => {
1026 let elem = param.element_prop.as_ref()?;
1027 let et = resolve_inner_cpp_type(elem, ctx);
1028 Some(format!("TSet<{et}>"))
1029 }
1030 _ => None,
1031 }
1032}
1033
1034fn collect_param_headers(
1036 param: &ParamInfo,
1037 ctx: &CodegenContext,
1038 includes: &mut BTreeSet<String>,
1039) {
1040 for cls_name in [
1042 param.class_name.as_deref(),
1043 param.meta_class_name.as_deref(),
1044 ]
1045 .into_iter()
1046 .flatten()
1047 {
1048 if let Some(cls) = ctx.classes.get(cls_name)
1049 && !cls.header.is_empty()
1050 {
1051 includes.insert(format!("\"{}\"", cls.header));
1052 }
1053 }
1054
1055 if let Some(sn) = param.struct_name.as_deref() {
1059 include_struct_header(sn, ctx, includes);
1060 }
1061
1062 for inner in [
1064 param.inner_prop.as_deref(),
1065 param.key_prop.as_deref(),
1066 param.value_prop.as_deref(),
1067 param.element_prop.as_deref(),
1068 ]
1069 .into_iter()
1070 .flatten()
1071 {
1072 for cls_name in [
1073 inner.class_name.as_deref(),
1074 inner.meta_class_name.as_deref(),
1075 ]
1076 .into_iter()
1077 .flatten()
1078 {
1079 if let Some(cls) = ctx.classes.get(cls_name)
1080 && !cls.header.is_empty()
1081 {
1082 includes.insert(format!("\"{}\"", cls.header));
1083 }
1084 }
1085 if let Some(sn) = inner.struct_name.as_deref() {
1086 include_struct_header(sn, ctx, includes);
1087 }
1088 }
1089}
1090
1091fn include_struct_header(struct_name: &str, ctx: &CodegenContext, includes: &mut BTreeSet<String>) {
1092 if let Some(st) = ctx.structs.get(struct_name)
1093 && !st.header.is_empty()
1094 {
1095 includes.insert(format!("\"{}\"", st.header));
1096 }
1097}