Skip to content

Commit 161c547

Browse files
committed
feat(UHT): add utf8 and some ansi unreal string support to UHT gen
1 parent 8fa8dd3 commit 161c547

File tree

3 files changed

+164
-20
lines changed

3 files changed

+164
-20
lines changed

UE4SS/include/SDKGenerator/UEHeaderGenerator.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,11 @@ namespace RC::UEGenerator
328328
auto static get_property_access_modifier(FProperty* property) -> AccessModifier;
329329
auto static get_function_access_modifier(UFunction* function) -> AccessModifier;
330330
auto static create_string_literal(const StringType& string) -> StringType;
331+
auto static create_utf8_string_literal(const StringType& string) -> StringType;
332+
auto static create_ansi_string_literal(const StringType& string) -> StringType;
331333
auto static get_header_name_for_object(UObject* object, bool get_existing_header = false) -> StringType;
332334
auto static generate_cross_module_include(UObject* object, const StringType& module_name, const StringType& fallback_name) -> StringType;
335+
private:
336+
auto static create_string_literal_with_macro(const StringType& string, const StringType& macro_name) -> StringType;
333337
};
334338
} // namespace RC::UEGenerator

UE4SS/src/SDKGenerator/Common.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <Unreal/Property/FStructProperty.hpp>
2222
#include <Unreal/Property/FWeakObjectProperty.hpp>
2323
#include <Unreal/Property/NumericPropertyTypes.hpp>
24+
#include <Unreal/CoreUObject/UObject/FUtf8StrProperty.hpp>
2425
#include <Unreal/UClass.hpp>
2526
#include <Unreal/UFunction.hpp>
2627
#include <Unreal/UEnum.hpp>
@@ -484,6 +485,10 @@ namespace RC::UEGenerator
484485
{
485486
return STR("FString");
486487
}
488+
else if (property->IsA<FUtf8StrProperty>())
489+
{
490+
return STR("FUtf8String");
491+
}
487492
else if (property->IsA<FTextProperty>())
488493
{
489494
return STR("FText");
@@ -801,6 +806,10 @@ namespace RC::UEGenerator
801806
{
802807
return STR("FString");
803808
}
809+
else if (field_class_name == STR("Utf8StrProperty"))
810+
{
811+
return STR("FUtf8String");
812+
}
804813
else if (field_class_name == STR("TextProperty"))
805814
{
806815
return STR("FText");

UE4SS/src/SDKGenerator/UEHeaderGenerator.cpp

Lines changed: 151 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
#include <Unreal/Property/FNameProperty.hpp>
3434
#include <Unreal/Property/FObjectProperty.hpp>
3535
#include <Unreal/Property/FSetProperty.hpp>
36-
#include <Unreal/Property/FStrProperty.hpp>
36+
#include <Unreal/CoreUObject/UObject/FStrProperty.hpp>
37+
#include <Unreal/CoreUObject/UObject/FUtf8StrProperty.hpp>
38+
#include <Unreal/CoreUObject/UObject/FAnsiStrProperty.hpp>
3739
#include <Unreal/Property/FSoftClassProperty.hpp>
3840
#include <Unreal/Property/FSoftObjectProperty.hpp>
3941
#include <Unreal/Property/FStructProperty.hpp>
@@ -1364,6 +1366,78 @@ namespace RC::UEGenerator
13641366
return;
13651367
}
13661368

1369+
// UTF8 String properties
1370+
if (property->IsA<FUtf8StrProperty>())
1371+
{
1372+
FUtf8String* string_value = property->ContainerPtrToValuePtr<FUtf8String>(object);
1373+
// Convert UTF8 to native string type for comparison and generation
1374+
const char* utf8_cstr = reinterpret_cast<const char*>(**string_value);
1375+
const StringType string_value_string = to_generic_string(utf8_cstr);
1376+
1377+
// Ensure property either does not exist in parent class or is overriden in the CDO for the child class
1378+
if (super_property != nullptr)
1379+
{
1380+
FUtf8String* super_string_value = super_property->ContainerPtrToValuePtr<FUtf8String>(super_object);
1381+
const char* super_utf8_cstr = reinterpret_cast<const char*>(**super_string_value);
1382+
const StringType super_string_value_string = to_generic_string(super_utf8_cstr);
1383+
if (string_value_string == super_string_value_string)
1384+
{
1385+
return;
1386+
}
1387+
super_and_no_access = private_access_modifier;
1388+
}
1389+
1390+
if (string_value_string != STR(""))
1391+
{
1392+
// Create UTF8TEXT literal for UTF8 strings
1393+
const StringType result_value = create_utf8_string_literal(string_value_string);
1394+
if (!super_and_no_access)
1395+
{
1396+
generate_simple_assignment_expression(property, result_value, implementation_file, property_scope);
1397+
}
1398+
else
1399+
{
1400+
generate_advanced_assignment_expression(property, result_value, implementation_file, property_scope, property_type);
1401+
}
1402+
}
1403+
return;
1404+
}
1405+
1406+
// ANSI String properties
1407+
if (property->IsA<FAnsiStrProperty>())
1408+
{
1409+
FAnsiString* string_value = property->ContainerPtrToValuePtr<FAnsiString>(object);
1410+
// Convert ANSI to native string type for comparison and generation
1411+
const StringType string_value_string = to_generic_string(**string_value);
1412+
1413+
// Ensure property either does not exist in parent class or is overriden in the CDO for the child class
1414+
if (super_property != nullptr)
1415+
{
1416+
FAnsiString* super_string_value = super_property->ContainerPtrToValuePtr<FAnsiString>(super_object);
1417+
const StringType super_string_value_string = to_generic_string(**super_string_value);
1418+
if (string_value_string == super_string_value_string)
1419+
{
1420+
return;
1421+
}
1422+
super_and_no_access = private_access_modifier;
1423+
}
1424+
1425+
if (string_value_string != STR(""))
1426+
{
1427+
// ANSI strings use plain string literals (no TEXT macro)
1428+
const StringType result_value = create_ansi_string_literal(string_value_string);
1429+
if (!super_and_no_access)
1430+
{
1431+
generate_simple_assignment_expression(property, result_value, implementation_file, property_scope);
1432+
}
1433+
else
1434+
{
1435+
generate_advanced_assignment_expression(property, result_value, implementation_file, property_scope, property_type);
1436+
}
1437+
}
1438+
return;
1439+
}
1440+
13671441
// Text properties are treated as FText::FromString, although it's not ideal
13681442
if (property->IsA<FTextProperty>())
13691443
{
@@ -1958,57 +2032,94 @@ namespace RC::UEGenerator
19582032
}
19592033

19602034
auto UEHeaderGenerator::create_string_literal(const StringType& string) -> StringType
2035+
{
2036+
return create_string_literal_with_macro(string, STR("TEXT"));
2037+
}
2038+
2039+
auto UEHeaderGenerator::create_utf8_string_literal(const StringType& string) -> StringType
2040+
{
2041+
return create_string_literal_with_macro(string, STR("UTF8TEXT"));
2042+
}
2043+
2044+
auto UEHeaderGenerator::create_ansi_string_literal(const StringType& string) -> StringType
2045+
{
2046+
// ANSI strings don't use a macro, just quotes
2047+
return create_string_literal_with_macro(string, STR(""));
2048+
}
2049+
2050+
auto UEHeaderGenerator::create_string_literal_with_macro(const StringType& string, const StringType& macro_name) -> StringType
19612051
{
19622052
StringType result;
1963-
result.append(STR("TEXT(\""));
2053+
2054+
// Open the literal
2055+
if (!macro_name.empty())
2056+
{
2057+
result.append(macro_name);
2058+
result.append(STR("(\""));
2059+
}
2060+
else
2061+
{
2062+
result.append(STR("\""));
2063+
}
19642064

19652065
bool previous_character_was_hex = false;
1966-
19672066
const CharType* ptr = string.c_str();
2067+
19682068
while (CharType ch = *ptr++)
19692069
{
19702070
switch (ch)
19712071
{
1972-
case STR('\r'): {
2072+
case STR('\r'):
19732073
continue;
1974-
}
1975-
case STR('\n'): {
2074+
case STR('\n'):
19762075
result.append(STR("\\n"));
19772076
previous_character_was_hex = false;
19782077
break;
1979-
}
1980-
case STR('\\'): {
2078+
case STR('\t'):
2079+
result.append(STR("\\t"));
2080+
previous_character_was_hex = false;
2081+
break;
2082+
case STR('\\'):
19812083
result.append(STR("\\\\"));
19822084
previous_character_was_hex = false;
19832085
break;
1984-
}
1985-
case STR('\"'): {
2086+
case STR('\"'):
19862087
result.append(STR("\\\""));
19872088
previous_character_was_hex = false;
19882089
break;
1989-
}
1990-
default: {
1991-
if (ch < 31 || ch >= 128)
2090+
default:
2091+
// Only escape control characters (< 32)
2092+
// For ANSI (no macro), also escape > 127
2093+
if (ch < 32 || (macro_name.empty() && ch > 127))
19922094
{
1993-
result.append(fmt::format(STR("\\x{:04X}"), ch));
2095+
result.append(fmt::format(STR("\\x{:02X}"), static_cast<unsigned char>(ch)));
19942096
previous_character_was_hex = true;
19952097
}
19962098
else
19972099
{
1998-
// We close and open the literal (with TEXT) here in order to ensure that successive hex characters aren't
1999-
// appended to the hex sequence, causing a different number
2100+
// Handle hex digit continuation
20002101
if (previous_character_was_hex && iswxdigit(ch) != 0)
20012102
{
2002-
result.append(STR("\")TEXT(\""));
2103+
if (!macro_name.empty())
2104+
{
2105+
result.append(STR("\")"));
2106+
result.append(macro_name);
2107+
result.append(STR("(\""));
2108+
}
2109+
else
2110+
{
2111+
result.append(STR("\"\"")); // String concatenation for ANSI
2112+
}
20032113
}
20042114
previous_character_was_hex = false;
20052115
result.push_back(ch);
20062116
}
20072117
break;
20082118
}
2009-
}
20102119
}
2011-
result.append(STR("\")"));
2120+
2121+
// Close the literal
2122+
result.append(!macro_name.empty() ? STR("\")") : STR("\""));
20122123
return result;
20132124
}
20142125

@@ -2701,6 +2812,14 @@ namespace RC::UEGenerator
27012812
{
27022813
return STR("FString");
27032814
}
2815+
else if (property->IsA<FUtf8StrProperty>())
2816+
{
2817+
return STR("FUtf8String");
2818+
}
2819+
else if (property->IsA<FAnsiStrProperty>())
2820+
{
2821+
return STR("FAnsiString");
2822+
}
27042823
else if (property->IsA<FTextProperty>())
27052824
{
27062825
return STR("FText");
@@ -3221,8 +3340,12 @@ namespace RC::UEGenerator
32213340

32223341
// Force const reference when we're dealing with strings, and they are not passed by reference
32233342
// UHT for whatever reason completely strips const and reference flags from string properties, but happily generates them in boilerplate code
3343+
const StringType property_class_name = property->GetClass().GetName();
32243344
const bool should_force_const_ref =
3225-
((property_flags & (CPF_ReferenceParm | CPF_OutParm)) == 0) && (property->GetClass().GetName() == STR("StrProperty"));
3345+
((property_flags & (CPF_ReferenceParm | CPF_OutParm)) == 0) &&
3346+
(property_class_name == STR("StrProperty") ||
3347+
property_class_name == STR("Utf8StrProperty") ||
3348+
property_class_name == STR("AnsiStrProperty"));
32263349

32273350
// Append const keyword to the parameter declaration if it is marked as const parameter
32283351
if ((property_flags & CPF_ConstParm) != 0 || should_force_const_ref)
@@ -3417,6 +3540,14 @@ namespace RC::UEGenerator
34173540
{
34183541
return STR("TEXT(\"\")");
34193542
}
3543+
if (field_class_name == STR("Utf8StrProperty"))
3544+
{
3545+
return STR("UTF8TEXT(\"\")");
3546+
}
3547+
if (field_class_name == STR("AnsiStrProperty"))
3548+
{
3549+
return STR("\"\"");
3550+
}
34203551
if (field_class_name == STR("TextProperty"))
34213552
{
34223553
return STR("FText::GetEmpty()");

0 commit comments

Comments
 (0)