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