From 96a8ae2fcacee9d3cb154b1e32119b163be7277a Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Mon, 29 Jun 2026 14:44:59 +0300 Subject: [PATCH 1/3] Add skip_vtables and skip_objects flags --- jmap_dumper/src/lib.rs | 26 +++++++++++++++++++++++--- jmap_dumper/src/main.rs | 10 ++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/jmap_dumper/src/lib.rs b/jmap_dumper/src/lib.rs index 0e2bf45..8b7bdeb 100644 --- a/jmap_dumper/src/lib.rs +++ b/jmap_dumper/src/lib.rs @@ -289,6 +289,10 @@ pub struct DumpOptions { pub names: bool, /// Print struct layouts before dumping pub verbose: bool, + /// Skip vtable analysis + pub skip_vtables: bool, + /// Skip all objects + pub skip_objects: bool, } pub fn dump( @@ -752,6 +756,14 @@ async fn dump_one( return Ok(None); }; + if options.verbose { + eprintln!("[{i}/{num}] {0:#x}", obj.address()); + } + + if obj.vtable().address() == 0 { + return Ok(None); + } + let path = obj.path().await?; if options.verbose { @@ -769,7 +781,11 @@ async fn dump_inner(mem: Ctx, source_name: &str, options: DumpOptions) -> Result let mut objects = BTreeMap::::default(); let mut child_map = HashMap::>::default(); - let num = uobjectarray.num_elements().await? as usize; + let num = if !options.skip_objects { + uobjectarray.num_elements().await? as usize + } else { + 0 + }; // keep many object dumps in flight let mut stream = futures_util::stream::iter(0..num) @@ -800,8 +816,12 @@ async fn dump_inner(mem: Ctx, source_name: &str, options: DumpOptions) -> Result } } - let vtables = vtable::analyze_vtables(&mem, &mut objects).await; - + let vtables = if options.skip_vtables { + BTreeMap::new() + } else { + vtable::analyze_vtables(&mem, &mut objects).await + }; + let names = if options.names { Some(extract_fnames(&mem).await?) } else { diff --git a/jmap_dumper/src/main.rs b/jmap_dumper/src/main.rs index c28fe41..72e9fbe 100644 --- a/jmap_dumper/src/main.rs +++ b/jmap_dumper/src/main.rs @@ -79,6 +79,14 @@ struct Cli { #[arg(long, short = 'v')] verbose: bool, + /// Skip vtables + #[arg(long)] + skip_vtables: bool, + + /// Skip objects + #[arg(long)] + skip_objects: bool, + /// Output dump .jmap path #[arg(index = 1)] output: PathBuf, @@ -129,6 +137,8 @@ fn main() -> Result<()> { all: cli.all, names: cli.names, verbose: cli.verbose, + skip_vtables: cli.skip_vtables, + skip_objects: cli.skip_objects, }; let overrides = ConfigOverrides { From db8be2098c337dca39fc5a37d2bdab932daaaa75 Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Mon, 29 Jun 2026 14:45:55 +0300 Subject: [PATCH 2/3] Print UPackage layout --- jmap_dumper/src/structs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/jmap_dumper/src/structs.rs b/jmap_dumper/src/structs.rs index 985a254..85e8de2 100644 --- a/jmap_dumper/src/structs.rs +++ b/jmap_dumper/src/structs.rs @@ -132,6 +132,7 @@ pub fn get_struct_info_for_version( ("properties", "FField"), ("properties", "FFieldClass"), ("properties", "FFieldVariant"), + ("package", "UPackage"), ]; let mounted_container = vm_state.mount_container(container)?; From 5274c04c8cc4d3c7e2f950a2335bf2ef37c8aecb Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Mon, 29 Jun 2026 14:46:51 +0300 Subject: [PATCH 3/3] Fix layout for UE5.8 --- jmap_dumper/unreal/src/objects.gs | 15 +++++++++++-- jmap_dumper/unreal/src/properties.gs | 1 + jmap_dumper/unreal/src/uobjectarray.gs | 29 +++++++++++++++++++------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/jmap_dumper/unreal/src/objects.gs b/jmap_dumper/unreal/src/objects.gs index ba3d8fd..dc8031f 100644 --- a/jmap_dumper/unreal/src/objects.gs +++ b/jmap_dumper/unreal/src/objects.gs @@ -207,6 +207,12 @@ class UClass : UStruct, uint32_t ClassUnique : 1; uint32_t bCooked : 1; } + if (UE_VERSION >= 508) { + bool bNeedsDynamicSubobjectInstancing; + bool bNeedsPostLoadSubobjectInstancing; + } + + if (UE_VERSION >= 508) int32_t PropertiesStartOffset; EClassFlags ClassFlags; EClassCastFlags ClassCastFlags; if (UE_VERSION < 408) int32_t ClassUnique; @@ -227,7 +233,7 @@ class UClass : UStruct, STUB* SparseClassData; UScriptStruct* SparseClassDataStruct; } - if (UE_VERSION >= 507) bool bNeedsDynamicSubobjectInstancing; + if (UE_VERSION == 507) bool bNeedsDynamicSubobjectInstancing; TMap FuncMap; if (UE_VERSION >= 411 && UE_VERSION < 418) { TMap ParentFuncMap; @@ -238,10 +244,14 @@ class UClass : UStruct, TMap SuperFuncMap; if (UE_VERSION >= 421) FWindowsRWLock SuperFuncMapLock; } - if (UE_VERSION >= 503) { + if (UE_VERSION >= 503 && UE_VERSION < 508) { TMap AllFunctionsCache; FWindowsRWLock AllFunctionsCacheLock; + } else if (UE_VERSION >= 508) { + FWindowsRWLock AllFunctionsCacheLock; + TMap AllFunctionsCache; } + TArray Interfaces; if (UE_VERSION >= 503) STUB* ReferenceSchema; // UE::GC::FSchemaOwner else if (UE_VERSION == 502) FTokenStreamOwner ReferenceTokens; // UE::GC::FTokenStreamOwner @@ -250,4 +260,5 @@ class UClass : UStruct, if (UE_VERSION >= 415) FCriticalSection ReferenceTokenStreamCritical; } TArray NativeFunctionLookupTable; + if (UE_VERSION >= 508) STUB* Partials; // UE::CoreUObject::Private::FPartialClass }; diff --git a/jmap_dumper/unreal/src/properties.gs b/jmap_dumper/unreal/src/properties.gs index 5dc497d..b41d60b 100644 --- a/jmap_dumper/unreal/src/properties.gs +++ b/jmap_dumper/unreal/src/properties.gs @@ -24,6 +24,7 @@ struct TObjectPtr { /// TEST class FFieldClass { + if (UE_VERSION >= 508) uint64_t VTable; FName Name; if (UE_VERSION >= 507) EClassFlags ClassFlags; uint64_t Id; diff --git a/jmap_dumper/unreal/src/uobjectarray.gs b/jmap_dumper/unreal/src/uobjectarray.gs index ae3bc98..c4e9bd9 100644 --- a/jmap_dumper/unreal/src/uobjectarray.gs +++ b/jmap_dumper/unreal/src/uobjectarray.gs @@ -16,9 +16,10 @@ struct member_pack(if (UE_PACK_FUOBJECT_ITEM) 4 else 8) FUObjectItem { if (UE_VERSION < 413) int32_t ClusterAndFlags; if (UE_VERSION >= 413 && UE_VERSION < 507) int32_t Flags; if (UE_VERSION >= 413 && UE_VERSION < 416) int32_t ClusterIndex; - if (UE_VERSION >= 416) int32_t ClusterRootIndex; + if (UE_VERSION >= 416 && UE_VERSION < 507) int32_t ClusterRootIndex; int32_t SerialNumber; + if (UE_VERSION >= 507) int32_t ClusterRootIndex; if (UE_VERSION >= 505 && UE_VERSION < 507) int32_t RefCount; }; @@ -32,11 +33,19 @@ struct FFixedUObjectArray { /// TEST struct FChunkedFixedUObjectArray { (FUObjectItem*)* Objects; // Array of pointers to FUObjectItem chunks - FUObjectItem* PreAllocatedObjects; - int32_t MaxElements; - int32_t NumElements; - int32_t MaxChunks; - int32_t NumChunks; + if (UE_VERSION >= 508) { + int32_t NumElements; + int32_t MaxElements; + int32_t NumChunks; + int32_t MaxChunks; + FUObjectItem* PreAllocatedObjects; + } else { + FUObjectItem* PreAllocatedObjects; + int32_t MaxElements; + int32_t NumElements; + int32_t MaxChunks; + int32_t NumChunks; + } }; template @@ -79,6 +88,8 @@ class FUObjectDeleteListener { /// FUObjectArray core structure /// TEST struct FUObjectArray { + if (UE_VERSION >= 508) FChunkedFixedUObjectArray ObjObjects; + int32_t ObjFirstGCIndex; int32_t ObjLastNonGCIndex; if (UE_VERSION >= 411) int32_t MaxObjectsNotConsideredByGC; @@ -88,7 +99,7 @@ struct FUObjectArray { if (UE_VERSION == 407) FUObjectArrayOlder ObjObjects; else if (UE_VERSION < 411) FUObjectArrayOld ObjObjects; else if (UE_VERSION < 420) FFixedUObjectArray ObjObjects; - else FChunkedFixedUObjectArray ObjObjects; + else if (UE_VERSION < 508) FChunkedFixedUObjectArray ObjObjects; if (UE_VERSION == 407) TArray ObjAvailable; else { @@ -96,9 +107,13 @@ struct FUObjectArray { if (UE_VERSION < 422) TLockFreePointerListUnordered ObjAvailableList; else if (UE_VERSION < 427) TLockFreePointerListUnordered ObjAvailableList; else TArray ObjAvailableList; + + if (UE_VERSION >= 507) int32_t ObjAvailableListEstimateCount; } TArray UObjectCreateListeners; + if (UE_VERSION >= 508) FCriticalSection UObjectCreateListenersCritical; + TArray UObjectDeleteListeners; if (UE_VERSION >= 409) FCriticalSection UObjectDeleteListenersCritical;