Skip to main content

rusteal_codegen/cpp_gen/
wrapper.rs

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