From f66ebb17eda815e7eb65f294dda186908f768b71 Mon Sep 17 00:00:00 2001 From: OutTheShade Date: Sun, 11 Dec 2022 11:56:46 -0500 Subject: [PATCH] Runtime version detection and complete engine types refactor. --- README.md | 2 +- .../UnrealMappingsDumper.vcxproj | 10 +- .../UnrealMappingsDumper.vcxproj.filters | 18 +- UnrealMappingsDumper/app.cpp | 78 ++- UnrealMappingsDumper/app.h | 18 +- UnrealMappingsDumper/dllmain.cpp | 22 +- UnrealMappingsDumper/dumper.cpp | 347 ++++++++----- UnrealMappingsDumper/dumper.h | 36 +- UnrealMappingsDumper/engine.cpp | 215 -------- UnrealMappingsDumper/engine.h | 486 ------------------ UnrealMappingsDumper/framework.h | 2 + UnrealMappingsDumper/unrealTypes.h | 475 +++++++++++++++++ UnrealMappingsDumper/unrealVersion.cpp | 60 +++ UnrealMappingsDumper/unrealVersion.h | 150 ++++++ UnrealMappingsDumper/uobjectDependency.h | 72 --- UnrealMappingsDumper/writer.h | 22 +- 16 files changed, 1012 insertions(+), 1001 deletions(-) delete mode 100644 UnrealMappingsDumper/engine.cpp delete mode 100644 UnrealMappingsDumper/engine.h create mode 100644 UnrealMappingsDumper/unrealTypes.h create mode 100644 UnrealMappingsDumper/unrealVersion.cpp create mode 100644 UnrealMappingsDumper/unrealVersion.h delete mode 100644 UnrealMappingsDumper/uobjectDependency.h diff --git a/README.md b/README.md index 71f1cad..3690764 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The source code relies a lot on the power of templates in order to make overridi ## Plans -> Deducing engine version at runtime. +> GPackageFileUEVersion support. > More compression methods. diff --git a/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj b/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj index 53cd36b..991c3b1 100644 --- a/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj +++ b/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj @@ -1,4 +1,4 @@ - + @@ -120,6 +120,7 @@ pch.h stdcpplatest 4244;4267; + Async Windows @@ -139,6 +140,7 @@ pch.h stdcpplatest 4244;4267; + Async Windows @@ -151,7 +153,6 @@ - @@ -159,14 +160,14 @@ - + + - Create Create @@ -174,6 +175,7 @@ Create + diff --git a/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj.filters b/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj.filters index a5a4701..38243de 100644 --- a/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj.filters +++ b/UnrealMappingsDumper/UnrealMappingsDumper.vcxproj.filters @@ -48,12 +48,6 @@ Core\Dumper - - Core\Engine - - - Core\Engine - Core\Engine @@ -72,6 +66,12 @@ Core\Compression + + Core\Engine + + + Core\Engine + @@ -83,14 +83,14 @@ Core\App - - Core\Engine - Core\Scanning Core\Dumper + + Core\Engine + \ No newline at end of file diff --git a/UnrealMappingsDumper/app.cpp b/UnrealMappingsDumper/app.cpp index 178efdd..88aefb2 100644 --- a/UnrealMappingsDumper/app.cpp +++ b/UnrealMappingsDumper/app.cpp @@ -1,20 +1,74 @@ #include "pch.h" #include "app.h" +#include "unrealVersion.h" -IDumper* CreateAppInstance(EUnrealVersion Version) +struct GameInstance { - switch (Version) + std::filesystem::path GamePath; + float Version = 0; +}; + +static std::optional TryGetGameInfo() +{ + wchar_t GameFilePath[MAX_PATH]; + GetModuleFileName(0, GameFilePath, MAX_PATH); + + DWORD verHandle = 0; + DWORD verSize = GetFileVersionInfoSize(GameFilePath, &verHandle); + + if (!verSize) + return std::nullopt; + + std::string VerData(verSize, '\0'); + uint32_t size = 0; + VS_FIXEDFILEINFO* VersionInfo = nullptr; + + if (!GetFileVersionInfo(GameFilePath, verHandle, verSize, VerData.data()) || + !VerQueryValue(VerData.data(), L"\\", (VOID FAR * FAR*) & VersionInfo, &size) || + !size || + VersionInfo->dwSignature != 0xfeef04bd) { - case EUnrealVersion::UE5: - case EUnrealVersion::UE5_01: - return new Dumper(); - break; - case EUnrealVersion::FORTNITE: - return new Dumper(); - break; - default: - return new Dumper>(); - break; + return std::nullopt; } + + auto VersionStr = std::format("{:d}.{:d}.{:d}.{:d}", + (VersionInfo->dwFileVersionMS >> 16) & 0xffff, + (VersionInfo->dwFileVersionMS >> 0) & 0xffff, + (VersionInfo->dwFileVersionLS >> 16) & 0xffff, + (VersionInfo->dwFileVersionLS >> 0) & 0xffff); + + GameInstance Ret; + Ret.GamePath = std::filesystem::path(GameFilePath); + Ret.Version = std::stof(VersionStr); + + return Ret; +} + +static bool InitEngine(GameInstance& Game) +{ + // if an engine version has a different type or offset, set it here + + if (Game.GamePath.filename() == "FortniteClient-Win64-Shipping.exe" + and Game.Version >= 5.0) + { + return IUnrealVersion::InitTypes(); + } + + return IUnrealVersion::InitTypes(); +} + +bool App::Init() +{ + auto GameOpt = TryGetGameInfo(); + + if (!GameOpt.has_value()) + return false; + + auto& Game = GameOpt.value(); + + UE_LOG("Detected Unreal Engine version %f", Game.Version); + UE_LOG("Detected game %s", Game.GamePath.filename().string().c_str()); + + return InitEngine(Game); } \ No newline at end of file diff --git a/UnrealMappingsDumper/app.h b/UnrealMappingsDumper/app.h index 388dfad..9e92db2 100644 --- a/UnrealMappingsDumper/app.h +++ b/UnrealMappingsDumper/app.h @@ -1,18 +1,5 @@ #pragma once -#include "dumper.h" - -enum class EUnrealVersion -{ - UE4_26, - UE4_27, - UE5, - UE5_01, - FORTNITE -}; - -IDumper* CreateAppInstance(EUnrealVersion Version); - static void UE_LOG(const char* str, ...) { va_list fmt; @@ -23,4 +10,9 @@ static void UE_LOG(const char* str, ...) printf("\n"); va_end(fmt); +} + +namespace App +{ + bool Init(); } \ No newline at end of file diff --git a/UnrealMappingsDumper/dllmain.cpp b/UnrealMappingsDumper/dllmain.cpp index f7dcaf2..3dbab94 100644 --- a/UnrealMappingsDumper/dllmain.cpp +++ b/UnrealMappingsDumper/dllmain.cpp @@ -1,10 +1,7 @@ #include "pch.h" #include "app.h" - -#define CLEANUP() \ - delete App; \ - FreeLibraryAndExitThread(Module, NULL); \ +#include "dumper.h" constexpr bool OpenConsole = true; @@ -19,31 +16,22 @@ void WINAPI Main(HMODULE Module) UE_LOG("Unreal Mappings Dumper created by OutTheShade"); - auto App = CreateAppInstance(EUnrealVersion::UE5_01); // TODO: a way to determine the engine version at runtime - - if (!App) - { - UE_LOG("Couldn't instantiate dumper instance. Returning."); - CLEANUP(); - return; - } - - if (!App->Init()) + if (!App::Init()) { UE_LOG("Failed to initialize the dumper. Returning."); - CLEANUP(); + FreeLibraryAndExitThread(Module, NULL); return; } auto Start = std::chrono::steady_clock::now(); - App->Run(ECompressionMethod::None); + Dumper::Run(ECompressionMethod::None); auto End = std::chrono::steady_clock::now(); UE_LOG("Successfully generated mappings file in %.02f ms", (End - Start).count() / 1000000.); - CLEANUP(); + FreeLibraryAndExitThread(Module, NULL); } BOOL APIENTRY DllMain( diff --git a/UnrealMappingsDumper/dumper.cpp b/UnrealMappingsDumper/dumper.cpp index ab8f52c..2cbd3c0 100644 --- a/UnrealMappingsDumper/dumper.cpp +++ b/UnrealMappingsDumper/dumper.cpp @@ -1,74 +1,165 @@ #include "pch.h" #include "dumper.h" -#include "unrealFunctions.h" -#include "app.h" #include "writer.h" #include "oodle.h" -template -bool Dumper::Init(uintptr_t GObjectsOverride, uintptr_t FNameToStringOverride) +static EPropertyType GetPropertyType(FProperty* Prop) { - if (GObjectsOverride) - Engine::ObjObjects::SetInstance(GObjectsOverride); - - if (FNameToStringOverride) - FNameToString = (_FNameToString)FNameToStringOverride; - - if (GObjectsOverride && FNameToStringOverride) - return true; - - uintptr_t GObjectsAddy = 0; - - for (auto Scan : Engine::GetGObjectsPatterns()) + switch (Prop->GetClass()->GetId()) { - GObjectsAddy = Scan->TryFind(); - - if (GObjectsAddy) - break; + case CASTCLASS_FObjectProperty: + case CASTCLASS_FClassProperty: + case CASTCLASS_FObjectPtrProperty: + case CASTCLASS_FClassPtrProperty: + { + return EPropertyType::ObjectProperty; } - - if (!GObjectsAddy) + case CASTCLASS_FStructProperty: { - UE_LOG("Could not find the address for GObjects. Try overriding it or adding the correct sig for it."); - return false; + return EPropertyType::StructProperty; } - - Engine::ObjObjects::SetInstance(GObjectsAddy); - - uintptr_t FNameStringAddy = 0; - - for (auto Scan : Engine::GetFNameStringPattrns()) + case CASTCLASS_FInt8Property: { - FNameStringAddy = Scan->TryFind(); - - if (FNameStringAddy) - break; + return EPropertyType::Int8Property; } - - if (!FNameStringAddy) + case CASTCLASS_FInt16Property: { - UE_LOG("Could not find the address for FNameToString. Try overriding it or adding the correct sig for it."); - return false; + return EPropertyType::Int16Property; } + case CASTCLASS_FIntProperty: + { + return EPropertyType::IntProperty; + } + case CASTCLASS_FInt64Property: + { + return EPropertyType::Int16Property; + } + case CASTCLASS_FUInt16Property: + { + return EPropertyType::UInt16Property; + } + case CASTCLASS_FUInt32Property: + { + return EPropertyType::UInt32Property; + } + case CASTCLASS_FUInt64Property: + { + return EPropertyType::UInt64Property; + } + case CASTCLASS_FArrayProperty: + { + return EPropertyType::ArrayProperty; + } + case CASTCLASS_FFloatProperty: + { + return EPropertyType::FloatProperty; + } + case CASTCLASS_FDoubleProperty: + { + return EPropertyType::DoubleProperty; + } + case CASTCLASS_FBoolProperty: + { + return EPropertyType::BoolProperty; + } + case CASTCLASS_FStrProperty: + { + return EPropertyType::StrProperty; + } + case CASTCLASS_FNameProperty: + { + return EPropertyType::NameProperty; + } + case CASTCLASS_FTextProperty: + { + return EPropertyType::TextProperty; + } + case CASTCLASS_FEnumProperty: + { + return EPropertyType::EnumProperty; + } + case CASTCLASS_FInterfaceProperty: + { + return EPropertyType::InterfaceProperty; + } + case CASTCLASS_FMapProperty: + { + return EPropertyType::MapProperty; + } + case CASTCLASS_FByteProperty: + { + FByteProperty* ByteProp = static_cast(Prop); - FNameToString = (_FNameToString)FNameStringAddy; + if (ByteProp->GetEnum()) + return EPropertyType::EnumAsByteProperty; - return true; + return EPropertyType::ByteProperty; + } + case CASTCLASS_FMulticastDelegateProperty: + case CASTCLASS_FMulticastInlineDelegateProperty: + case CASTCLASS_FMulticastSparseDelegateProperty: + { + return EPropertyType::MulticastDelegateProperty; + } + case CASTCLASS_FDelegateProperty: + { + return EPropertyType::DelegateProperty; + } + case CASTCLASS_FSoftObjectProperty: + case CASTCLASS_FSoftClassProperty: + case CASTCLASS_FWeakObjectProperty: + { + return EPropertyType::SoftObjectProperty; + } + case CASTCLASS_FLazyObjectProperty: + { + return EPropertyType::LazyObjectProperty; + } + case CASTCLASS_FSetProperty: + { + return EPropertyType::SetProperty; + } + case CASTCLASS_FFieldPathProperty: + { + return EPropertyType::FieldPathProperty; + } + default: + { + return EPropertyType::Unknown; + } + } } -template -void Dumper::Run(ECompressionMethod CompressionMethod) const +struct FPropertyData +{ + FProperty* Prop; + uint16_t Index; + uint8_t ArrayDim; + FName Name; + EPropertyType PropertyType; + + FPropertyData(FProperty* P, int Idx) : + Prop(P), + Index(Idx), + ArrayDim(P->GetArrayDim()), + Name(P->GetFName()), + PropertyType(GetPropertyType(P)) + { + } +}; + +void Dumper::Run(ECompressionMethod CompressionMethod) { StreamWriter Buffer; - phmap::parallel_flat_hash_map NameMap; + phmap::parallel_flat_hash_map NameMap; - std::vector Enums; - std::vector Structs; // TODO: a better way than making this completely dynamic + std::vector Enums; + std::vector Structs; // TODO: a better way than making this completely dynamic - std::function WritePropertyWrapper{}; // hacky.. i know + std::function WritePropertyWrapper{}; // hacky.. i know - auto WriteProperty = [&](Engine::FProperty*& Prop, EPropertyType Type) + auto WriteProperty = [&](FProperty*& Prop, EPropertyType Type) { if (Type == EPropertyType::EnumAsByteProperty) Buffer.Write(EPropertyType::EnumProperty); @@ -76,87 +167,89 @@ void Dumper::Run(ECompressionMethod CompressionMethod) const switch (Type) { - case EPropertyType::EnumProperty: - { - auto EnumProp = static_cast(Prop); + case EPropertyType::EnumProperty: + { + auto EnumProp = static_cast(Prop); - auto Inner = EnumProp->GetUnderlying(); - auto InnerType = Inner->GetPropertyType(); - WritePropertyWrapper(Inner, InnerType); - Buffer.Write(NameMap[EnumProp->GetEnum()->GetName()]); + auto Inner = EnumProp->GetUnderlying(); + auto InnerType = GetPropertyType(Inner); + WritePropertyWrapper(Inner, InnerType); + Buffer.Write(NameMap[EnumProp->GetEnum()->GetFName()]); - break; - } - case EPropertyType::EnumAsByteProperty: - { - Buffer.Write(EPropertyType::ByteProperty); - Buffer.Write(NameMap[static_cast(Prop)->GetEnum()->GetName()]); + break; + } + case EPropertyType::EnumAsByteProperty: + { + Buffer.Write(EPropertyType::ByteProperty); + Buffer.Write(NameMap[static_cast(Prop)->GetEnum()->GetFName()]); - break; - } - case EPropertyType::StructProperty: - { - Buffer.Write(NameMap[static_cast(Prop)->GetStruct()->GetName()]); - break; - } - case EPropertyType::SetProperty: - case EPropertyType::ArrayProperty: - { - auto Inner = static_cast(Prop)->GetInner(); - auto InnerType = Inner->GetPropertyType(); - WritePropertyWrapper(Inner, InnerType); + break; + } + case EPropertyType::StructProperty: + { + Buffer.Write(NameMap[static_cast(Prop)->GetStruct()->GetFName()]); + break; + } + case EPropertyType::SetProperty: + case EPropertyType::ArrayProperty: + { + auto Inner = static_cast(Prop)->GetInner(); + auto InnerType = GetPropertyType(Inner); + WritePropertyWrapper(Inner, InnerType); - break; - } - case EPropertyType::MapProperty: - { - auto Inner = static_cast(Prop)->GetKey(); - auto InnerType = Inner->GetPropertyType(); - WritePropertyWrapper(Inner, InnerType); + break; + } + case EPropertyType::MapProperty: + { + auto Inner = static_cast(Prop)->GetKey(); + auto InnerType = GetPropertyType(Inner); + WritePropertyWrapper(Inner, InnerType); - auto Value = static_cast(Prop)->GetValue(); - auto ValueType = Value->GetPropertyType(); - WritePropertyWrapper(Value, ValueType); + auto Value = static_cast(Prop)->GetValue(); + auto ValueType = GetPropertyType(Value); + WritePropertyWrapper(Value, ValueType); - break; - } + break; + } } }; WritePropertyWrapper = WriteProperty; - Engine::ObjObjects::ForEach([&](Engine::UObject*& Object) + ObjObjects::ForEach([&](UObject*& Object) { - if (Object->Class() == Engine::UClass::StaticClass() || - Object->Class() == Engine::UScriptStruct::StaticClass()) + if (Object->Class() == UClass::StaticClass() || + Object->Class() == UScriptStruct::StaticClass()) { - auto Struct = static_cast(Object); + auto Struct = static_cast(Object); Structs.push_back(Struct); - - NameMap.insert_or_assign(Struct->GetName(), 0); - if (Struct->Super() && !NameMap.contains(Struct->Super()->GetName())) - NameMap.insert_or_assign(Struct->Super()->GetName(), 0); + NameMap.insert_or_assign(Struct->GetFName(), 0); + + if (Struct->Super() && !NameMap.contains(Struct->Super()->GetFName())) + NameMap.insert_or_assign(Struct->Super()->GetFName(), 0); - auto Props = Struct->GetProperties(); + auto Props = Struct->ChildProperties(); while (Props) { - NameMap.insert_or_assign(Props->GetName(), 0); - Props = static_cast(Props->GetNext()); + NameMap.insert_or_assign(Props->GetFName(), 0); + Props = static_cast(Props->GetNext()); } } - else if (Object->Class() == Engine::UEnum::StaticClass()) + else if (Object->Class() == UEnum::StaticClass()) { - auto Enum = static_cast(Object); + auto Enum = static_cast(Object); Enums.push_back(Enum); - - NameMap.insert_or_assign(Enum->GetName(), 0); - for (auto i = 0; i < Enum->Names.Num(); i++) + NameMap.insert_or_assign(Enum->GetFName(), 0); + + auto& EnumNames = Enum->Names(); + + for (auto i = 0; i < EnumNames.Num(); i++) { - NameMap.insert_or_assign(Enum->Names[i].Key, 0); + NameMap.insert_or_assign(EnumNames[i].Key, 0); } } }); @@ -188,12 +281,14 @@ void Dumper::Run(ECompressionMethod CompressionMethod) const for (auto Enum : Enums) { - Buffer.Write(NameMap[Enum->GetName()]); - Buffer.Write(Enum->Names.Num()); + auto& EnumNames = Enum->Names(); + + Buffer.Write(NameMap[Enum->GetFName()]); + Buffer.Write(EnumNames.Num()); - for (size_t i = 0; i < Enum->Names.Num(); i++) + for (size_t i = 0; i < EnumNames.Num(); i++) { - Buffer.Write(NameMap[Enum->Names[i].Key]); + Buffer.Write(NameMap[EnumNames[i].Key]); } } @@ -201,12 +296,12 @@ void Dumper::Run(ECompressionMethod CompressionMethod) const for (auto Struct : Structs) { - Buffer.Write(NameMap[Struct->GetName()]); - Buffer.Write(Struct->Super() ? NameMap[Struct->Super()->GetName()] : 0xffffffff); + Buffer.Write(NameMap[Struct->GetFName()]); + Buffer.Write(Struct->Super() ? NameMap[Struct->Super()->GetFName()] : 0xffffffff); std::vector Properties; - auto Props = Struct->GetProperties(); + auto Props = Struct->ChildProperties(); uint16_t PropCount = 0; uint16_t SerializablePropCount = 0; @@ -215,7 +310,7 @@ void Dumper::Run(ECompressionMethod CompressionMethod) const FPropertyData Data(Props, PropCount); Properties.push_back(Data); - Props = static_cast(Props->GetNext()); + Props = static_cast(Props->GetNext()); PropCount += Data.ArrayDim; SerializablePropCount++; @@ -230,25 +325,25 @@ void Dumper::Run(ECompressionMethod CompressionMethod) const Buffer.Write(P.ArrayDim); Buffer.Write(NameMap[P.Name]); - WriteProperty(P.Prop, P.Type); + WriteProperty(P.Prop, P.PropertyType); } } std::vector UsmapData; - switch (CompressionMethod) + switch (CompressionMethod) { - case ECompressionMethod::Oodle: - { - UsmapData = Oodle::Compress(Buffer.GetBuffer()); - break; - } - default: - { - std::string UncompressedStream = Buffer.GetBuffer().str(); - UsmapData.resize(UncompressedStream.size()); - memcpy(UsmapData.data(), UncompressedStream.data(), UsmapData.size()); - } + case ECompressionMethod::Oodle: + { + UsmapData = Oodle::Compress(Buffer.GetBuffer()); + break; + } + default: + { + std::string UncompressedStream = Buffer.GetBuffer().str(); + UsmapData.resize(UncompressedStream.size()); + memcpy(UsmapData.data(), UncompressedStream.data(), UsmapData.size()); + } } auto FileOutput = FileWriter("Mappings.usmap"); @@ -260,8 +355,4 @@ void Dumper::Run(ECompressionMethod CompressionMethod) const FileOutput.Write(Buffer.Size()); //decompressed size FileOutput.Write(UsmapData.data(), UsmapData.size()); -} - -template class Dumper>; -template class Dumper; -template class Dumper; \ No newline at end of file +} \ No newline at end of file diff --git a/UnrealMappingsDumper/dumper.h b/UnrealMappingsDumper/dumper.h index ba10ad2..46b4852 100644 --- a/UnrealMappingsDumper/dumper.h +++ b/UnrealMappingsDumper/dumper.h @@ -1,38 +1,8 @@ #pragma once -#include "engine.h" +#include "unrealTypes.h" -class IDumper +namespace Dumper { -public: - - virtual bool Init(uintptr_t GObjectsOverride = 0, uintptr_t FNameToStringOverride = 0) = 0; - virtual void Run(ECompressionMethod CompressionMethod = ECompressionMethod::None) const = 0; -}; - -template -class Dumper : public IDumper -{ -public: - - struct FPropertyData - { - Engine::FProperty* Prop; - uint16_t Index; - uint8_t ArrayDim; - Engine::FName Name; - EPropertyType Type; - - FPropertyData(Engine::FProperty* P, int Idx) : - Prop(P), - Index(Idx), - ArrayDim(P->GetArrayDim()), - Name(P->GetName()), - Type(P->GetPropertyType()) - { - } - }; - - bool Init(uintptr_t GObjectsOverride, uintptr_t FNameToStringOverride) override; - void Run(ECompressionMethod CompressionMethod) const override; + void Run(ECompressionMethod CompressionMethod); }; \ No newline at end of file diff --git a/UnrealMappingsDumper/engine.cpp b/UnrealMappingsDumper/engine.cpp deleted file mode 100644 index 148daaa..0000000 --- a/UnrealMappingsDumper/engine.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "pch.h" - -#include "engine.h" -#include "unrealFunctions.h" - -std::wstring_view FNameBase::AsString() const -{ - FString Ret; - FNameToString(this, Ret); - - if (Ret.Data() != nullptr) - { - return std::wstring_view(Ret.Data()); - } - - return {}; -} - -std::string FNameBase::ToString() const -{ - auto Ret = AsString(); - - return std::string(Ret.begin(), Ret.end()); -} - -template -void DefaultEngine::UObject::GetPathName(std::wstring& Result, UObject* StopOuter) -{ - if (this == StopOuter || this == NULL) - { - Result += L"None"; - return; - } - - if (this->OuterPrivate && this->OuterPrivate != StopOuter) - { - this->OuterPrivate->GetPathName(Result, StopOuter); - Result += L"."; - } - - Result += NamePrivate.AsString(); -} - -// inspired by https://github.com/kem0x/FortKit/blob/b22bb917ea254ac789333922c709d112c8989184/FortKitInsider/ue4.cpp#L176 -template -EPropertyType DefaultEngine::FProperty::GetPropertyType() -{ - switch (this->GetClass()->GetId()) - { - case CASTCLASS_FObjectProperty: - case CASTCLASS_FClassProperty: - case CASTCLASS_FObjectPtrProperty: - case CASTCLASS_FClassPtrProperty: - { - return EPropertyType::ObjectProperty; - } - case CASTCLASS_FStructProperty: - { - return EPropertyType::StructProperty; - } - case CASTCLASS_FInt8Property: - { - return EPropertyType::Int8Property; - } - case CASTCLASS_FInt16Property: - { - return EPropertyType::Int16Property; - } - case CASTCLASS_FIntProperty: - { - return EPropertyType::IntProperty; - } - case CASTCLASS_FInt64Property: - { - return EPropertyType::Int16Property; - } - case CASTCLASS_FUInt16Property: - { - return EPropertyType::UInt16Property; - } - case CASTCLASS_FUInt32Property: - { - return EPropertyType::UInt32Property; - } - case CASTCLASS_FUInt64Property: - { - return EPropertyType::UInt64Property; - } - case CASTCLASS_FArrayProperty: - { - return EPropertyType::ArrayProperty; - } - case CASTCLASS_FFloatProperty: - { - return EPropertyType::FloatProperty; - } - case CASTCLASS_FDoubleProperty: - { - return EPropertyType::DoubleProperty; - } - case CASTCLASS_FBoolProperty: - { - return EPropertyType::BoolProperty; - } - case CASTCLASS_FStrProperty: - { - return EPropertyType::StrProperty; - } - case CASTCLASS_FNameProperty: - { - return EPropertyType::NameProperty; - } - case CASTCLASS_FTextProperty: - { - return EPropertyType::TextProperty; - } - case CASTCLASS_FEnumProperty: - { - return EPropertyType::EnumProperty; - } - case CASTCLASS_FInterfaceProperty: - { - return EPropertyType::InterfaceProperty; - } - case CASTCLASS_FMapProperty: - { - return EPropertyType::MapProperty; - } - case CASTCLASS_FByteProperty: - { - FByteProperty* ByteProp = static_cast(this); - - if (ByteProp->GetEnum()) - return EPropertyType::EnumAsByteProperty; - - return EPropertyType::ByteProperty; - } - case CASTCLASS_FMulticastDelegateProperty: - case CASTCLASS_FMulticastInlineDelegateProperty: - case CASTCLASS_FMulticastSparseDelegateProperty: - { - return EPropertyType::MulticastDelegateProperty; - } - case CASTCLASS_FDelegateProperty: - { - return EPropertyType::DelegateProperty; - } - case CASTCLASS_FSoftObjectProperty: - case CASTCLASS_FSoftClassProperty: - case CASTCLASS_FWeakObjectProperty: - { - return EPropertyType::SoftObjectProperty; - } - case CASTCLASS_FLazyObjectProperty: - { - return EPropertyType::LazyObjectProperty; - } - case CASTCLASS_FSetProperty: - { - return EPropertyType::SetProperty; - } - case CASTCLASS_FFieldPathProperty: - { - return EPropertyType::FieldPathProperty; - } - default: - { - return EPropertyType::Unknown; - } - } -} - -template -void DefaultEngine::ObjObjects::ForEach(std::function Action) -{ - for (int i = 0; i < Num(); i++) - { - auto Obj = GetObjectByIndex(i); - - if (!Obj) continue; - - Action(Obj); - } -} - -template -std::vector> DefaultEngine::GetFNameStringPattrns() // TODO: add more -{ - return - { - std::make_shared("E8 ? ? ? ? 83 7D C8 00 48 8D 15 ? ? ? ? 0F 5A DE", 1, true), - std::make_shared>( - L"%s %s SetTimer passed a negative or zero time. The associated timer may fail to be created/fire! If using InitialStartDelayVariance, be sure it is smaller than (Time + InitialStartDelay).", - true, 1, true, 0xE8) - }; -} - -template -std::vector> DefaultEngine::GetGObjectsPatterns() // TODO: add more -{ - return - { - std::make_shared("48 89 05 ? ? ? ? E8 ? ? ? ? ? ? ? 0F 84", 3, true), - std::make_shared("48 8B 05 ? ? ? ? 48 8B 0C 07 48 85 C9 74 20", 3, true), - std::make_shared("48 8B 05 ? ? ? ? 48 8B 0C", 3, true), - std::make_shared("48 03 ? ? ? ? ? ? ? ? ? ? 48 8B 10 48 85 D2 74 07", 3, true) - - }; -} - -template class DefaultEngine; -template class DefaultEngine; - -template -typename DefaultEngine::ObjObjects* DefaultEngine::ObjObjects::Inst; \ No newline at end of file diff --git a/UnrealMappingsDumper/engine.h b/UnrealMappingsDumper/engine.h deleted file mode 100644 index 2529e44..0000000 --- a/UnrealMappingsDumper/engine.h +++ /dev/null @@ -1,486 +0,0 @@ -#pragma once - -#include "uobjectDependency.h" -#include "unrealEnums.h" -#include "unrealContainers.h" -#include "scanning.h" - -typedef int FThreadSafeCounter; - -#define DECLARE_STATIC_CLASS(PATH) \ - static FORCEINLINE class UClass* StaticClass() \ - { \ - static auto Inst = FindObject(PATH); \ - return Inst; \ - } \ - - -/// -/// The idea here is to easily override UE classes for specific Engine versiosn if needed. DefaultEngine is targeted for UE5. -/// -template -class DefaultEngine -{ -public: - - class FName : public UObjectBase::FName - { - }; - - template - static T* FindObject(std::wstring FullName) - { - for (int i = 0; i < ObjObjects::Num(); i++) - { - auto Obj = ObjObjects::GetObjectByIndex(i); - - if (!Obj) continue; - - auto Path = Obj->GetPath(); - - if (FullName.size() != Path.size()) - continue; - - bool Same = wcsncmp(FullName.c_str(), Path.c_str(), FullName.size()) == 0; - - if (Same) - return (T*)Obj; - } - - return nullptr; - } - - class UObject - { - void* Vtbl; - EObjectFlags ObjectFlags; - int32_t InternalIndex; - class UClass* ClassPrivate; - FName NamePrivate; - UObject* OuterPrivate; - - public: - - void GetPathName(std::wstring& Result, UObject* StopOuter = nullptr); - - FORCEINLINE FName GetName() - { - return NamePrivate; - } - - FORCEINLINE std::wstring GetPath() - { - std::wstring Ret; - - GetPathName(Ret); - - return Ret; - } - - FORCEINLINE class UClass* Class() - { - return ClassPrivate; - } - - FORCEINLINE UObject* Outer() - { - return OuterPrivate; - } - }; - - class UField : public UObject - { - UField* Next; - - public: - - FORCEINLINE UField* GetNext() - { - return Next; - } - }; - - class FFieldClass - { - FName Name; - EClassCastFlags Id; - uint64_t CastFlags; - EClassFlags ClassFlags; - FFieldClass* SuperClass; - class FField* DefaultObject; - class FField* (*ConstructFn)(class FFieldVariant&, FName&, EObjectFlags); - FThreadSafeCounter UnqiueNameIndexCounter; - - public: - - FORCEINLINE std::string GetName() const - { - return Name.ToString(); - } - - FORCEINLINE UObjectBase::FName GetFName() const - { - return Name; - } - - FORCEINLINE uint64_t GetId() const - { - return Id; - } - - FORCEINLINE uint64_t GetCastFlags() const - { - return CastFlags; - } - - FORCEINLINE bool HasAnyCastFlags(const uint64_t InCastFlags) const - { - return !!(CastFlags & InCastFlags); - } - - FORCEINLINE bool HasAllCastFlags(const uint64_t InCastFlags) const - { - return (CastFlags & InCastFlags) == InCastFlags; - } - - FORCEINLINE EClassCastFlags GetId() - { - return Id; - } - }; - - class FFieldVariant - { - union FFieldObjectUnion - { - class FField* Field; - UObject* Object; - }Container; - - bool bIsUObject; - }; - - class FField - { - void* Vtbl; - FFieldClass* ClassPrivate; - FFieldVariant Owner; - FField* Next; - FName NamePrivate; - EObjectFlags FlagsPrivate; - - public: - - FORCEINLINE FName GetName() - { - return NamePrivate; - } - - FORCEINLINE FField* GetNext() - { - return Next; - } - - FORCEINLINE FFieldClass* GetClass() const - { - return ClassPrivate; - } - - FORCEINLINE EObjectFlags GetFlags() const - { - return FlagsPrivate; - } - - FORCEINLINE uint64_t GetCastFlags() const - { - return GetClass()->GetCastFlags(); - } - }; - - class FProperty : public FField - { - int32_t ArrayDim; - int32_t ElementSize; - EPropertyFlags PropertyFlags; - uint16_t RepIndex; - TEnumAsByte BlueprintReplicationCondition; - int32_t Offset_Internal; - - public: - - FName RepNotifyFunc; - FProperty* PropertyLinkNext; - FProperty* NextRef; - FProperty* DestructorLinkNext; - FProperty* PostConstructLinkNext; - - EPropertyType GetPropertyType(); - - FORCEINLINE int32_t GetArrayDim() - { - return ArrayDim; - } - }; - - class FStructProperty : public FProperty - { - class UScriptStruct* Struct; - - public: - - FORCEINLINE class UScriptStruct* GetStruct() - { - return Struct; - } - }; - - class FByteProperty : public FProperty - { - class UEnum* Enum; - - public: - - FORCEINLINE class UEnum* GetEnum() - { - return Enum; - } - }; - - class FArrayProperty : public FProperty - { - enum class EArrayPropertyFlags - { - None, - UsesMemoryImageAllocator - }; - - FProperty* Inner; - EArrayPropertyFlags ArrayFlags; - - public: - - FORCEINLINE FProperty* GetInner() - { - return Inner; - } - }; - - class FMapProperty : public FProperty - { - FProperty* KeyProp; - FProperty* ValueProp; - - public: - - FORCEINLINE FProperty* GetKey() - { - return KeyProp; - } - - FORCEINLINE FProperty* GetValue() - { - return ValueProp; - } - }; - - class FEnumProperty : public FProperty - { - FProperty* UnderlyingProp; - class UEnum* Enum; - - public: - - FORCEINLINE FProperty* GetUnderlying() - { - return UnderlyingProp; - } - - FORCEINLINE class UEnum* GetEnum() - { - return Enum; - } - }; - - class FStructBaseChain - { - FStructBaseChain** StructBaseChainArray; - int32_t NumStructBasesInChainMinusOne; - }; - - class UEnum : public UField - { - public: - - enum class ECppForm - { - Regular, - Namespaced, - EnumClass - }; - - FString CppType; - TArray> Names; - ECppForm CppForm; - EEnumFlags EnumFlags; - - DECLARE_STATIC_CLASS(L"/Script/CoreUObject.Enum"); - - std::wstring_view GetCPPString() - { - return CppType.AsString(); - } - }; - - class UStruct : public UField, FStructBaseChain - { - private: - - UStruct* SuperStruct; - - public: - - UField* Children; - FField* ChildProperties; - int32_t PropertiesSize; - int32_t MinAlignment; - TArray Script; - FProperty* PropertyLink; - FProperty* RefLink; - FProperty* DestructorLink; - FProperty* PostConstructLink; - TArray ScriptAndPropertyObjectReferences; - - FORCEINLINE UStruct* Super() - { - return SuperStruct; - } - - FORCEINLINE FProperty* GetProperties() - { - return static_cast(ChildProperties); - } - }; - - class UScriptStruct : public UStruct - { - public: - - DECLARE_STATIC_CLASS(L"/Script/CoreUObject.ScriptStruct"); - }; - - class UClass : public UStruct - { - typedef void (*ClassConstructorType)(const class FObjectInitializer&); - typedef UObject* (*ClassVTableHelperCtorCallerType)(class FVTableHelper& Helper); - typedef UClass* (*StaticClassFunctionType)(); - - public: - - ClassConstructorType ClassConstructor; - ClassVTableHelperCtorCallerType ClassVTableHelperCtorCaller; - void* CppClassStaticFunctions; - mutable int32_t ClassUnique; - int32_t FirstOwnedClassRep = 0; - bool bCooked; - bool bLayoutChanging; - EClassFlags ClassFlags; - EClassCastFlags ClassCastFlags; - UClass* ClassWithin; - FName ClassConfigName; - TUndefinedArray ClassReps; - TArray NetFields; - - DECLARE_STATIC_CLASS(L"/Script/CoreUObject.Class"); - }; - - // Completely refactor this if we need to override it for a different engine version, but still keep it as a singleton. - class ObjObjects - { - enum - { - NumElementsPerChunk = 64 * 1024, - }; - - static ObjObjects* Inst; - - public: - - struct FUObjectItem - { - UObject* Object; - int32_t Flags; - int32_t ClusterRootIndex; - int32_t SerialNumber; - }; - - ObjObjects& operator=(const ObjObjects&) = delete; - - private: - - FUObjectItem** Objects; - FUObjectItem* PreAllocatedObjects; - int32_t MaxElements; - int32_t NumElements; - int32_t MaxChunks; - int32_t NumChunks; - - public: - - static UObject* GetObjectByIndex(int Index) - { - int ChunkIndex = Index / NumElementsPerChunk; - int WithinChunkIndex = Index % NumElementsPerChunk; - - if ( - Index < Inst->NumElements && - Index >= 0 && - ChunkIndex < Inst->NumChunks && - Index < Inst->MaxElements - ) - { - auto Chunk = Inst->Objects[ChunkIndex]; - - if (Chunk) - return (Chunk + WithinChunkIndex)->Object; - } - - return nullptr; - } - - static void ForEach(std::function Action); - - static FORCEINLINE int Num() - { - return Inst->NumElements; - } - - static void SetInstance(uintptr_t Val) - { - if (Val) - Inst = (ObjObjects*)Val; - } - }; - -public: - - static std::vector> GetFNameStringPattrns(); - static std::vector> GetGObjectsPatterns(); - - template - static FORCEINLINE UClass* StaticClass() - { - return T::StaticClass(); - } -}; - -class Engine_UE5 : public DefaultEngine<> // DefaultEngine is pretty much already for UE5 -{ -public: - -}; - -class Engine_Fortnite : public DefaultEngine -{ -public: - -}; \ No newline at end of file diff --git a/UnrealMappingsDumper/framework.h b/UnrealMappingsDumper/framework.h index c15c583..5a255ed 100644 --- a/UnrealMappingsDumper/framework.h +++ b/UnrealMappingsDumper/framework.h @@ -11,4 +11,6 @@ #include #include #include +#include +#include #include "../Dependencies/parallel_hashmap/phmap.h" \ No newline at end of file diff --git a/UnrealMappingsDumper/unrealTypes.h b/UnrealMappingsDumper/unrealTypes.h new file mode 100644 index 0000000..516dd0e --- /dev/null +++ b/UnrealMappingsDumper/unrealTypes.h @@ -0,0 +1,475 @@ +#pragma once + +#include +#include +#include + +#include "unrealEnums.h" +#include "unrealFunctions.h" + +#define QUICK_OFFSET(type, offset) *(type*)((uintptr_t)this + offset) + +#define DECLARE_STATIC_CLASS(PATH) \ + static FORCEINLINE class UClass* StaticClass() \ + { \ + static auto Inst = ObjObjects::FindObject(PATH); \ + return Inst; \ + } \ + +class FName +{ +private: + + uint32_t Number = 0; + uint32_t Padding; + +public: + + static inline bool IsOptimized = false; + + __forceinline uint32_t GetNumber() + { + return Number; + } + + bool operator== (FName n) const + { + return n.Number == n.Number; + } + + friend size_t hash_value(const FName& p) + { + return size_t(p.Number); + } + + std::wstring_view AsString() const + { + FString Ret; + FNameToString(this, Ret); + + if (Ret.Data() != nullptr) + { + return std::wstring_view(Ret.Data()); + } + + return {}; + } + + std::string ToString() const + { + auto Ret = AsString(); + + return std::string(Ret.begin(), Ret.end()); + } +}; + +class UObject +{ +private: + + static inline int NameOffset = 0; + static inline int ClassOffset = 0; + static inline int OuterOffset = 0; + + friend struct IUnrealVersion; + +public: + + void GetPathName(std::wstring& Result, UObject* StopOuter = nullptr) + { + if (this == StopOuter || this == NULL) + { + Result += L"None"; + return; + } + + if (Outer() && Outer() != StopOuter) + { + Outer()->GetPathName(Result, StopOuter); + Result += L"."; + } + + Result += GetFName().AsString(); + } + + FORCEINLINE std::wstring_view GetName() + { + auto& Name = QUICK_OFFSET(FName, NameOffset); + return Name.AsString(); + } + + FORCEINLINE FName& GetFName() + { + return QUICK_OFFSET(FName, NameOffset); + } + + FORCEINLINE std::wstring GetPath() + { + std::wstring Ret; + + GetPathName(Ret); + + return Ret; + } + + FORCEINLINE class UClass* Class() + { + return QUICK_OFFSET(class UClass*, ClassOffset); + } + + FORCEINLINE UObject* Outer() + { + return QUICK_OFFSET(UObject*, OuterOffset); + } +}; + +class ObjObjects +{ + enum + { + NumElementsPerChunk = 64 * 1024, + }; + + static inline ObjObjects* Inst; + +public: + + struct FUObjectItem + { + UObject* Object; + int32_t Flags; + int32_t ClusterRootIndex; + int32_t SerialNumber; + }; + + ObjObjects& operator=(const ObjObjects&) = delete; + +private: + + FUObjectItem** Objects; + FUObjectItem* PreAllocatedObjects; + int32_t MaxElements; + int32_t NumElements; + int32_t MaxChunks; + int32_t NumChunks; + +public: + + static UObject* GetObjectByIndex(int Index) + { + int ChunkIndex = Index / NumElementsPerChunk; + int WithinChunkIndex = Index % NumElementsPerChunk; + + if ( + Index < Inst->NumElements && + Index >= 0 && + ChunkIndex < Inst->NumChunks && + Index < Inst->MaxElements + ) + { + auto Chunk = Inst->Objects[ChunkIndex]; + + if (Chunk) + return (Chunk + WithinChunkIndex)->Object; + } + + return nullptr; + } + + static FORCEINLINE int Num() + { + return Inst->NumElements; + } + + static void SetInstance(uintptr_t Val) + { + if (Val) + Inst = (ObjObjects*)Val; + } + + template + static T* FindObjectByName(const wchar_t* ObjectName) + { + for (int i = 0; i < Num(); i++) + { + auto Obj = GetObjectByIndex(i); + + if (!Obj) continue; + + if (Obj->GetName() == ObjectName) + return (T*)Obj; + } + + return nullptr; + } + + static void ForEach(std::function Action) + { + for (int i = 0; i < Num(); i++) + { + auto Obj = GetObjectByIndex(i); + + if (!Obj) continue; + + Action(Obj); + } + } + + template + static T* FindObject(std::wstring FullName) + { + for (int i = 0; i < Num(); i++) + { + auto Obj = GetObjectByIndex(i); + + if (!Obj) continue; + + auto Path = Obj->GetPath(); + + if (FullName.size() != Path.size()) + continue; + + bool Same = wcsncmp(FullName.c_str(), Path.c_str(), FullName.size()) == 0; + + if (Same) + return (T*)Obj; + } + + return nullptr; + } +}; + +class UStruct : public UObject +{ +private: + + static inline int SuperOffset = 0; + static inline int ChildPropertiesOffset = 0; + + friend struct IUnrealVersion; + +public: + + FORCEINLINE UStruct* Super() + { + return QUICK_OFFSET(UStruct*, SuperOffset); + } + + FORCEINLINE int32_t PropertiesSize() + { + return QUICK_OFFSET(int32_t, ChildPropertiesOffset + sizeof(void*)); + } + + FORCEINLINE class FProperty* ChildProperties() + { + return QUICK_OFFSET(class FProperty*, ChildPropertiesOffset); + } +}; + +class UClass : public UStruct +{ +public: + + DECLARE_STATIC_CLASS(L"/Script/CoreUObject.Class"); +}; + +class UScriptStruct : public UStruct +{ +public: + + DECLARE_STATIC_CLASS(L"/Script/CoreUObject.ScriptStruct"); +}; + +class FFieldClass +{ + FName Name; + EClassCastFlags Id; + +public: + + FORCEINLINE FName& GetFName() + { + return Name; + } + + FORCEINLINE std::wstring_view GetName() + { + return Name.AsString(); + } + + FORCEINLINE EClassCastFlags GetId() + { + return Id; + } +}; + +class FField +{ +public: + + class Variant + { + union FFieldObjectUnion + { + FField* Field; + UObject* Object; + }Container; + + bool bIsUObject; + }; + +private: + + void* Vtbl; + FFieldClass* ClassPrivate; + Variant Owner; + FField* Next; + FName NamePrivate; + EObjectFlags FlagsPrivate; + +public: + + FORCEINLINE FName& GetFName() + { + return NamePrivate; + } + + FORCEINLINE FField* GetNext() const + { + return Next; + } + + FORCEINLINE FFieldClass* GetClass() const + { + return ClassPrivate; + } + + FORCEINLINE EObjectFlags GetFlags() const + { + if (FName::IsOptimized) + { + return QUICK_OFFSET(EObjectFlags, offsetof(FField, NamePrivate) + 4); + } + + return FlagsPrivate; + } +}; + +class FProperty : public FField +{ +private: + + int32_t ArrayDim; + +protected: + + static inline int FPropertySize = 0; + +public: + + friend struct IUnrealVersion; + + FORCEINLINE int32_t GetArrayDim() + { + if (FName::IsOptimized) + { + return QUICK_OFFSET(int32_t, sizeof(FField) - 4); + } + + return ArrayDim; + } +}; + +class UEnum : public UObject +{ +public: + + TArray>& Names() + { + static auto FieldSize = ObjObjects::FindObjectByName(L"Field")->PropertiesSize(); + + return *(TArray>*) + & QUICK_OFFSET(uint8_t, FieldSize + sizeof(FString)); + } + + DECLARE_STATIC_CLASS(L"/Script/CoreUObject.Enum"); +}; + +class FStructProperty : public FProperty +{ + UScriptStruct* Struct; + +public: + + FORCEINLINE UScriptStruct* GetStruct() + { + return QUICK_OFFSET(UScriptStruct*, FPropertySize); + } +}; + +class FByteProperty : public FProperty +{ + UEnum* Enum; + +public: + + FORCEINLINE UEnum* GetEnum() + { + return QUICK_OFFSET(UEnum*, FPropertySize); + } +}; + +class FArrayProperty : public FProperty +{ + enum class EArrayPropertyFlags + { + None, + UsesMemoryImageAllocator + }; + + FProperty* Inner; + EArrayPropertyFlags ArrayFlags; + +public: + + FORCEINLINE FProperty* GetInner() + { + return QUICK_OFFSET(FProperty*, FPropertySize); + } +}; + +class FMapProperty : public FProperty +{ + FProperty* KeyProp; + FProperty* ValueProp; + +public: + + FORCEINLINE FProperty* GetKey() + { + return QUICK_OFFSET(FProperty*, FPropertySize); + } + + FORCEINLINE FProperty* GetValue() + { + return QUICK_OFFSET(FProperty*, FPropertySize + sizeof(FProperty*)); + } +}; + +class FEnumProperty : public FProperty +{ + FProperty* UnderlyingProp; + UEnum* Enum; + +public: + + FORCEINLINE FProperty* GetUnderlying() + { + return QUICK_OFFSET(FProperty*, FPropertySize); + } + + FORCEINLINE UEnum* GetEnum() + { + return QUICK_OFFSET(UEnum*, FPropertySize + sizeof(FProperty*)); + } +}; \ No newline at end of file diff --git a/UnrealMappingsDumper/unrealVersion.cpp b/UnrealMappingsDumper/unrealVersion.cpp new file mode 100644 index 0000000..142574f --- /dev/null +++ b/UnrealMappingsDumper/unrealVersion.cpp @@ -0,0 +1,60 @@ +#include "pch.h" + +#include "unrealVersion.h" + +#pragma comment(lib, "Version.lib") + +#define SCAN_LIMIT 0x300 + +#define SCAN_FOR_MEMBER_OFFSET(obj, member, outOffset) \ + for (uint8_t* i = (uint8_t*)obj; ; i++)\ + {\ + auto Count = i - (uint8_t*)obj;\ + if (Count >= SCAN_LIMIT)\ + return false;\ + \ + if (*(uintptr_t*)i == (uintptr_t)member)\ + {\ + outOffset = Count;\ + break;\ + }\ + }\ + + +//this is super unsafe but hopefully stackoverflow comes in clutch https://stackoverflow.com/a/42389638 +bool IUnrealVersion::TryDynamicOffsets() +{ + try + { + auto UClassPtr = ObjObjects::FindObjectByName(L"Class"); + auto UObjectPtr = ObjObjects::FindObjectByName(L"Object"); + auto ActorPtr = ObjObjects::FindObjectByName(L"Actor"); + auto EnginePtr = ObjObjects::FindObjectByName(L"/Script/Engine"); + + if (!UClassPtr or !UObjectPtr or !ActorPtr or !EnginePtr) + return false; + + SCAN_FOR_MEMBER_OFFSET(UObjectPtr, UClassPtr, UObject::ClassOffset); + + if (!UObject::ClassOffset) + return false; + + SCAN_FOR_MEMBER_OFFSET(ActorPtr, UObjectPtr, UStruct::SuperOffset); + + if (!UStruct::SuperOffset) + return false; + + UStruct::ChildPropertiesOffset = UStruct::SuperOffset + (sizeof(void*) * 2); + + SCAN_FOR_MEMBER_OFFSET(ActorPtr, EnginePtr, UObject::OuterOffset); + + if (!UObject::OuterOffset) + return false; + } + catch (...) + { + return false; + } + + return true; +} \ No newline at end of file diff --git a/UnrealMappingsDumper/unrealVersion.h b/UnrealMappingsDumper/unrealVersion.h new file mode 100644 index 0000000..7ac22be --- /dev/null +++ b/UnrealMappingsDumper/unrealVersion.h @@ -0,0 +1,150 @@ +#pragma once + +#include "app.h" +#include "unrealTypes.h" +#include "scanning.h" + +/* +* The idea here is to make something that can easily be overriden for engine or game versions with different types +* that need to be overriden or handled differently. +*/ + +struct IUnrealVersion +{ +private: + static bool TryDynamicOffsets(); + +public: + + template + static bool InitTypes() + { + uintptr_t GObjectsAddy = 0; + + for (auto Scan : Version::GetGObjectsPatterns()) + { + GObjectsAddy = Scan->TryFind(); + + if (GObjectsAddy) + break; + } + + if (!GObjectsAddy) + { + UE_LOG("Could not find the address for GObjects. Try overriding it or adding the correct sig for it."); + return false; + } + + ObjObjects::SetInstance(GObjectsAddy); + + uintptr_t FNameStringAddy = 0; + + for (auto Scan : Version::GetFNameStringPatterns()) + { + FNameStringAddy = Scan->TryFind(); + + if (FNameStringAddy) + break; + } + + if (!FNameStringAddy) + { + UE_LOG("Could not find the address for FNameToString. Try overriding it or adding the correct sig for it."); + return false; + } + + FNameToString = (_FNameToString)FNameStringAddy; + + using UObjectImpl = Version::Offsets::UObject; + using UStructImpl = Version::Offsets::UStruct; + + UObject::NameOffset = UObjectImpl::NameOffset; + FName::IsOptimized = Version::HasOptimizedFName; + FProperty::FPropertySize = Version::FPropertySize; + + if (!TryDynamicOffsets()) + { + UE_LOG("Could not grab dynamic offsets. Just gonna use the hardcoded ones."); + + UObject::ClassOffset = UObjectImpl::ClassOffset; + UObject::OuterOffset = UObjectImpl::OuterOffset; + + UStruct::SuperOffset = UStructImpl::SuperOffset; + UStruct::ChildPropertiesOffset = UStructImpl::ChildPropertiesOffset; + } + + return true; + } +}; + +/* +* Yes, it's true, UObject should pretty much always be the same across all games, +* however there are some games that use a custom UObject, so should they ever need mappings, +* this design pattern makes it easier to override the offsets. +*/ + +struct UnrealVersionBase : IUnrealVersion +{ + static constexpr int FPropertySize = 0x78; + static constexpr bool HasOptimizedFName = false; + + struct Offsets + { + struct UObject + { + static constexpr int NameOffset = 0x18; + static constexpr int ClassOffset = 0x10; + static constexpr int OuterOffset = 0x20; + }; + + struct UStruct + { + static constexpr int SuperOffset = 0x40; + static constexpr int ChildPropertiesOffset = 0x50; + }; + }; + + static std::vector> GetFNameStringPatterns() + { + return + { + std::make_shared("E8 ? ? ? ? 83 7D C8 00 48 8D 15 ? ? ? ? 0F 5A DE", 1, true), + std::make_shared>( + L"%s %s SetTimer passed a negative or zero time. The associated timer may fail to be created/fire! If using InitialStartDelayVariance, be sure it is smaller than (Time + InitialStartDelay).", + true, 1, true, 0xE8) + }; + } + + + static std::vector> GetGObjectsPatterns() + { + return + { + std::make_shared("48 89 05 ? ? ? ? E8 ? ? ? ? ? ? ? 0F 84", 3, true), + std::make_shared("48 8B 05 ? ? ? ? 48 8B 0C 07 48 85 C9 74 20", 3, true), + std::make_shared("48 8B 05 ? ? ? ? 48 8B 0C", 3, true), + std::make_shared("48 03 ? ? ? ? ? ? ? ? ? ? 48 8B 10 48 85 D2 74 07", 3, true) + + }; + } +}; + +/* +* Use this if the games engine has UE_FNAME_OUTLINE_NUMBER defined as 1 +*/ +struct Version_OptimizedFName : UnrealVersionBase +{ + static constexpr int FPropertySize = 0x70; + static constexpr bool HasOptimizedFName = true; +}; + +struct Version_FortniteLatest : Version_OptimizedFName +{ + static std::vector> GetGObjectsPatterns() + { + return + { + std::make_shared("48 8B 05 ? ? ? ? 48 8B 0C C8", 3, true) + }; + } +}; \ No newline at end of file diff --git a/UnrealMappingsDumper/uobjectDependency.h b/UnrealMappingsDumper/uobjectDependency.h deleted file mode 100644 index 915329a..0000000 --- a/UnrealMappingsDumper/uobjectDependency.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#ifndef UE_FNAME_OUTLINE_NUMBER - #define UE_FNAME_OUTLINE_NUMBER 1 -#endif - -typedef uint32_t FNameEntryId; - -class FNameBase -{ -public: - - std::wstring_view AsString() const; - std::string ToString() const; -}; - -class UObjectDependency -{ -public: - - struct FName : public FNameBase - { - FNameEntryId ComparisonIndex; - uint32_t Number; - - bool operator== (FName n) const - { - return ComparisonIndex == n.ComparisonIndex; - } - - friend size_t hash_value(const FName& p) - { - return phmap::HashState().combine(0, p.ComparisonIndex, p.ComparisonIndex / 3); - } - }; - -protected: - - UObjectDependency() - { - } -}; - -class FortniteUObjectBase : public UObjectDependency -{ -public: - - struct FName : public FNameBase - { - FNameEntryId ComparisonIndex; - -#if !UE_FNAME_OUTLINE_NUMBER - uint32_t Number; -#endif - - bool operator== (FName n) const - { - return ComparisonIndex == n.ComparisonIndex; - } - - friend size_t hash_value(const FName& p) - { - return phmap::HashState().combine(0, p.ComparisonIndex, p.ComparisonIndex / 3); - } - }; - -protected: - - FortniteUObjectBase() - { - } -}; \ No newline at end of file diff --git a/UnrealMappingsDumper/writer.h b/UnrealMappingsDumper/writer.h index a189499..168a660 100644 --- a/UnrealMappingsDumper/writer.h +++ b/UnrealMappingsDumper/writer.h @@ -66,46 +66,46 @@ class StreamWriter : IBufferWriter class FileWriter : IBufferWriter { - FILE* m_File; + std::ofstream m_File; public: FileWriter(const char* FileName) { - fopen_s(&m_File, FileName, "wb"); + m_File = std::ofstream(FileName, std::ios::binary); } ~FileWriter() { - std::fclose(m_File); + m_File.close(); } FORCEINLINE void WriteString(std::string String) override { - std::fwrite(String.c_str(), String.length(), 1, m_File); + m_File.write(String.c_str(), String.length()); } FORCEINLINE void WriteString(std::string_view String) override { - std::fwrite(String.data(), String.size(), 1, m_File); + m_File.write(String.data(), String.size()); } FORCEINLINE void Write(void* Input, size_t Size) override { - std::fwrite(Input, Size, 1, m_File); + m_File.write(static_cast(Input), Size); } FORCEINLINE void Seek(int Pos, int Origin = SEEK_CUR) override { - std::fseek(m_File, Pos, Origin); + m_File.seekp(Pos, Origin); } uint32_t Size() override { - auto pos = std::ftell(m_File); - std::fseek(m_File, 0, SEEK_END); - auto ret = std::ftell(m_File); - std::fseek(m_File, pos, SEEK_SET); + auto pos = m_File.tellp(); + Seek(0, SEEK_END); + auto ret = m_File.tellp(); + Seek(pos, SEEK_SET); return ret; }