use std::collections::BTreeSet;
use crate::context::{CodegenContext, FuncEntry};
use crate::schema::*;
use crate::type_map::{self, ConversionKind, MappedType, ParamDirection};
enum ReturnStrategy {
Direct,
ObjectRef,
SoftWeakObjectRef,
Cast(String),
StringReturn,
TextReturn,
StructReturn,
FNameReturn,
ContainerReturn,
}
enum PostCallAction {
StringOutput(String),
TextOutput(String),
StructOutput { name: String, struct_cpp: String },
ObjectOutput(String),
SoftWeakObjectOutput(String),
FNameOutput(String),
EnumOutput { name: String, ffi_type: String },
InOutStructCopyback { name: String, struct_cpp: String },
InOutStringCopyback(String),
InOutTextCopyback(String),
}
pub fn generate_wrapper_file(entries: &[&FuncEntry], ctx: &CodegenContext) -> String {
let mut out = String::with_capacity(entries.len() * 512 + 256);
out.push_str("// Auto-generated by rusteal-codegen. Do not edit.\n\n");
let mut includes = BTreeSet::new();
includes.insert("\"RustealApiTable.h\"".to_string());
let has_container_params = entries
.iter()
.any(|e| e.func.params.iter().any(is_container_param));
for entry in entries {
if !entry.header.is_empty() {
includes.insert(format!("\"{}\"", entry.header));
}
for param in &entry.func.params {
collect_param_headers(param, ctx, &mut includes);
}
}
if has_container_params {
includes.insert("\"UObject/UnrealType.h\"".to_string());
}
let has_fname = entries.iter().any(|e| {
e.func
.params
.iter()
.any(|p| matches!(map_param(p).rust_to_ffi, ConversionKind::FName))
});
if has_fname {
includes.insert("\"RustealFNameHelper.h\"".to_string());
}
for inc in &includes {
out.push_str(&format!("#include {inc}\n"));
}
out.push_str("#include <string>\n");
out.push_str("#include <type_traits>\n");
out.push('\n');
out.push_str("#ifdef _MSC_VER\n");
out.push_str("#pragma warning(push)\n");
out.push_str("#pragma warning(disable: 4996)\n");
out.push_str("#endif\n\n");
out.push_str("#ifndef RUSTEAL_ERROR_CODES_DEFINED\n");
out.push_str("#define RUSTEAL_ERROR_CODES_DEFINED\n");
out.push_str("static constexpr uint32_t RustealErrorCode_Ok = 0;\n");
out.push_str("static constexpr uint32_t RustealErrorCode_ObjectDestroyed = 1;\n");
out.push_str("#endif\n\n");
for entry in entries {
generate_wrapper_function(&mut out, entry, ctx);
}
out.push_str("#ifdef _MSC_VER\n");
out.push_str("#pragma warning(pop)\n");
out.push_str("#endif\n");
out
}
pub fn cpp_wrapper_name(prefix: &str, class_name: &str, func_name: &str) -> String {
format!("{prefix}Rusteal_{class_name}_{func_name}")
}
fn generate_wrapper_function(out: &mut String, entry: &FuncEntry, ctx: &CodegenContext) {
let func = &entry.func;
let class_cpp = &entry.cpp_class_name;
let func_name = &entry.func_name;
let is_static = func.is_static || (func.func_flags & FUNC_STATIC != 0);
let is_blueprint_native =
(func.func_flags & FUNC_BLUEPRINT_EVENT != 0) && (func.func_flags & FUNC_NATIVE != 0);
let c_func_name = cpp_wrapper_name(&ctx.cpp_prefix, &entry.class_name, func_name);
let mut inputs = Vec::new();
let mut outputs = Vec::new();
let mut return_param: Option<&ParamInfo> = None;
for param in &func.params {
let dir = type_map::param_direction(param);
match dir {
ParamDirection::Return => return_param = Some(param),
ParamDirection::In | ParamDirection::InOut => inputs.push((param, dir)),
ParamDirection::Out => outputs.push(param),
}
}
out.push_str(&format!("extern \"C\" uint32_t {c_func_name}(\n"));
let mut params_str = Vec::new();
if !is_static {
params_str.push(" void* Obj".to_string());
}
for param in &func.params {
let dir = type_map::param_direction(param);
match dir {
ParamDirection::In | ParamDirection::InOut => {
expand_input_sig(&mut params_str, param, dir);
}
ParamDirection::Out => {
expand_output_sig(&mut params_str, param);
}
ParamDirection::Return => {
expand_return_sig(&mut params_str, param);
}
}
}
out.push_str(¶ms_str.join(",\n"));
out.push_str("\n) {\n");
if !is_static {
out.push_str(&format!(
" {class_cpp}* Self = static_cast<{class_cpp}*>(Obj);\n\
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT\n\
\x20 if (!IsValid(Self)) return RustealErrorCode_ObjectDestroyed;\n\
#endif\n"
));
}
for param in &func.params {
if !is_container_param(param) {
continue;
}
let dir = type_map::param_direction(param);
if dir == ParamDirection::Return {
continue;
}
let name = ¶m.name;
let cpp_type = resolve_container_cpp_type(param, ctx)
.unwrap_or_else(|| "void /* ERROR */".to_string());
let (var_name, base_name, prop_name) = if dir == ParamDirection::Return {
(
"ReturnValue".to_string(),
"OutReturnValue_Base".to_string(),
"OutReturnValue_Prop".to_string(),
)
} else {
(name.clone(), format!("{name}_Base"), format!("{name}_Prop"))
};
out.push_str(&format!(
" auto& __Container_{var_name} = *reinterpret_cast<{cpp_type}*>(\n\
\x20 static_cast<FProperty*>({prop_name})->ContainerPtrToValuePtr<void>({base_name}));\n"
));
}
let mut post_call_actions = Vec::new();
for param in &outputs {
if is_container_param(param) {
continue;
}
let mapped = map_param(param);
let name = ¶m.name;
match mapped.ffi_to_rust {
ConversionKind::StringUtf8 => {
if param.prop_type == "TextProperty" {
out.push_str(&format!(" FText __Out{name};\n"));
post_call_actions.push(PostCallAction::TextOutput(name.clone()));
} else {
out.push_str(&format!(" FString __Out{name};\n"));
post_call_actions.push(PostCallAction::StringOutput(name.clone()));
}
}
ConversionKind::StructOpaque => {
let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
out.push_str(&format!(" {struct_cpp} __Out{name};\n"));
post_call_actions.push(PostCallAction::StructOutput {
name: name.clone(),
struct_cpp,
});
}
ConversionKind::ObjectRef => match param.prop_type.as_str() {
"SoftObjectProperty" => {
let bare = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
out.push_str(&format!(" TSoftObjectPtr<{bare}> __Out{name};\n"));
post_call_actions.push(PostCallAction::SoftWeakObjectOutput(name.clone()));
}
"WeakObjectProperty" => {
let bare = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
out.push_str(&format!(" TWeakObjectPtr<{bare}> __Out{name};\n"));
post_call_actions.push(PostCallAction::SoftWeakObjectOutput(name.clone()));
}
_ => {
let obj_cpp = resolve_object_cpp_type(ctx, param.class_name.as_deref());
out.push_str(&format!(" {obj_cpp} __Out{name} = nullptr;\n"));
post_call_actions.push(PostCallAction::ObjectOutput(name.clone()));
}
},
ConversionKind::FName => {
out.push_str(&format!(" FName __Out{name};\n"));
post_call_actions.push(PostCallAction::FNameOutput(name.clone()));
}
ConversionKind::EnumCast => {
let cpp_enum = resolve_enum_cpp_type(param);
let form = param.enum_cpp_form.unwrap_or(2);
let local_type = match form {
0 | 1 => format!("TEnumAsByte<{cpp_enum}>"),
_ => cpp_enum,
};
out.push_str(&format!(" {local_type} __Out{name};\n"));
post_call_actions.push(PostCallAction::EnumOutput {
name: name.clone(),
ffi_type: enum_ffi_ctype(param.enum_underlying_type.as_deref()),
});
}
_ => {}
}
}
for param in &func.params {
let dir = type_map::param_direction(param);
if dir == ParamDirection::InOut {
let mapped = map_param(param);
if mapped.rust_to_ffi == ConversionKind::StructOpaque {
let name = ¶m.name;
let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
out.push_str(&format!(" {struct_cpp} __InOut{name};\n"));
out.push_str(&format!(" if ({name}) {{ FMemory::Memcpy(&__InOut{name}, {name}, sizeof({struct_cpp})); }}\n"));
post_call_actions.push(PostCallAction::InOutStructCopyback {
name: name.clone(),
struct_cpp,
});
}
}
}
for param in &func.params {
let dir = type_map::param_direction(param);
if dir == ParamDirection::InOut {
let mapped = map_param(param);
if mapped.rust_to_ffi == ConversionKind::StringUtf8 {
let name = ¶m.name;
if param.prop_type == "TextProperty" {
out.push_str(&format!(
" FText __InOut{name} = FText::FromString(FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str())));\n"
));
post_call_actions.push(PostCallAction::InOutTextCopyback(name.clone()));
} else {
out.push_str(&format!(
" FString __InOut{name} = FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str()));\n"
));
post_call_actions.push(PostCallAction::InOutStringCopyback(name.clone()));
}
}
}
}
let mut call_args = Vec::new();
for param in &func.params {
let dir = type_map::param_direction(param);
match dir {
ParamDirection::Return => {}
ParamDirection::In | ParamDirection::InOut => {
call_args.push(input_arg_expression(param, dir, ctx));
}
ParamDirection::Out => {
if is_container_param(param) {
call_args.push(format!("__Container_{}", param.name));
} else {
let mapped = map_param(param);
match mapped.ffi_to_rust {
ConversionKind::StringUtf8
| ConversionKind::StructOpaque
| ConversionKind::ObjectRef
| ConversionKind::FName
| ConversionKind::EnumCast => {
call_args.push(format!("__Out{}", param.name));
}
_ => {
call_args.push(format!("*Out{}", param.name));
}
}
}
}
}
}
let args = call_args.join(", ");
let is_protected = func.func_flags & FUNC_PROTECTED != 0;
let call_expr = if is_protected && func.interface.is_none() && !is_blueprint_native {
out.push_str(&format!(
" struct FAccess : {class_cpp} {{ using {class_cpp}::{func_name}; }};\n"
));
if is_static {
format!("FAccess::{func_name}({args})")
} else {
format!("(Self->*&FAccess::{func_name})({args})")
}
} else if is_static {
format!("{class_cpp}::{func_name}({args})")
} else if let Some(iface) = &func.interface {
format!("static_cast<{iface}*>(Self)->{func_name}({args})")
} else if is_blueprint_native {
out.push_str(&format!(
" struct FAccess : {class_cpp} {{ using {class_cpp}::{func_name}_Implementation; }};\n"
));
format!("(Self->*&FAccess::{func_name}_Implementation)({args})")
} else {
format!("Self->{func_name}({args})")
};
if let Some(rp) = return_param {
let strategy = return_strategy(rp);
emit_return(out, &strategy, &call_expr);
} else {
out.push_str(&format!(" {call_expr};\n"));
}
for action in &post_call_actions {
emit_post_call(out, action);
}
out.push_str(" return RustealErrorCode_Ok;\n");
out.push_str("}\n\n");
}
fn expand_input_sig(params: &mut Vec<String>, param: &ParamInfo, dir: ParamDirection) {
if is_container_param(param) {
let name = ¶m.name;
params.push(format!(" void* {name}_Base"));
params.push(format!(" void* {name}_Prop"));
return;
}
let mapped = map_param(param);
let name = ¶m.name;
match mapped.rust_to_ffi {
ConversionKind::StringUtf8 => {
params.push(format!(" const char* {name}"));
params.push(format!(" uint32_t {name}Len"));
if dir == ParamDirection::InOut {
params.push(format!(" char* Out{name}"));
params.push(format!(" uint32_t Out{name}BufLen"));
params.push(format!(" uint32_t* Out{name}Len"));
}
}
_ => {
let cpp_type = scalar_input_cpp_type(&mapped, param, dir);
params.push(format!(" {cpp_type} {name}"));
}
}
}
fn expand_output_sig(params: &mut Vec<String>, param: &ParamInfo) {
if is_container_param(param) {
let name = ¶m.name;
params.push(format!(" void* {name}_Base"));
params.push(format!(" void* {name}_Prop"));
return;
}
let mapped = map_param(param);
let name = ¶m.name;
match mapped.ffi_to_rust {
ConversionKind::StringUtf8 => {
params.push(format!(" char* Out{name}"));
params.push(format!(" uint32_t Out{name}BufLen"));
params.push(format!(" uint32_t* Out{name}Len"));
}
_ => {
let cpp_type = scalar_output_cpp_type(&mapped, param);
params.push(format!(" {cpp_type} Out{name}"));
}
}
}
fn expand_return_sig(params: &mut Vec<String>, param: &ParamInfo) {
if is_container_param(param) {
params.push(" void* OutReturnValue_Base".to_string());
params.push(" void* OutReturnValue_Prop".to_string());
return;
}
let mapped = map_param(param);
match mapped.ffi_to_rust {
ConversionKind::StringUtf8 => {
params.push(" char* OutBuf".to_string());
params.push(" uint32_t BufLen".to_string());
params.push(" uint32_t* OutLen".to_string());
}
_ => {
let cpp_type = scalar_output_cpp_type(&mapped, param);
params.push(format!(" {cpp_type} OutReturnValue"));
}
}
}
fn map_param(param: &ParamInfo) -> MappedType {
type_map::map_property_type(
¶m.prop_type,
param.class_name.as_deref(),
param.struct_name.as_deref(),
param.enum_name.as_deref(),
param.enum_underlying_type.as_deref(),
param.meta_class_name.as_deref(),
param.interface_name.as_deref(),
)
}
fn scalar_input_cpp_type(mapped: &MappedType, param: &ParamInfo, dir: ParamDirection) -> String {
match mapped.rust_to_ffi {
ConversionKind::Identity => mapped.cpp_type.clone(),
ConversionKind::ObjectRef => "void*".to_string(),
ConversionKind::StringUtf8 => unreachable!("strings handled by expand_input_sig"),
ConversionKind::EnumCast => enum_ffi_ctype(param.enum_underlying_type.as_deref()),
ConversionKind::IntCast => int_ctype(&mapped.cpp_type),
ConversionKind::StructOpaque => {
if dir == ParamDirection::InOut {
"uint8_t*".to_string()
} else {
"const uint8_t*".to_string()
}
}
ConversionKind::FName => "uint64_t".to_string(),
ConversionKind::ContainerArray
| ConversionKind::ContainerMap
| ConversionKind::ContainerSet
| ConversionKind::Delegate
| ConversionKind::MulticastDelegate => {
unreachable!("container/delegate types are property-only, never function params")
}
}
}
fn scalar_output_cpp_type(mapped: &MappedType, param: &ParamInfo) -> String {
match mapped.ffi_to_rust {
ConversionKind::Identity => format!("{}*", mapped.cpp_type),
ConversionKind::ObjectRef => "void**".to_string(),
ConversionKind::StringUtf8 => unreachable!("strings handled by expand_*_sig"),
ConversionKind::EnumCast => {
format!("{}*", enum_ffi_ctype(param.enum_underlying_type.as_deref()))
}
ConversionKind::IntCast => format!("{}*", int_ctype(&mapped.cpp_type)),
ConversionKind::StructOpaque => "uint8_t*".to_string(),
ConversionKind::FName => "uint64_t*".to_string(),
ConversionKind::ContainerArray
| ConversionKind::ContainerMap
| ConversionKind::ContainerSet
| ConversionKind::Delegate
| ConversionKind::MulticastDelegate => {
unreachable!("container/delegate types are property-only, never function params")
}
}
}
fn enum_ffi_ctype(underlying: Option<&str>) -> String {
match underlying.unwrap_or("uint8") {
"uint8" => "uint8_t",
"int8" => "int8_t",
"uint16" => "uint16_t",
"int16" => "int16_t",
"uint32" => "uint32_t",
"int32" => "int32_t",
"uint64" => "uint64_t",
"int64" => "int64_t",
_ => "uint8_t",
}
.to_string()
}
fn int_ctype(cpp_type: &str) -> String {
match cpp_type {
"uint8" => "uint8_t",
"int8" => "int8_t",
"uint16" => "uint16_t",
"int16" => "int16_t",
"uint32" => "uint32_t",
"int32" => "int32_t",
"uint64" => "uint64_t",
"int64" => "int64_t",
_ => "int32_t",
}
.to_string()
}
fn resolve_struct_opaque_cpp_type(param: &ParamInfo, ctx: &CodegenContext) -> String {
match param.prop_type.as_str() {
"SoftObjectProperty" => {
let cls_cpp = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
format!("TSoftObjectPtr<{cls_cpp}>")
}
"WeakObjectProperty" => {
let cls_cpp = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
format!("TWeakObjectPtr<{cls_cpp}>")
}
_ => {
format!("F{}", param.struct_name.as_deref().unwrap_or("Unknown"))
}
}
}
fn resolve_object_cpp_type_bare(ctx: &CodegenContext, class_name: Option<&str>) -> String {
match class_name {
Some(cn) => {
if let Some(cls) = ctx.classes.get(cn) {
cls.cpp_name.clone()
} else {
cn.to_string()
}
}
None => "UObject".to_string(),
}
}
fn resolve_object_cpp_type(ctx: &CodegenContext, class_name: Option<&str>) -> String {
match class_name {
Some(cn) => {
if let Some(cls) = ctx.classes.get(cn) {
format!("{}*", cls.cpp_name)
} else {
format!("{cn}*")
}
}
None => "UObject*".to_string(),
}
}
fn input_arg_expression(param: &ParamInfo, dir: ParamDirection, ctx: &CodegenContext) -> String {
if is_container_param(param) {
return format!("__Container_{}", param.name);
}
let mapped = map_param(param);
match mapped.rust_to_ffi {
ConversionKind::Identity => param.name.clone(),
ConversionKind::ObjectRef => {
let cpp_type = resolve_object_cpp_type(ctx, param.class_name.as_deref());
match param.prop_type.as_str() {
"SoftObjectProperty" => {
let bare_type = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
format!(
"TSoftObjectPtr<{bare_type}>(static_cast<{cpp_type}>({}))",
param.name
)
}
"WeakObjectProperty" => {
let bare_type = resolve_object_cpp_type_bare(ctx, param.class_name.as_deref());
format!(
"TWeakObjectPtr<{bare_type}>(static_cast<{cpp_type}>({}))",
param.name
)
}
_ => format!("static_cast<{cpp_type}>({})", param.name),
}
}
ConversionKind::StringUtf8 => {
if dir == ParamDirection::InOut {
format!("__InOut{}", param.name)
} else {
let fstring_expr = format!(
"FString(UTF8_TO_TCHAR(std::string({name}, {name}Len).c_str()))",
name = param.name
);
if param.prop_type == "TextProperty" {
format!("FText::FromString({fstring_expr})")
} else {
fstring_expr
}
}
}
ConversionKind::EnumCast => {
let cpp_enum = resolve_enum_cpp_type(param);
format!("static_cast<{cpp_enum}>({})", param.name)
}
ConversionKind::StructOpaque => {
if dir == ParamDirection::InOut {
format!("__InOut{}", param.name)
} else {
let struct_cpp = resolve_struct_opaque_cpp_type(param, ctx);
format!("*reinterpret_cast<const {struct_cpp}*>({})", param.name)
}
}
ConversionKind::IntCast => param.name.clone(),
ConversionKind::FName => {
format!("RustealUnpackFName({})", param.name)
}
ConversionKind::ContainerArray
| ConversionKind::ContainerMap
| ConversionKind::ContainerSet
| ConversionKind::Delegate
| ConversionKind::MulticastDelegate => {
unreachable!("container/delegate types are property-only, never function params")
}
}
}
fn return_strategy(param: &ParamInfo) -> ReturnStrategy {
if is_container_param(param) {
return ReturnStrategy::ContainerReturn;
}
let mapped = map_param(param);
match mapped.ffi_to_rust {
ConversionKind::Identity | ConversionKind::IntCast => ReturnStrategy::Direct,
ConversionKind::ObjectRef => match param.prop_type.as_str() {
"SoftObjectProperty" | "WeakObjectProperty" => ReturnStrategy::SoftWeakObjectRef,
_ => ReturnStrategy::ObjectRef,
},
ConversionKind::StringUtf8 => {
if param.prop_type == "TextProperty" {
ReturnStrategy::TextReturn
} else {
ReturnStrategy::StringReturn
}
}
ConversionKind::EnumCast => {
let ctype = match mapped.rust_ffi_type.as_str() {
"u8" => "uint8_t",
"i8" => "int8_t",
"i32" => "int32_t",
_ => "uint8_t",
};
ReturnStrategy::Cast(ctype.to_string())
}
ConversionKind::StructOpaque => ReturnStrategy::StructReturn,
ConversionKind::FName => ReturnStrategy::FNameReturn,
ConversionKind::ContainerArray
| ConversionKind::ContainerMap
| ConversionKind::ContainerSet
| ConversionKind::Delegate
| ConversionKind::MulticastDelegate => {
unreachable!("container/delegate types are property-only, never function return types")
}
}
}
fn emit_return(out: &mut String, strategy: &ReturnStrategy, call_expr: &str) {
match strategy {
ReturnStrategy::Direct => {
out.push_str(&format!(" *OutReturnValue = {call_expr};\n"));
}
ReturnStrategy::ObjectRef => {
out.push_str(&format!(
" *OutReturnValue = const_cast<void*>(static_cast<const void*>({call_expr}));\n"
));
}
ReturnStrategy::SoftWeakObjectRef => {
out.push_str(&format!(
" *OutReturnValue = const_cast<void*>(static_cast<const void*>({call_expr}.Get()));\n"
));
}
ReturnStrategy::Cast(ctype) => {
out.push_str(&format!(
" *OutReturnValue = static_cast<{ctype}>({call_expr});\n"
));
}
ReturnStrategy::StringReturn => {
out.push_str(&format!(" FString __RustealResult = {call_expr};\n"));
out.push_str(
" auto __RustealUtf8 = StringCast<ANSICHAR>(*__RustealResult);\n\
\x20 int32 __RustealLen = __RustealUtf8.Length();\n\
\x20 if (__RustealLen > static_cast<int32>(BufLen)) __RustealLen = static_cast<int32>(BufLen);\n\
\x20 FMemory::Memcpy(OutBuf, __RustealUtf8.Get(), __RustealLen);\n\
\x20 *OutLen = static_cast<uint32_t>(__RustealLen);\n",
);
}
ReturnStrategy::TextReturn => {
out.push_str(&format!(
" FString __RustealResult = ({call_expr}).ToString();\n"
));
out.push_str(
" auto __RustealUtf8 = StringCast<ANSICHAR>(*__RustealResult);\n\
\x20 int32 __RustealLen = __RustealUtf8.Length();\n\
\x20 if (__RustealLen > static_cast<int32>(BufLen)) __RustealLen = static_cast<int32>(BufLen);\n\
\x20 FMemory::Memcpy(OutBuf, __RustealUtf8.Get(), __RustealLen);\n\
\x20 *OutLen = static_cast<uint32_t>(__RustealLen);\n",
);
}
ReturnStrategy::StructReturn => {
out.push_str(&format!(" auto __RustealResult = {call_expr};\n"));
out.push_str(
" FMemory::Memcpy(OutReturnValue, &__RustealResult, sizeof(__RustealResult));\n",
);
}
ReturnStrategy::FNameReturn => {
out.push_str(&format!(" FName __RustealResult = {call_expr};\n"));
out.push_str(" *OutReturnValue = RustealPackFName(__RustealResult);\n");
}
ReturnStrategy::ContainerReturn => {
out.push_str(&format!(
" using __RustealReturnType = std::decay_t<decltype({call_expr})>;\n\
\x20 *reinterpret_cast<__RustealReturnType*>(\n\
\x20 static_cast<FProperty*>(OutReturnValue_Prop)->ContainerPtrToValuePtr<void>(OutReturnValue_Base))\n\
\x20 = {call_expr};\n"
));
}
}
}
fn wrap_enum_as_byte(name: &str, cpp_form: Option<u32>) -> String {
let form = cpp_form.unwrap_or(2);
match form {
0 => format!("TEnumAsByte<{name}>"),
1 => format!("TEnumAsByte<{name}::Type>"),
_ => name.to_string(),
}
}
fn resolve_enum_cpp_type(param: &ParamInfo) -> String {
let name = param
.enum_cpp_name
.as_deref()
.or(param.enum_name.as_deref())
.unwrap_or("uint8");
let form = param.enum_cpp_form.unwrap_or(2);
if form == 1 {
format!("{name}::Type")
} else {
name.to_string()
}
}
fn emit_post_call(out: &mut String, action: &PostCallAction) {
match action {
PostCallAction::StringOutput(name) => {
out.push_str(&format!(
" {{\n\
\x20 auto __Utf8 = StringCast<ANSICHAR>(*__Out{name});\n\
\x20 int32 __Len = __Utf8.Length();\n\
\x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
\x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
\x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
\x20 }}\n"
));
}
PostCallAction::TextOutput(name) => {
out.push_str(&format!(
" {{\n\
\x20 FString __Str = __Out{name}.ToString();\n\
\x20 auto __Utf8 = StringCast<ANSICHAR>(*__Str);\n\
\x20 int32 __Len = __Utf8.Length();\n\
\x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
\x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
\x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
\x20 }}\n"
));
}
PostCallAction::StructOutput { name, struct_cpp } => {
out.push_str(&format!(
" if (Out{name}) {{ FMemory::Memcpy(Out{name}, &__Out{name}, sizeof({struct_cpp})); }}\n"
));
}
PostCallAction::ObjectOutput(name) => {
out.push_str(&format!(
" *Out{name} = static_cast<void*>(__Out{name});\n"
));
}
PostCallAction::SoftWeakObjectOutput(name) => {
out.push_str(&format!(
" *Out{name} = static_cast<void*>(__Out{name}.Get());\n"
));
}
PostCallAction::FNameOutput(name) => {
out.push_str(&format!(
" *Out{name} = RustealPackFName(__Out{name});\n"
));
}
PostCallAction::EnumOutput { name, ffi_type } => {
out.push_str(&format!(
" *Out{name} = static_cast<{ffi_type}>(__Out{name});\n"
));
}
PostCallAction::InOutStructCopyback { name, struct_cpp } => {
out.push_str(&format!(
" if ({name}) {{ FMemory::Memcpy({name}, &__InOut{name}, sizeof({struct_cpp})); }}\n"
));
}
PostCallAction::InOutStringCopyback(name) => {
out.push_str(&format!(
" {{\n\
\x20 auto __Utf8 = StringCast<ANSICHAR>(*__InOut{name});\n\
\x20 int32 __Len = __Utf8.Length();\n\
\x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
\x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
\x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
\x20 }}\n"
));
}
PostCallAction::InOutTextCopyback(name) => {
out.push_str(&format!(
" {{\n\
\x20 FString __Str = __InOut{name}.ToString();\n\
\x20 auto __Utf8 = StringCast<ANSICHAR>(*__Str);\n\
\x20 int32 __Len = __Utf8.Length();\n\
\x20 if (__Len > static_cast<int32>(Out{name}BufLen)) __Len = static_cast<int32>(Out{name}BufLen);\n\
\x20 FMemory::Memcpy(Out{name}, __Utf8.Get(), __Len);\n\
\x20 *Out{name}Len = static_cast<uint32_t>(__Len);\n\
\x20 }}\n"
));
}
}
}
fn is_container_param(param: &ParamInfo) -> bool {
matches!(
param.prop_type.as_str(),
"ArrayProperty" | "MapProperty" | "SetProperty"
)
}
fn resolve_inner_cpp_type(inner: &PropertyInfo, ctx: &CodegenContext) -> String {
match inner.prop_type.as_str() {
"BoolProperty" => "bool".to_string(),
"Int8Property" => "int8".to_string(),
"ByteProperty" => {
if let Some(ref en) = inner.enum_name {
let name = inner.enum_cpp_name.as_deref().unwrap_or(en.as_str());
wrap_enum_as_byte(name, inner.enum_cpp_form)
} else {
"uint8".to_string()
}
}
"Int16Property" => "int16".to_string(),
"UInt16Property" => "uint16".to_string(),
"IntProperty" => "int32".to_string(),
"UInt32Property" => "uint32".to_string(),
"Int64Property" => "int64".to_string(),
"UInt64Property" => "uint64".to_string(),
"FloatProperty" => "float".to_string(),
"DoubleProperty" => "double".to_string(),
"StrProperty" => "FString".to_string(),
"TextProperty" => "FText".to_string(),
"NameProperty" => "FName".to_string(),
"ObjectProperty" => resolve_object_cpp_type(ctx, inner.class_name.as_deref()),
"SoftObjectProperty" => {
let cls_cpp = resolve_object_cpp_type_bare(ctx, inner.class_name.as_deref());
format!("TSoftObjectPtr<{cls_cpp}>")
}
"WeakObjectProperty" => {
let cls_cpp = resolve_object_cpp_type_bare(ctx, inner.class_name.as_deref());
format!("TWeakObjectPtr<{cls_cpp}>")
}
"ClassProperty" => match inner.meta_class_name.as_deref() {
Some(meta) => {
let meta_cpp = resolve_object_cpp_type_bare(ctx, Some(meta));
format!("TSubclassOf<{meta_cpp}>")
}
None => "UClass*".to_string(),
},
"InterfaceProperty" => resolve_object_cpp_type(ctx, inner.interface_name.as_deref()),
"EnumProperty" => {
let name = inner
.enum_cpp_name
.as_deref()
.or(inner.enum_name.as_deref())
.unwrap_or("uint8");
wrap_enum_as_byte(name, inner.enum_cpp_form)
}
"StructProperty" => {
if let Some(ref sn) = inner.struct_name {
if let Some(si) = ctx.structs.get(sn.as_str()) {
si.cpp_name.clone()
} else {
format!("F{sn}")
}
} else {
"uint8".to_string()
}
}
_ => "uint8".to_string(),
}
}
fn resolve_container_cpp_type(param: &ParamInfo, ctx: &CodegenContext) -> Option<String> {
match param.prop_type.as_str() {
"ArrayProperty" => {
let inner = param.inner_prop.as_ref()?;
let elem = resolve_inner_cpp_type(inner, ctx);
Some(format!("TArray<{elem}>"))
}
"MapProperty" => {
let key = param.key_prop.as_ref()?;
let val = param.value_prop.as_ref()?;
let kt = resolve_inner_cpp_type(key, ctx);
let vt = resolve_inner_cpp_type(val, ctx);
Some(format!("TMap<{kt}, {vt}>"))
}
"SetProperty" => {
let elem = param.element_prop.as_ref()?;
let et = resolve_inner_cpp_type(elem, ctx);
Some(format!("TSet<{et}>"))
}
_ => None,
}
}
fn collect_param_headers(param: &ParamInfo, ctx: &CodegenContext, includes: &mut BTreeSet<String>) {
for cls_name in [
param.class_name.as_deref(),
param.meta_class_name.as_deref(),
]
.into_iter()
.flatten()
{
if let Some(cls) = ctx.classes.get(cls_name)
&& !cls.header.is_empty()
{
includes.insert(format!("\"{}\"", cls.header));
}
}
if let Some(sn) = param.struct_name.as_deref() {
include_struct_header(sn, ctx, includes);
}
for inner in [
param.inner_prop.as_deref(),
param.key_prop.as_deref(),
param.value_prop.as_deref(),
param.element_prop.as_deref(),
]
.into_iter()
.flatten()
{
for cls_name in [
inner.class_name.as_deref(),
inner.meta_class_name.as_deref(),
]
.into_iter()
.flatten()
{
if let Some(cls) = ctx.classes.get(cls_name)
&& !cls.header.is_empty()
{
includes.insert(format!("\"{}\"", cls.header));
}
}
if let Some(sn) = inner.struct_name.as_deref() {
include_struct_header(sn, ctx, includes);
}
}
}
fn include_struct_header(struct_name: &str, ctx: &CodegenContext, includes: &mut BTreeSet<String>) {
if let Some(st) = ctx.structs.get(struct_name)
&& !st.header.is_empty()
{
includes.insert(format!("\"{}\"", st.header));
}
}