├── LICENSE ├── PakAnalyzer ├── PakAnalyzer.Build.cs ├── Private │ ├── AssetParseThreadWorker.cpp │ ├── AssetParseThreadWorker.h │ ├── BaseAnalyzer.cpp │ ├── BaseAnalyzer.h │ ├── ExtractThreadWorker.cpp │ ├── ExtractThreadWorker.h │ ├── FolderAnalyzer.cpp │ ├── FolderAnalyzer.h │ ├── IoStoreAnalyzer.cpp │ ├── IoStoreAnalyzer.h │ ├── IoStoreDefines.h │ ├── PakAnalyzer.cpp │ ├── PakAnalyzer.h │ ├── PakAnalyzerModule.cpp │ ├── PakFileEntry.cpp │ ├── UnrealAnalyzer.cpp │ └── UnrealAnalyzer.h └── Public │ ├── CommonDefines.h │ ├── IPakAnalyzer.h │ ├── PakAnalyzerModule.h │ └── PakFileEntry.h ├── README-EN.md ├── README.md ├── Resources ├── Icons │ ├── Edit │ │ └── icon_Edit_Copy_40x.png │ ├── FolderClosed.png │ ├── FolderOpen.png │ ├── icon_Blueprint_Find_40px.png │ ├── icon_ContentBrowser_40x.png │ ├── icon_FontEd_Export_40x.png │ ├── icon_Persona_Skeleton_Tree_16x.png │ ├── icon_StaticMeshEd_Collision_40x.png │ ├── icon_file_open_40x.png │ ├── icon_levels_visible_40x.png │ └── icon_source_control_40x.png ├── Images │ ├── AESKey.png │ ├── AssetSummary.png │ ├── ClassFilter.png │ ├── DependencyPackages.png │ ├── DependentPackages.png │ ├── ExportObjects.png │ ├── FileDetail.png │ ├── FolderDetail.png │ ├── FolderDetailClass.png │ ├── ImportObjects.png │ ├── ListView.png │ ├── ListViewContext.png │ ├── LoadAssetRegistry.png │ ├── NameFilter.png │ ├── Names.png │ ├── ObjectDependencies.png │ ├── OpenPak.png │ ├── PakSummary.png │ ├── TreeView.png │ └── TreeViewContext.png └── Windows │ ├── RCa27464 │ ├── Resource.rc │ └── UnrealPakViewer.ico ├── UnrealPakViewer.Target.cs └── UnrealPakViewer ├── Private ├── PlatformMain │ ├── Linux │ │ └── UnrealPakViewerMainLinux.cpp │ ├── Mac │ │ └── UnrealPakViewerMainMac.cpp │ └── Windows │ │ └── UnrealPakViewerMainWindows.cpp ├── UnrealPakViewerApplication.cpp ├── UnrealPakViewerApplication.h ├── UnrealPakViewerMain.cpp ├── UnrealPakViewerMain.h ├── UnrealPakViewerStyle.cpp ├── UnrealPakViewerStyle.h ├── ViewModels │ ├── ClassColumn.cpp │ ├── ClassColumn.h │ ├── FileColumn.cpp │ ├── FileColumn.h │ ├── FileSortAndFilter.cpp │ ├── FileSortAndFilter.h │ ├── WidgetDelegates.cpp │ └── WidgetDelegates.h └── Widgets │ ├── SAboutWindow.cpp │ ├── SAboutWindow.h │ ├── SAssetSummaryView.cpp │ ├── SAssetSummaryView.h │ ├── SExtractProgressWindow.cpp │ ├── SExtractProgressWindow.h │ ├── SKeyInputWindow.cpp │ ├── SKeyInputWindow.h │ ├── SKeyValueRow.cpp │ ├── SKeyValueRow.h │ ├── SMainWindow.cpp │ ├── SMainWindow.h │ ├── SOptionsWindow.cpp │ ├── SOptionsWindow.h │ ├── SPakClassView.cpp │ ├── SPakClassView.h │ ├── SPakFileView.cpp │ ├── SPakFileView.h │ ├── SPakSummaryView.cpp │ ├── SPakSummaryView.h │ ├── SPakTreeView.cpp │ └── SPakTreeView.h └── UnrealPakViewer.Build.cs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PakAnalyzer/PakAnalyzer.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class PakAnalyzer : ModuleRules 6 | { 7 | public PakAnalyzer(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PrivateDependencyModuleNames.AddRange( 12 | new string[] 13 | { 14 | "Core", 15 | "Json", 16 | "AssetRegistry", 17 | "CoreUObject", 18 | } 19 | ); 20 | 21 | PublicDependencyModuleNames.AddRange( 22 | new string[] 23 | { 24 | "PakFile", 25 | } 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/AssetParseThreadWorker.cpp: -------------------------------------------------------------------------------- 1 | #include "AssetParseThreadWorker.h" 2 | 3 | #include "Async/ParallelFor.h" 4 | #include "HAL/CriticalSection.h" 5 | #include "HAL/FileManager.h" 6 | #include "HAL/PlatformProcess.h" 7 | #include "HAL/RunnableThread.h" 8 | #include "IPlatformFilePak.h" 9 | #include "Misc/Compression.h" 10 | #include "Misc/Paths.h" 11 | #include "Misc/ScopeLock.h" 12 | #include "Serialization/Archive.h" 13 | #include "Serialization/MemoryWriter.h" 14 | #include "Serialization/MemoryReader.h" 15 | #include "UObject/ObjectResource.h" 16 | #include "UObject/ObjectVersion.h" 17 | #include "UObject/PackageFileSummary.h" 18 | 19 | #include "CommonDefines.h" 20 | #include "ExtractThreadWorker.h" 21 | 22 | class FAssetParseMemoryReader : public FMemoryReader 23 | { 24 | public: 25 | explicit FAssetParseMemoryReader(const TArray& InNameMap, const TArray& InBytes, bool bIsPersistent = false) 26 | : FMemoryReader(InBytes, bIsPersistent) 27 | , NameMap(InNameMap) 28 | { 29 | } 30 | 31 | FArchive& operator<<(FName& InName) 32 | { 33 | FArchive& Ar = *this; 34 | int32 NameIndex; 35 | Ar << NameIndex; 36 | int32 Number = 0; 37 | Ar << Number; 38 | 39 | if (NameMap.IsValidIndex(NameIndex)) 40 | { 41 | // if the name wasn't loaded (because it wasn't valid in this context) 42 | FNameEntryId MappedName = NameMap[NameIndex]; 43 | 44 | // simply create the name from the NameMap's name and the serialized instance number 45 | InName = FName::CreateFromDisplayId(MappedName, Number); 46 | } 47 | 48 | return Ar; 49 | } 50 | 51 | protected: 52 | const TArray& NameMap; 53 | }; 54 | 55 | template 56 | FString FindFullPath(const TArray& InMaps, int32 Index, const FString& InPathSpliter = TEXT("/")) 57 | { 58 | if (!InMaps.IsValidIndex(Index)) 59 | { 60 | return TEXT("Invalid"); 61 | } 62 | 63 | const T& Object = InMaps[Index]; 64 | FString ObjectPath = Object.ObjectName.ToString(); 65 | 66 | if (!Object.OuterIndex.IsNull()) 67 | { 68 | const FString ParentObjectPath = FindFullPath(InMaps, Object.OuterIndex.IsImport() ? Object.OuterIndex.ToImport() : Object.OuterIndex.ToExport(), InPathSpliter); 69 | ObjectPath = ParentObjectPath + InPathSpliter + ObjectPath; 70 | } 71 | 72 | return ObjectPath; 73 | } 74 | 75 | FAssetParseThreadWorker::FAssetParseThreadWorker() 76 | : Thread(nullptr) 77 | { 78 | } 79 | 80 | FAssetParseThreadWorker::~FAssetParseThreadWorker() 81 | { 82 | Shutdown(); 83 | } 84 | 85 | bool FAssetParseThreadWorker::Init() 86 | { 87 | return true; 88 | } 89 | 90 | uint32 FAssetParseThreadWorker::Run() 91 | { 92 | UE_LOG(LogPakAnalyzer, Display, TEXT("Asset parse worker starts.")); 93 | 94 | FCriticalSection Mutex; 95 | const static bool bForceSingleThread = false; 96 | const int32 TotalCount = Files.Num(); 97 | 98 | TMultiMap DependsMap; 99 | TMap ClassMap; 100 | 101 | // Parse assets 102 | ParallelFor(TotalCount, [this, &DependsMap, &ClassMap, &Mutex](int32 InIndex){ 103 | if (StopTaskCounter.GetValue() > 0) 104 | { 105 | return; 106 | } 107 | 108 | TArray FileBuffer; 109 | bool SerializeSuccess = false; 110 | 111 | FPakFileEntryPtr File = Files[InIndex]; 112 | if (!Summaries.IsValidIndex(File->OwnerPakIndex) || File->PakEntry.IsDeleteRecord()) 113 | { 114 | return; 115 | } 116 | 117 | const FPakFileSumary& Summary = Summaries[File->OwnerPakIndex]; 118 | const FString PakFilePath = Summary.PakFilePath; 119 | const int32 PakVersion = Summary.PakInfo.Version; 120 | const FAES::FAESKey AESKey = Summary.DecryptAESKey; 121 | 122 | if (OnReadAssetContent.IsBound()) 123 | { 124 | OnReadAssetContent.Execute(File, SerializeSuccess, FileBuffer); 125 | } 126 | else 127 | { 128 | FArchive* ReaderArchive = IFileManager::Get().CreateFileReader(*PakFilePath); 129 | ReaderArchive->Seek(File->PakEntry.Offset); 130 | 131 | FPakEntry EntryInfo; 132 | EntryInfo.Serialize(*ReaderArchive, PakVersion); 133 | 134 | if (EntryInfo.IndexDataEquals(File->PakEntry)) 135 | { 136 | FMemoryWriter Writer(FileBuffer, false, true); 137 | 138 | if (EntryInfo.CompressionMethodIndex == 0) 139 | { 140 | const int64 BufferSize = 8 * 1024 * 1024; // 8MB buffer for extracting 141 | void* Buffer = FMemory::Malloc(BufferSize); 142 | 143 | if (FExtractThreadWorker::BufferedCopyFile(Writer, *ReaderArchive, File->PakEntry, Buffer, BufferSize, AESKey)) 144 | { 145 | SerializeSuccess = true; 146 | } 147 | 148 | FMemory::Free(Buffer); 149 | } 150 | else 151 | { 152 | uint8* PersistantCompressionBuffer = NULL; 153 | int64 CompressionBufferSize = 0; 154 | const bool bHasRelativeCompressedChunkOffsets = PakVersion >= FPakInfo::PakFile_Version_RelativeChunkOffsets; 155 | 156 | if (FExtractThreadWorker::UncompressCopyFile(Writer, *ReaderArchive, File->PakEntry, PersistantCompressionBuffer, CompressionBufferSize, AESKey, File->CompressionMethod, bHasRelativeCompressedChunkOffsets)) 157 | { 158 | SerializeSuccess = true; 159 | } 160 | 161 | FMemory::Free(PersistantCompressionBuffer); 162 | } 163 | } 164 | 165 | ReaderArchive->Close(); 166 | delete ReaderArchive; 167 | } 168 | 169 | if (SerializeSuccess) 170 | { 171 | if (!File->AssetSummary.IsValid()) 172 | { 173 | File->AssetSummary = MakeShared(); 174 | } 175 | else 176 | { 177 | File->AssetSummary->Names.Empty(); 178 | File->AssetSummary->ObjectExports.Empty(); 179 | File->AssetSummary->ObjectImports.Empty(); 180 | } 181 | 182 | TArray NameMap; 183 | FAssetParseMemoryReader Reader(NameMap, FileBuffer); 184 | 185 | // Serialize summary 186 | Reader << File->AssetSummary->PackageSummary; 187 | 188 | Reader.Seek(0); 189 | int32 Tag = 0; 190 | Reader << Tag; 191 | if (Tag == PACKAGE_FILE_TAG_SWAPPED) 192 | { 193 | if (Reader.ForceByteSwapping()) 194 | { 195 | Reader.SetByteSwapping(false); 196 | } 197 | else 198 | { 199 | Reader.SetByteSwapping(true); 200 | } 201 | } 202 | 203 | int32 LegacyFileVersion = -8; 204 | Reader << LegacyFileVersion; 205 | 206 | if (LegacyFileVersion >= -7) 207 | { 208 | // UE4 pak 209 | Reader.SetUEVer(FPackageFileVersion(VER_LATEST_ENGINE_UE4, EUnrealEngineObjectUE5Version::INITIAL_VERSION)); 210 | } 211 | 212 | // Serialize Names 213 | const int32 NameCount = File->AssetSummary->PackageSummary.NameCount; 214 | if (NameCount > 0) 215 | { 216 | NameMap.Reserve(NameCount); 217 | } 218 | 219 | FNameEntrySerialized NameEntry(ENAME_LinkerConstructor); 220 | Reader.Seek(File->AssetSummary->PackageSummary.NameOffset); 221 | 222 | for (int32 i = 0; i < NameCount; ++i) 223 | { 224 | Reader << NameEntry; 225 | NameMap.Emplace(FName(NameEntry).GetDisplayIndex()); 226 | 227 | if (NameEntry.bIsWide) 228 | { 229 | File->AssetSummary->Names.Add(MakeShared(NameEntry.WideName)); 230 | } 231 | else 232 | { 233 | File->AssetSummary->Names.Add(MakeShared(NameEntry.AnsiName)); 234 | } 235 | } 236 | File->AssetSummary->Names.Shrink(); 237 | 238 | // Serialize Export Table 239 | TArray Exports; 240 | Exports.AddZeroed(File->AssetSummary->PackageSummary.ExportCount); 241 | Reader.Seek(File->AssetSummary->PackageSummary.ExportOffset); 242 | for (int32 i = 0; i < File->AssetSummary->PackageSummary.ExportCount; ++i) 243 | { 244 | Reader << Exports[i]; 245 | 246 | FObjectExportPtrType ExportEx = MakeShared(); 247 | ExportEx->Index = i; 248 | ExportEx->ObjectName = Exports[i].ObjectName; 249 | ExportEx->SerialSize = Exports[i].SerialSize; 250 | ExportEx->SerialOffset = Exports[i].SerialOffset; 251 | ExportEx->bIsAsset = Exports[i].bIsAsset; 252 | ExportEx->bNotForClient = Exports[i].bNotForClient; 253 | ExportEx->bNotForServer = Exports[i].bNotForServer; 254 | 255 | File->AssetSummary->ObjectExports.Add(ExportEx); 256 | } 257 | File->AssetSummary->ObjectExports.Shrink(); 258 | 259 | // Serialize Import Table 260 | TArray Imports; 261 | Imports.AddZeroed(File->AssetSummary->PackageSummary.ImportCount); 262 | Reader.Seek(File->AssetSummary->PackageSummary.ImportOffset); 263 | for (int32 i = 0; i < File->AssetSummary->PackageSummary.ImportCount; ++i) 264 | { 265 | Reader << Imports[i]; 266 | 267 | FObjectImportPtrType ImportEx = MakeShared(); 268 | ImportEx->Index = i; 269 | ImportEx->ObjectName = Imports[i].ObjectName; 270 | ImportEx->ClassPackage = Imports[i].ClassPackage; 271 | ImportEx->ClassName = Imports[i].ClassName; 272 | 273 | File->AssetSummary->ObjectImports.Add(ImportEx); 274 | } 275 | File->AssetSummary->ObjectImports.Shrink(); 276 | 277 | FName MainObjectName = *FPaths::GetBaseFilename(File->Filename.ToString()); 278 | FName MainClassObjectName = *FString::Printf(TEXT("%s_C"), *MainObjectName.ToString()); 279 | FName MainObjectClassName = NAME_None; 280 | FName MainClassObjectClassName = NAME_None; 281 | FName AssetClass = NAME_None; 282 | 283 | // Parse Export Object Path 284 | for (int32 i = 0; i < File->AssetSummary->ObjectExports.Num(); ++i) 285 | { 286 | const FObjectExport& Export = Exports[i]; 287 | FObjectExportPtrType& ExportEx = File->AssetSummary->ObjectExports[i]; 288 | ExportEx->ObjectPath = *FindFullPath(Exports, i, TEXT(".")); 289 | 290 | ParseObjectName(Imports, Exports, Export.ClassIndex, ExportEx->ClassName); 291 | ParseObjectName(Imports, Exports, Export.TemplateIndex, ExportEx->TemplateObject); 292 | ParseObjectName(Imports, Exports, Export.SuperIndex, ExportEx->Super); 293 | 294 | FName ObjectName = *FPaths::GetBaseFilename(ExportEx->ObjectName.ToString()); 295 | if (ObjectName == MainObjectName) 296 | { 297 | MainObjectClassName = ExportEx->ClassName; 298 | } 299 | else if (ObjectName == MainClassObjectName) 300 | { 301 | MainClassObjectClassName = ExportEx->ClassName; 302 | } 303 | 304 | if (ExportEx->bIsAsset) 305 | { 306 | AssetClass = ExportEx->ClassName; 307 | } 308 | } 309 | 310 | if (MainObjectClassName == NAME_None && MainClassObjectClassName == NAME_None) 311 | { 312 | if (File->AssetSummary->ObjectExports.Num() == 1) 313 | { 314 | MainObjectClassName = File->AssetSummary->ObjectExports[0]->ClassName; 315 | } 316 | else if (!AssetClass.IsNone()) 317 | { 318 | MainObjectClassName = AssetClass; 319 | } 320 | } 321 | 322 | if (MainObjectClassName != NAME_None || MainClassObjectClassName != NAME_None) 323 | { 324 | FScopeLock ScopeLock(&Mutex); 325 | ClassMap.Add(File->PackagePath, MainObjectClassName != NAME_None ? MainObjectClassName : MainClassObjectClassName); 326 | } 327 | 328 | const bool bFillDependency = File->AssetSummary->DependencyList.Num() <= 0; 329 | for (int32 i = 0; i < File->AssetSummary->ObjectImports.Num(); ++i) 330 | { 331 | const FObjectImport& Import = Imports[i]; 332 | FObjectImportPtrType& ImportEx = File->AssetSummary->ObjectImports[i]; 333 | 334 | ImportEx->ObjectPath = *FindFullPath(Imports, i); 335 | 336 | if (bFillDependency && Import.ClassName == "Package" && !ImportEx->ObjectPath.ToString().StartsWith(TEXT("/Script"))) 337 | { 338 | FPackageInfoPtr Depends = MakeShared(); 339 | Depends->PackageName = ImportEx->ObjectPath; 340 | File->AssetSummary->DependencyList.Add(Depends); 341 | 342 | FScopeLock ScopeLock(&Mutex); 343 | DependsMap.Add(ImportEx->ObjectPath, File->PackagePath); 344 | } 345 | } 346 | File->AssetSummary->DependencyList.Shrink(); 347 | 348 | // Serialize Preload Dependency 349 | TArray PreloadDependencies; 350 | if (File->AssetSummary->PackageSummary.PreloadDependencyCount > 0) 351 | { 352 | PreloadDependencies.AddZeroed(File->AssetSummary->PackageSummary.PreloadDependencyCount); 353 | Reader.Seek(File->AssetSummary->PackageSummary.PreloadDependencyOffset); 354 | for (int32 i = 0; i < File->AssetSummary->PackageSummary.PreloadDependencyCount; ++i) 355 | { 356 | Reader << PreloadDependencies[i]; 357 | } 358 | 359 | // Parse Preload Dependency 360 | for (int32 i = 0; i < File->AssetSummary->ObjectExports.Num(); ++i) 361 | { 362 | const FObjectExport& Export = Exports[i]; 363 | FObjectExportPtrType& ExportEx = File->AssetSummary->ObjectExports[i]; 364 | 365 | if (Export.FirstExportDependency >= 0) 366 | { 367 | FName ObjectName; 368 | int32 RunningIndex = Export.FirstExportDependency; 369 | for (int32 Index = Export.SerializationBeforeSerializationDependencies; Index > 0; Index--) 370 | { 371 | FPackageIndex Dep = PreloadDependencies[RunningIndex++]; 372 | 373 | if (ParseObjectPath(File->AssetSummary, Dep, ObjectName)) 374 | { 375 | FPackageInfoPtr Depends = MakeShared(); 376 | Depends->PackageName = ObjectName; 377 | Depends->ExtraInfo = TEXT("Serialization Before Serialization"); 378 | 379 | ExportEx->DependencyList.Add(Depends); 380 | } 381 | } 382 | 383 | for (int32 Index = Export.CreateBeforeSerializationDependencies; Index > 0; Index--) 384 | { 385 | FPackageIndex Dep = PreloadDependencies[RunningIndex++]; 386 | 387 | if (ParseObjectPath(File->AssetSummary, Dep, ObjectName)) 388 | { 389 | FPackageInfoPtr Depends = MakeShared(); 390 | Depends->PackageName = ObjectName; 391 | Depends->ExtraInfo = TEXT("Create Before Serialization"); 392 | 393 | ExportEx->DependencyList.Add(Depends); 394 | } 395 | } 396 | 397 | for (int32 Index = Export.SerializationBeforeCreateDependencies; Index > 0; Index--) 398 | { 399 | FPackageIndex Dep = PreloadDependencies[RunningIndex++]; 400 | 401 | if (ParseObjectPath(File->AssetSummary, Dep, ObjectName)) 402 | { 403 | FPackageInfoPtr Depends = MakeShared(); 404 | Depends->PackageName = ObjectName; 405 | Depends->ExtraInfo = TEXT("Serialization Before Create"); 406 | 407 | ExportEx->DependencyList.Add(Depends); 408 | } 409 | } 410 | 411 | for (int32 Index = Export.CreateBeforeCreateDependencies; Index > 0; Index--) 412 | { 413 | FPackageIndex Dep = PreloadDependencies[RunningIndex++]; 414 | 415 | if (ParseObjectPath(File->AssetSummary, Dep, ObjectName)) 416 | { 417 | FPackageInfoPtr Depends = MakeShared(); 418 | Depends->PackageName = ObjectName; 419 | Depends->ExtraInfo = TEXT("Create Before Create"); 420 | 421 | ExportEx->DependencyList.Add(Depends); 422 | } 423 | } 424 | 425 | ExportEx->DependencyList.Shrink(); 426 | } 427 | } 428 | } 429 | } 430 | }, bForceSingleThread); 431 | 432 | // Parse depends 433 | ParallelFor(TotalCount, [this, &DependsMap](int32 InIndex) { 434 | if (StopTaskCounter.GetValue() > 0) 435 | { 436 | return; 437 | } 438 | 439 | FPakFileEntryPtr File = Files[InIndex]; 440 | if (!File->AssetSummary.IsValid()) 441 | { 442 | return; 443 | } 444 | if (File->AssetSummary->DependentList.Num() > 0) 445 | { 446 | return; 447 | } 448 | 449 | TArray Assets; 450 | DependsMap.MultiFind(File->PackagePath, Assets); 451 | 452 | for (const FName& Asset : Assets) 453 | { 454 | FPackageInfoPtr Depends = MakeShared(); 455 | Depends->PackageName = Asset; 456 | File->AssetSummary->DependentList.Add(Depends); 457 | } 458 | File->AssetSummary->DependentList.Shrink(); 459 | }, bForceSingleThread); 460 | 461 | OnParseFinish.ExecuteIfBound(StopTaskCounter.GetValue() > 0, ClassMap); 462 | 463 | StopTaskCounter.Reset(); 464 | 465 | UE_LOG(LogPakAnalyzer, Display, TEXT("Asset parse worker exits.")); 466 | 467 | return 0; 468 | } 469 | 470 | void FAssetParseThreadWorker::Stop() 471 | { 472 | StopTaskCounter.Increment(); 473 | EnsureCompletion(); 474 | StopTaskCounter.Reset(); 475 | } 476 | 477 | void FAssetParseThreadWorker::Exit() 478 | { 479 | 480 | } 481 | 482 | void FAssetParseThreadWorker::Shutdown() 483 | { 484 | Stop(); 485 | 486 | if (Thread) 487 | { 488 | UE_LOG(LogPakAnalyzer, Log, TEXT("Shutdown asset parse worker.")); 489 | 490 | delete Thread; 491 | Thread = nullptr; 492 | } 493 | } 494 | 495 | void FAssetParseThreadWorker::EnsureCompletion() 496 | { 497 | if (Thread) 498 | { 499 | Thread->WaitForCompletion(); 500 | } 501 | } 502 | 503 | void FAssetParseThreadWorker::StartParse(TArray& InFiles, TArray& InSummaries) 504 | { 505 | Shutdown(); 506 | 507 | UE_LOG(LogPakAnalyzer, Log, TEXT("Start asset parse worker, file count: %d."), InFiles.Num()); 508 | 509 | Files = MoveTemp(InFiles); 510 | Summaries = MoveTemp(InSummaries); 511 | 512 | Thread = FRunnableThread::Create(this, TEXT("AssetParseThreadWorker"), 0, EThreadPriority::TPri_Highest); 513 | } 514 | 515 | bool FAssetParseThreadWorker::ParseObjectName(const TArray& Imports, const TArray& Exports, FPackageIndex Index, FName& OutObjectName) 516 | { 517 | if (Index.IsImport()) 518 | { 519 | const int32 RawIndex = Index.ToImport(); 520 | if (Imports.IsValidIndex(RawIndex)) 521 | { 522 | OutObjectName = Imports[RawIndex].ObjectName; 523 | return true; 524 | } 525 | } 526 | else if (Index.IsExport()) 527 | { 528 | const int32 RawIndex = Index.ToExport(); 529 | if (Exports.IsValidIndex(RawIndex)) 530 | { 531 | OutObjectName = Exports[RawIndex].ObjectName; 532 | return true; 533 | } 534 | } 535 | 536 | return false; 537 | } 538 | 539 | bool FAssetParseThreadWorker::ParseObjectPath(FAssetSummaryPtr InSummary, FPackageIndex Index, FName& OutFullPath) 540 | { 541 | if (Index.IsImport()) 542 | { 543 | const int32 RawIndex = Index.ToImport(); 544 | if (InSummary->ObjectImports.IsValidIndex(RawIndex)) 545 | { 546 | OutFullPath = InSummary->ObjectImports[RawIndex]->ObjectPath; 547 | return true; 548 | } 549 | } 550 | else if (Index.IsExport()) 551 | { 552 | const int32 RawIndex = Index.ToExport(); 553 | if (InSummary->ObjectExports.IsValidIndex(RawIndex)) 554 | { 555 | OutFullPath = InSummary->ObjectExports[RawIndex]->ObjectPath; 556 | return true; 557 | } 558 | } 559 | 560 | return false; 561 | } 562 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/AssetParseThreadWorker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "HAL/Runnable.h" 5 | #include "Misc/AES.h" 6 | 7 | #include "Misc/Guid.h" 8 | #include "PakFileEntry.h" 9 | 10 | typedef TMap ClassTypeMap; 11 | DECLARE_DELEGATE_ThreeParams(FOnReadAssetContent, FPakFileEntryPtr /*InFile*/, bool& /*bOutSuccess*/, TArray& /*OutContent*/); 12 | DECLARE_DELEGATE_TwoParams(FOnParseFinish, bool/* bCancel*/, const ClassTypeMap&/* ClassMap*/); 13 | 14 | class FAssetParseThreadWorker : public FRunnable 15 | { 16 | public: 17 | FAssetParseThreadWorker(); 18 | ~FAssetParseThreadWorker(); 19 | 20 | virtual bool Init() override; 21 | virtual uint32 Run() override; 22 | virtual void Stop() override; 23 | virtual void Exit() override; 24 | 25 | void Shutdown(); 26 | void EnsureCompletion(); 27 | void StartParse(TArray& InFiles, TArray& InSummaries); 28 | 29 | FOnReadAssetContent OnReadAssetContent; 30 | FOnParseFinish OnParseFinish; 31 | 32 | protected: 33 | bool ParseObjectName(const TArray& Imports, const TArray& Exports, FPackageIndex Index, FName& OutObjectName); 34 | bool ParseObjectPath(FAssetSummaryPtr InSummary, FPackageIndex Index, FName& OutFullPath); 35 | 36 | protected: 37 | class FRunnableThread* Thread; 38 | FThreadSafeCounter StopTaskCounter; 39 | 40 | TArray Files; 41 | TArray Summaries; 42 | }; 43 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/BaseAnalyzer.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseAnalyzer.h" 2 | 3 | #include "AssetRegistry/AssetRegistryState.h" 4 | #include "Json.h" 5 | #include "Misc/Base64.h" 6 | #include "Misc/FileHelper.h" 7 | #include "Misc/Paths.h" 8 | #include "Serialization/ArrayReader.h" 9 | 10 | #include "CommonDefines.h" 11 | 12 | FBaseAnalyzer::FBaseAnalyzer() 13 | { 14 | 15 | } 16 | 17 | FBaseAnalyzer::~FBaseAnalyzer() 18 | { 19 | 20 | } 21 | 22 | bool FBaseAnalyzer::LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex) 23 | { 24 | Reset(); 25 | return false; 26 | } 27 | 28 | void FBaseAnalyzer::GetFiles(const FString& InFilterText, const TMap& InClassFilterMap, const TMap& InPakIndexFilter, TArray& OutFiles) const 29 | { 30 | FScopeLock Lock(const_cast(&CriticalSection)); 31 | 32 | for (FPakTreeEntryPtr PakTreeRoot : PakTreeRoots) 33 | { 34 | RetriveFiles(PakTreeRoot, InFilterText, InClassFilterMap, InPakIndexFilter, OutFiles); 35 | } 36 | } 37 | 38 | const TArray& FBaseAnalyzer::GetPakFileSumary() const 39 | { 40 | return PakFileSummaries; 41 | } 42 | 43 | const TArray& FBaseAnalyzer::GetPakTreeRootNode() const 44 | { 45 | return PakTreeRoots; 46 | } 47 | 48 | bool FBaseAnalyzer::LoadAssetRegistry(const FString& InRegristryPath) 49 | { 50 | FArrayReader ContentReader; 51 | if (!FFileHelper::LoadFileToArray(ContentReader, *InRegristryPath)) 52 | { 53 | return false; 54 | } 55 | 56 | if (!LoadAssetRegistry(ContentReader)) 57 | { 58 | return false; 59 | } 60 | 61 | AssetRegistryPath = FPaths::ConvertRelativePathToFull(InRegristryPath); 62 | 63 | for (FPakTreeEntryPtr TreeRoot : PakTreeRoots) 64 | { 65 | RefreshClassMap(TreeRoot, TreeRoot); 66 | RefreshPackageDependency(TreeRoot, TreeRoot); 67 | } 68 | 69 | return true; 70 | } 71 | 72 | bool FBaseAnalyzer::LoadAssetRegistry(FArrayReader& InData) 73 | { 74 | FAssetRegistrySerializationOptions LoadOptions; 75 | LoadOptions.bSerializeDependencies = true; 76 | LoadOptions.bSerializeSearchableNameDependencies = true; 77 | LoadOptions.bSerializeManageDependencies = true; 78 | LoadOptions.bSerializePackageData = false; 79 | 80 | TSharedPtr NewAssetRegistryState = MakeShared(); 81 | if (NewAssetRegistryState->Serialize(InData, LoadOptions)) 82 | { 83 | AssetRegistryState = NewAssetRegistryState; 84 | return true; 85 | } 86 | 87 | return false; 88 | } 89 | 90 | void FBaseAnalyzer::RefreshPackageDependency(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot) 91 | { 92 | if (!AssetRegistryState.IsValid()) 93 | { 94 | return; 95 | } 96 | 97 | for (auto& Pair : InRoot->ChildrenMap) 98 | { 99 | FPakTreeEntryPtr Child = Pair.Value; 100 | 101 | if (Child->bIsDirectory) 102 | { 103 | RefreshPackageDependency(InTreeRoot, Child); 104 | } 105 | else 106 | { 107 | TArray Dependencies; 108 | if (AssetRegistryState->GetDependencies(Child->PackagePath, Dependencies, UE::AssetRegistry::EDependencyCategory::All)) 109 | { 110 | if (!Child->AssetSummary.IsValid()) 111 | { 112 | Child->AssetSummary = MakeShared(); 113 | } 114 | else 115 | { 116 | Child->AssetSummary->DependencyList.Empty(); 117 | } 118 | 119 | for (const FAssetIdentifier& Identifier : Dependencies) 120 | { 121 | FPackageInfoPtr Depends = MakeShared(); 122 | Depends->PackageName = Identifier.PackageName; 123 | 124 | Child->AssetSummary->DependencyList.Add(Depends); 125 | } 126 | } 127 | 128 | TArray Dependents; 129 | if (AssetRegistryState->GetReferencers(Child->PackagePath, Dependents, UE::AssetRegistry::EDependencyCategory::All)) 130 | { 131 | if (!Child->AssetSummary.IsValid()) 132 | { 133 | Child->AssetSummary = MakeShared(); 134 | } 135 | else 136 | { 137 | Child->AssetSummary->DependentList.Empty(); 138 | } 139 | 140 | for (const FAssetIdentifier& Identifier : Dependents) 141 | { 142 | FPackageInfoPtr Depends = MakeShared(); 143 | Depends->PackageName = Identifier.PackageName; 144 | 145 | Child->AssetSummary->DependentList.Add(Depends); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | bool FBaseAnalyzer::ExportToJson(const FString& InOutputPath, const TArray& InFiles) 153 | { 154 | UE_LOG(LogPakAnalyzer, Log, TEXT("Export to json: %s."), *InOutputPath); 155 | 156 | TSharedRef RootObject = MakeShareable(new FJsonObject); 157 | RootObject->SetNumberField(TEXT("Exported File Count"), InFiles.Num()); 158 | 159 | TArray> FileObjects; 160 | 161 | int64 TotalSize = 0; 162 | int64 TotalCompressedSize = 0; 163 | 164 | TMap ExportedClassMap; 165 | 166 | for (const FPakFileEntryPtr It : InFiles) 167 | { 168 | const FPakEntry& PakEntry = It->PakEntry; 169 | 170 | TSharedRef FileObject = MakeShareable(new FJsonObject); 171 | 172 | FileObject->SetStringField(TEXT("Name"), It->Filename.ToString()); 173 | FileObject->SetStringField(TEXT("Path"), It->Path); 174 | FileObject->SetNumberField(TEXT("Offset"), PakEntry.Offset); 175 | FileObject->SetNumberField(TEXT("Size"), PakEntry.UncompressedSize); 176 | FileObject->SetNumberField(TEXT("Compressed Size"), PakEntry.Size); 177 | FileObject->SetNumberField(TEXT("Compressed Block Count"), PakEntry.CompressionBlocks.Num()); 178 | FileObject->SetNumberField(TEXT("Compressed Block Size"), PakEntry.CompressionBlockSize); 179 | FileObject->SetStringField(TEXT("SHA1"), BytesToHex(PakEntry.Hash, sizeof(PakEntry.Hash))); 180 | FileObject->SetStringField(TEXT("IsEncrypted"), PakEntry.IsEncrypted() ? TEXT("True") : TEXT("False")); 181 | FileObject->SetStringField(TEXT("Class"), It->Class.ToString()); 182 | FileObject->SetNumberField(TEXT("Dependency Count"), It->AssetSummary.IsValid() ? It->AssetSummary->DependencyList.Num() : 0); 183 | FileObject->SetNumberField(TEXT("Dependent Count"), It->AssetSummary.IsValid() ? It->AssetSummary->DependentList.Num() : 0); 184 | FileObject->SetStringField(TEXT("OwnerPak"), PakFileSummaries.IsValidIndex(It->OwnerPakIndex) ? FPaths::GetCleanFilename(PakFileSummaries[It->OwnerPakIndex]->PakFilePath) : TEXT("")); 185 | 186 | FileObjects.Add(MakeShareable(new FJsonValueObject(FileObject))); 187 | 188 | TotalSize += PakEntry.UncompressedSize; 189 | TotalCompressedSize += PakEntry.Size; 190 | 191 | FPakClassEntry* ClassEntry = ExportedClassMap.Find(It->Class); 192 | if (ClassEntry) 193 | { 194 | ClassEntry->FileCount += 1; 195 | ClassEntry->CompressedSize += PakEntry.Size; 196 | ClassEntry->Size += PakEntry.UncompressedSize; 197 | } 198 | else 199 | { 200 | ExportedClassMap.Add(It->Class, FPakClassEntry(It->Class, PakEntry.UncompressedSize, PakEntry.Size, 1)); 201 | } 202 | } 203 | 204 | RootObject->SetNumberField(TEXT("Exported Total Size"), TotalSize); 205 | RootObject->SetNumberField(TEXT("Exported Total Compressed Size"), TotalCompressedSize); 206 | 207 | ExportedClassMap.ValueSort( 208 | [](const FPakClassEntry& A, const FPakClassEntry& B) -> bool 209 | { 210 | return A.CompressedSize > B.CompressedSize; 211 | }); 212 | 213 | TArray> ClassObjects; 214 | for (const auto& Pair : ExportedClassMap) 215 | { 216 | const FPakClassEntry& ClassEntry = Pair.Value; 217 | 218 | TSharedRef ClassObject = MakeShareable(new FJsonObject); 219 | ClassObject->SetStringField(TEXT("Class"), ClassEntry.Class.ToString()); 220 | ClassObject->SetNumberField(TEXT("File Count"), ClassEntry.FileCount); 221 | ClassObject->SetNumberField(TEXT("Size"), ClassEntry.Size); 222 | ClassObject->SetNumberField(TEXT("Compressed Size"), ClassEntry.CompressedSize); 223 | ClassObject->SetNumberField(TEXT("Compressed Size Percent Of Exported"), TotalCompressedSize > 0 ? 100 * (float)ClassEntry.CompressedSize / TotalCompressedSize : 0.f); 224 | 225 | ClassObjects.Add(MakeShareable(new FJsonValueObject(ClassObject))); 226 | } 227 | 228 | RootObject->SetArrayField(TEXT("Group By Class"), ClassObjects); 229 | RootObject->SetArrayField(TEXT("Files"), FileObjects); 230 | 231 | bool bExportResult = false; 232 | 233 | FString FileContents; 234 | TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&FileContents); 235 | if (FJsonSerializer::Serialize(RootObject, JsonWriter)) 236 | { 237 | JsonWriter->Close(); 238 | bExportResult = FFileHelper::SaveStringToFile(FileContents, *InOutputPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); 239 | } 240 | 241 | UE_LOG(LogPakAnalyzer, Log, TEXT("Export to json: %s finished, file count: %d, result: %d."), *InOutputPath, InFiles.Num(), bExportResult); 242 | 243 | return bExportResult; 244 | } 245 | 246 | bool FBaseAnalyzer::ExportToCsv(const FString& InOutputPath, const TArray& InFiles) 247 | { 248 | UE_LOG(LogPakAnalyzer, Log, TEXT("Export to csv: %s."), *InOutputPath); 249 | 250 | TArray Lines; 251 | Lines.Empty(InFiles.Num() + 2); 252 | Lines.Add(TEXT("Id, Name, Path, Offset, Class, Size, Compressed Size, Compressed Block Count, Compressed Block Size, SHA1, IsEncrypted, Dependency Count, Dependent Count, OwnerPak")); 253 | 254 | int32 Index = 1; 255 | for (const FPakFileEntryPtr It : InFiles) 256 | { 257 | const FPakEntry& PakEntry = It->PakEntry; 258 | 259 | Lines.Add(FString::Printf(TEXT("%d, %s, %s, %lld, %s, %lld, %lld, %d, %d, %s, %s, %d, %d, %s"), 260 | Index, 261 | *It->Filename.ToString(), 262 | *It->Path, 263 | PakEntry.Offset, 264 | *It->Class.ToString(), 265 | PakEntry.UncompressedSize, 266 | PakEntry.Size, 267 | PakEntry.CompressionBlocks.Num(), 268 | PakEntry.CompressionBlockSize, 269 | *BytesToHex(PakEntry.Hash, sizeof(PakEntry.Hash)), 270 | PakEntry.IsEncrypted() ? TEXT("True") : TEXT("False"), 271 | It->AssetSummary.IsValid() ? It->AssetSummary->DependencyList.Num() : 0, 272 | It->AssetSummary.IsValid() ? It->AssetSummary->DependentList.Num() : 0, 273 | PakFileSummaries.IsValidIndex(It->OwnerPakIndex) ? *FPaths::GetCleanFilename(PakFileSummaries[It->OwnerPakIndex]->PakFilePath) : TEXT("")) 274 | ); 275 | ++Index; 276 | } 277 | 278 | const bool bExportResult = FFileHelper::SaveStringArrayToFile(Lines, *InOutputPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); 279 | 280 | UE_LOG(LogPakAnalyzer, Log, TEXT("Export to csv: %s finished, file count: %d, result: %d."), *InOutputPath, InFiles.Num(), bExportResult); 281 | 282 | return bExportResult; 283 | } 284 | 285 | FString FBaseAnalyzer::GetAssetRegistryPath() const 286 | { 287 | return AssetRegistryPath; 288 | } 289 | 290 | void FBaseAnalyzer::RefreshClassMap(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot) 291 | { 292 | InRoot->FileClassMap.Empty(); 293 | 294 | for (auto& Pair : InRoot->ChildrenMap) 295 | { 296 | FPakTreeEntryPtr Child = Pair.Value; 297 | 298 | if (Child->bIsDirectory) 299 | { 300 | RefreshClassMap(InTreeRoot, Child); 301 | for (auto& ClassPair : Child->FileClassMap) 302 | { 303 | InsertClassInfo(InTreeRoot, InRoot, ClassPair.Key, ClassPair.Value->FileCount, ClassPair.Value->Size, ClassPair.Value->CompressedSize); 304 | } 305 | } 306 | else 307 | { 308 | Child->Class = GetAssetClass(Child->Path, Child->PackagePath); 309 | InsertClassInfo(InTreeRoot, InRoot, Child->Class, 1, Child->Size, Child->CompressedSize); 310 | } 311 | } 312 | } 313 | 314 | void FBaseAnalyzer::RefreshTreeNode(FPakTreeEntryPtr InRoot) 315 | { 316 | for (auto& Pair : InRoot->ChildrenMap) 317 | { 318 | FPakTreeEntryPtr Child = Pair.Value; 319 | if (Child->bIsDirectory) 320 | { 321 | RefreshTreeNode(Child); 322 | } 323 | else 324 | { 325 | Child->FileCount = 1; 326 | Child->Size = Child->PakEntry.UncompressedSize; 327 | Child->CompressedSize = Child->PakEntry.Size; 328 | } 329 | 330 | InRoot->FileCount += Child->FileCount; 331 | InRoot->Size += Child->Size; 332 | InRoot->CompressedSize += Child->CompressedSize; 333 | } 334 | 335 | InRoot->ChildrenMap.ValueSort([](const FPakTreeEntryPtr& A, const FPakTreeEntryPtr& B) -> bool 336 | { 337 | if (A->bIsDirectory == B->bIsDirectory) 338 | { 339 | return A->Filename.LexicalLess(B->Filename); 340 | } 341 | 342 | return (int32)A->bIsDirectory > (int32)B->bIsDirectory; 343 | }); 344 | } 345 | 346 | void FBaseAnalyzer::RefreshTreeNodeSizePercent(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot) 347 | { 348 | for (auto& Pair : InRoot->ChildrenMap) 349 | { 350 | FPakTreeEntryPtr Child = Pair.Value; 351 | Child->CompressedSizePercentOfTotal = InTreeRoot->CompressedSize > 0 ? (float)Child->CompressedSize / InTreeRoot->CompressedSize : 0.f; 352 | Child->CompressedSizePercentOfParent = InRoot->CompressedSize > 0 ? (float)Child->CompressedSize / InRoot->CompressedSize : 0.f; 353 | 354 | if (Child->bIsDirectory) 355 | { 356 | RefreshTreeNodeSizePercent(InTreeRoot, Child); 357 | } 358 | } 359 | } 360 | 361 | void FBaseAnalyzer::RetriveFiles(FPakTreeEntryPtr InRoot, const FString& InFilterText, const TMap& InClassFilterMap, const TMap& InPakIndexFilter, TArray& OutFiles) const 362 | { 363 | for (auto& Pair : InRoot->ChildrenMap) 364 | { 365 | FPakTreeEntryPtr Child = Pair.Value; 366 | if (Child->bIsDirectory) 367 | { 368 | RetriveFiles(Child, InFilterText, InClassFilterMap, InPakIndexFilter, OutFiles); 369 | } 370 | else 371 | { 372 | const bool* bShow = InClassFilterMap.Find(Child->Class); 373 | const bool* bShowIndex = InPakIndexFilter.Find(Child->OwnerPakIndex); 374 | const bool bMatchClass = (InClassFilterMap.Num() <= 0 || (bShow && *bShow)); 375 | const bool bMatchIndex = (InPakIndexFilter.Num() <= 0) || (bShowIndex && *bShowIndex); 376 | 377 | if (bMatchClass && bMatchIndex && (InFilterText.IsEmpty() || /*Child->Filename.Contains(InFilterText) ||*/ Child->Path.Contains(InFilterText))) 378 | { 379 | OutFiles.Add(Child); 380 | } 381 | } 382 | } 383 | } 384 | 385 | void FBaseAnalyzer::RetriveUAssetFiles(FPakTreeEntryPtr InRoot, TArray& OutFiles) const 386 | { 387 | for (auto& Pair : InRoot->ChildrenMap) 388 | { 389 | FPakTreeEntryPtr Child = Pair.Value; 390 | if (Child->bIsDirectory) 391 | { 392 | RetriveUAssetFiles(Child, OutFiles); 393 | } 394 | else 395 | { 396 | if (Child->Filename.ToString().EndsWith(TEXT(".uasset")) || Child->Filename.ToString().EndsWith(TEXT(".umap"))) 397 | { 398 | OutFiles.Add(Child); 399 | } 400 | } 401 | } 402 | } 403 | 404 | void FBaseAnalyzer::InsertClassInfo(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot, FName InClassName, int32 InFileCount, int64 InSize, int64 InCompressedSize) 405 | { 406 | FPakClassEntryPtr* ClassEntryPtr = InRoot->FileClassMap.Find(InClassName); 407 | FPakClassEntryPtr ClassEntry = nullptr; 408 | 409 | if (!ClassEntryPtr) 410 | { 411 | ClassEntry = MakeShared(InClassName, InSize, InCompressedSize, InFileCount); 412 | InRoot->FileClassMap.Add(InClassName, ClassEntry); 413 | } 414 | else 415 | { 416 | ClassEntry = *ClassEntryPtr; 417 | ClassEntry->Class = InClassName; 418 | ClassEntry->FileCount += InFileCount; 419 | ClassEntry->Size += InSize; 420 | ClassEntry->CompressedSize += InCompressedSize; 421 | } 422 | 423 | ClassEntry->PercentOfTotal = InTreeRoot->CompressedSize > 0 ? (float)ClassEntry->CompressedSize / InTreeRoot->CompressedSize : 0.f; 424 | ClassEntry->PercentOfParent = InRoot->CompressedSize > 0 ? (float)ClassEntry->CompressedSize / InRoot->CompressedSize : 0.f; 425 | } 426 | 427 | FName FBaseAnalyzer::GetAssetClass(const FString& InFilename, FName InPackagePath) 428 | { 429 | bool bFoundClassInRegistry = false; 430 | FName AssetClass = *FPaths::GetExtension(InFilename); 431 | if (AssetRegistryState.IsValid()) 432 | { 433 | TArrayView AssetDataArray = AssetRegistryState->GetAssetsByPackageName(InPackagePath); 434 | if (AssetDataArray.Num() > 0) 435 | { 436 | bFoundClassInRegistry = true; 437 | AssetClass = AssetDataArray[0]->AssetClassPath.GetAssetName(); 438 | } 439 | } 440 | 441 | if (!bFoundClassInRegistry) 442 | { 443 | const FName* ClassName = DefaultClassMap.Find(InPackagePath); 444 | if (ClassName) 445 | { 446 | AssetClass = *ClassName; 447 | } 448 | } 449 | 450 | return AssetClass.IsNone() ? TEXT("Unknown") : AssetClass; 451 | } 452 | 453 | FName FBaseAnalyzer::GetPackagePath(const FString& InFilePath) 454 | { 455 | FString Left, Right; 456 | if (InFilePath.Split(TEXT("/Content/"), &Left, &Right)) 457 | { 458 | const FString Prefix = FPaths::GetPathLeaf(Left); 459 | const bool bNotUseGamePrefix = Prefix == TEXT("Engine") || InFilePath.Contains(TEXT("Plugin")); 460 | 461 | const FString RemoveFirstExtension = FPaths::SetExtension(Right, TEXT("")); 462 | const FString RemoveSecondExtension = FPaths::SetExtension(RemoveFirstExtension, TEXT("")); 463 | 464 | return *FString(bNotUseGamePrefix ? TEXT("/") / Prefix / RemoveSecondExtension : TEXT("/Game") / RemoveSecondExtension); 465 | } 466 | else 467 | { 468 | return *FString(TEXT("/") / FPaths::SetExtension(InFilePath, TEXT(""))); 469 | } 470 | } 471 | 472 | void FBaseAnalyzer::Reset() 473 | { 474 | for (FPakFileSumaryPtr Summary : PakFileSummaries) 475 | { 476 | Summary.Reset(); 477 | } 478 | 479 | for (FPakTreeEntryPtr Root : PakTreeRoots) 480 | { 481 | Root.Reset(); 482 | } 483 | 484 | PakFileSummaries.Empty(); 485 | PakTreeRoots.Empty(); 486 | 487 | AssetRegistryState.Reset(); 488 | 489 | AssetRegistryPath = TEXT(""); 490 | DefaultClassMap.Empty(); 491 | } 492 | 493 | FString FBaseAnalyzer::ResolveCompressionMethod(const FPakFileSumary& Summary, const FPakEntry* InPakEntry) const 494 | { 495 | if (InPakEntry->CompressionMethodIndex >= 0 && InPakEntry->CompressionMethodIndex < (uint32)Summary.PakInfo.CompressionMethods.Num()) 496 | { 497 | return Summary.PakInfo.CompressionMethods[InPakEntry->CompressionMethodIndex].ToString(); 498 | } 499 | else 500 | { 501 | return TEXT("Unknown"); 502 | } 503 | } 504 | 505 | FPakTreeEntryPtr FBaseAnalyzer::InsertFileToTree(FPakTreeEntryPtr InRoot, const FPakFileSumary& Summary, const FString& InFullPath, const FPakEntry& InPakEntry) 506 | { 507 | static const TCHAR* Delims[2] = { TEXT("\\"), TEXT("/") }; 508 | 509 | TArray PathItems; 510 | InFullPath.ParseIntoArray(PathItems, Delims, 2); 511 | 512 | if (PathItems.Num() <= 0) 513 | { 514 | return nullptr; 515 | } 516 | 517 | FString CurrentPath; 518 | FPakTreeEntryPtr Parent = InRoot; 519 | 520 | for (int32 i = 0; i < PathItems.Num(); ++i) 521 | { 522 | CurrentPath = CurrentPath / PathItems[i]; 523 | 524 | FPakTreeEntryPtr* Child = Parent->ChildrenMap.Find(*PathItems[i]); 525 | if (Child) 526 | { 527 | Parent = *Child; 528 | continue; 529 | } 530 | else 531 | { 532 | const bool bLastItem = (i == PathItems.Num() - 1); 533 | 534 | FPakTreeEntryPtr NewChild = MakeShared(*PathItems[i], CurrentPath, !bLastItem); 535 | if (bLastItem) 536 | { 537 | NewChild->PakEntry = InPakEntry; 538 | NewChild->CompressionMethod = *ResolveCompressionMethod(Summary, &InPakEntry); 539 | NewChild->PackagePath = GetPackagePath(CurrentPath); 540 | } 541 | 542 | Parent->ChildrenMap.Add(*PathItems[i], NewChild); 543 | Parent = NewChild; 544 | } 545 | } 546 | 547 | return Parent; 548 | } 549 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/BaseAnalyzer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "HAL/CriticalSection.h" 8 | #include "Misc/AES.h" 9 | #include "Misc/Guid.h" 10 | #include "Misc/SecureHash.h" 11 | 12 | #include "IPakAnalyzer.h" 13 | 14 | class FArrayReader; 15 | 16 | class FBaseAnalyzer : public IPakAnalyzer 17 | { 18 | public: 19 | FBaseAnalyzer(); 20 | virtual ~FBaseAnalyzer(); 21 | 22 | virtual bool LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex = 0) override; 23 | virtual void GetFiles(const FString& InFilterText, const TMap& InClassFilterMap, const TMap& InPakIndexFilter, TArray& OutFiles) const override; 24 | virtual const TArray& GetPakFileSumary() const override; 25 | virtual const TArray& GetPakTreeRootNode() const override; 26 | virtual bool LoadAssetRegistry(const FString& InRegristryPath) override; 27 | virtual bool ExportToJson(const FString& InOutputPath, const TArray& InFiles) override; 28 | virtual bool ExportToCsv(const FString& InOutputPath, const TArray& InFiles) override; 29 | virtual FString GetAssetRegistryPath() const override; 30 | virtual void ExtractFiles(const FString& InOutputPath, TArray& InFiles) override {} 31 | virtual void CancelExtract() override {} 32 | virtual void SetExtractThreadCount(int32 InThreadCount) override {} 33 | 34 | protected: 35 | virtual void Reset(); 36 | virtual FString ResolveCompressionMethod(const FPakFileSumary& Summary, const FPakEntry* InPakEntry) const; 37 | 38 | FPakTreeEntryPtr InsertFileToTree(FPakTreeEntryPtr InRoot, const FPakFileSumary& Summary, const FString& InFullPath, const FPakEntry& InPakEntry); 39 | bool LoadAssetRegistry(FArrayReader& InData); 40 | void RefreshPackageDependency(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot); 41 | void RefreshClassMap(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot); 42 | void RefreshTreeNode(FPakTreeEntryPtr InRoot); 43 | void RefreshTreeNodeSizePercent(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot); 44 | void RetriveFiles(FPakTreeEntryPtr InRoot, const FString& InFilterText, const TMap& InClassFilterMap, const TMap& InPakIndexFilter, TArray& OutFiles) const; 45 | void RetriveUAssetFiles(FPakTreeEntryPtr InRoot, TArray& OutFiles) const; 46 | void InsertClassInfo(FPakTreeEntryPtr InTreeRoot, FPakTreeEntryPtr InRoot, FName InClassName, int32 InFileCount, int64 InSize, int64 InCompressedSize); 47 | FName GetAssetClass(const FString& InFilename, const FName InPackagePath); 48 | FName GetPackagePath(const FString& InFilePath); 49 | 50 | protected: 51 | FCriticalSection CriticalSection; 52 | 53 | TArray PakFileSummaries; 54 | TArray PakTreeRoots; 55 | TMap DefaultClassMap; 56 | 57 | FString AssetRegistryPath; 58 | 59 | TSharedPtr AssetRegistryState; 60 | }; 61 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/ExtractThreadWorker.cpp: -------------------------------------------------------------------------------- 1 | #include "ExtractThreadWorker.h" 2 | 3 | #include "HAL/FileManager.h" 4 | #include "HAL/PlatformProcess.h" 5 | #include "HAL/RunnableThread.h" 6 | #include "IPlatformFilePak.h" 7 | #include "Misc/Compression.h" 8 | #include "Misc/Paths.h" 9 | #include "Serialization/Archive.h" 10 | 11 | #include "CommonDefines.h" 12 | 13 | FExtractThreadWorker::FExtractThreadWorker() 14 | : Thread(nullptr) 15 | { 16 | Guid = FGuid::NewGuid(); 17 | } 18 | 19 | FExtractThreadWorker::~FExtractThreadWorker() 20 | { 21 | Shutdown(); 22 | } 23 | 24 | bool FExtractThreadWorker::Init() 25 | { 26 | return true; 27 | } 28 | 29 | uint32 FExtractThreadWorker::Run() 30 | { 31 | const int64 BufferSize = 8 * 1024 * 1024; // 8MB buffer for extracting 32 | void* Buffer = FMemory::Malloc(BufferSize); 33 | uint8* PersistantCompressionBuffer = NULL; 34 | int64 CompressionBufferSize = 0; 35 | 36 | int32 CompleteCount = 0; 37 | int32 ErrorCount = 0; 38 | const int32 TotalCount = Files.Num(); 39 | 40 | FArchive* ReaderArchive = nullptr; 41 | int32 LastReaderIndex = -1; 42 | 43 | for (const FPakFileEntry& File : Files) 44 | { 45 | if (StopTaskCounter.GetValue() > 0) 46 | { 47 | break; 48 | } 49 | 50 | if (!Summaries.IsValidIndex(File.OwnerPakIndex)) 51 | { 52 | continue; 53 | } 54 | 55 | const FPakFileSumary& Summary = Summaries[File.OwnerPakIndex]; 56 | 57 | if (!ReaderArchive || File.OwnerPakIndex != LastReaderIndex) 58 | { 59 | if (ReaderArchive) 60 | { 61 | ReaderArchive->Close(); 62 | delete ReaderArchive; 63 | ReaderArchive = nullptr; 64 | } 65 | 66 | ReaderArchive = IFileManager::Get().CreateFileReader(*Summary.PakFilePath); 67 | LastReaderIndex = File.OwnerPakIndex; 68 | } 69 | 70 | if (!ReaderArchive) 71 | { 72 | ++ErrorCount; 73 | } 74 | else 75 | { 76 | const bool bHasRelativeCompressedChunkOffsets = Summary.PakInfo.Version >= FPakInfo::PakFile_Version_RelativeChunkOffsets; 77 | 78 | ReaderArchive->Seek(File.PakEntry.Offset); 79 | 80 | FPakEntry EntryInfo; 81 | EntryInfo.Serialize(*ReaderArchive, Summary.PakInfo.Version); 82 | if (File.PakEntry == EntryInfo) 83 | { 84 | const FString OutputFilePath = OutputPath / File.Path; 85 | const FString BasePath = FPaths::GetPath(OutputFilePath); 86 | if (!FPaths::DirectoryExists(BasePath)) 87 | { 88 | IFileManager::Get().MakeDirectory(*BasePath, true); 89 | } 90 | 91 | TUniquePtr FileHandle(IFileManager::Get().CreateFileWriter(*OutputFilePath)); 92 | if (FileHandle) 93 | { 94 | if (EntryInfo.CompressionMethodIndex == 0) 95 | { 96 | if (!BufferedCopyFile(*FileHandle, *ReaderArchive, File.PakEntry, Buffer, BufferSize, Summary.DecryptAESKey)) 97 | { 98 | // Add to failed list 99 | ++ErrorCount; 100 | UE_LOG(LogPakAnalyzer, Error, TEXT("Extract none-compressed file failed! File: %s"), *File.Path); 101 | } 102 | } 103 | else 104 | { 105 | if (!UncompressCopyFile(*FileHandle, *ReaderArchive, File.PakEntry, PersistantCompressionBuffer, CompressionBufferSize, Summary.DecryptAESKey, File.CompressionMethod, bHasRelativeCompressedChunkOffsets)) 106 | { 107 | // Add to failed list 108 | ++ErrorCount; 109 | UE_LOG(LogPakAnalyzer, Error, TEXT("Extract compressed file failed! File: %s"), *File.Path); 110 | } 111 | } 112 | } 113 | else 114 | { 115 | // open to write failed 116 | ++ErrorCount; 117 | UE_LOG(LogPakAnalyzer, Error, TEXT("Open local file to write failed! File: %s"), *OutputFilePath); 118 | } 119 | } 120 | else 121 | { 122 | // mismatch 123 | ++ErrorCount; 124 | UE_LOG(LogPakAnalyzer, Error, TEXT("Extract file failed! PakEntry mismatch! File: %s"), *File.Path); 125 | } 126 | } 127 | 128 | ++CompleteCount; 129 | 130 | OnUpdateExtractProgress.ExecuteIfBound(Guid, CompleteCount, ErrorCount, TotalCount); 131 | } 132 | 133 | FMemory::Free(Buffer); 134 | FMemory::Free(PersistantCompressionBuffer); 135 | 136 | if (ReaderArchive) 137 | { 138 | ReaderArchive->Close(); 139 | delete ReaderArchive; 140 | ReaderArchive = nullptr; 141 | } 142 | 143 | 144 | if (CompleteCount == TotalCount) 145 | { 146 | UE_LOG(LogPakAnalyzer, Log, TEXT("Extract worker: %s finished, file count: %d, error count: %d."), *Guid.ToString(), TotalCount, ErrorCount); 147 | } 148 | else 149 | { 150 | UE_LOG(LogPakAnalyzer, Warning, TEXT("Extract worker: %s interrupted, file count: %d, complete count: %d, error count: %d."), *Guid.ToString(), TotalCount, CompleteCount, ErrorCount); 151 | } 152 | 153 | StopTaskCounter.Reset(); 154 | return 0; 155 | } 156 | 157 | void FExtractThreadWorker::Stop() 158 | { 159 | StopTaskCounter.Increment(); 160 | EnsureCompletion(); 161 | StopTaskCounter.Reset(); 162 | } 163 | 164 | void FExtractThreadWorker::Exit() 165 | { 166 | 167 | } 168 | 169 | void FExtractThreadWorker::Shutdown() 170 | { 171 | Stop(); 172 | 173 | if (Thread) 174 | { 175 | UE_LOG(LogPakAnalyzer, Log, TEXT("Shutdown extract worker: %s."), *Guid.ToString()); 176 | 177 | delete Thread; 178 | Thread = nullptr; 179 | } 180 | } 181 | 182 | void FExtractThreadWorker::EnsureCompletion() 183 | { 184 | if (Thread) 185 | { 186 | Thread->WaitForCompletion(); 187 | } 188 | } 189 | 190 | void FExtractThreadWorker::StartExtract(const TArray& InSummaries, const FString& InOutputPath) 191 | { 192 | Shutdown(); 193 | 194 | UE_LOG(LogPakAnalyzer, Log, TEXT("Start extract worker: %s, output: %s, file count: %d."), *Guid.ToString(), *InOutputPath, Files.Num()); 195 | 196 | Summaries = InSummaries; 197 | OutputPath = InOutputPath; 198 | 199 | Thread = FRunnableThread::Create(this, TEXT("ExtractThreadWorker"), 0, EThreadPriority::TPri_Highest); 200 | } 201 | 202 | void FExtractThreadWorker::InitTaskFiles(TArray& InFiles) 203 | { 204 | Files = MoveTemp(InFiles); 205 | } 206 | 207 | FExtractThreadWorker::FOnUpdateExtractProgress& FExtractThreadWorker::GetOnUpdateExtractProgressDelegate() 208 | { 209 | return OnUpdateExtractProgress; 210 | } 211 | 212 | bool FExtractThreadWorker::BufferedCopyFile(FArchive& Dest, FArchive& Source, const FPakEntry& Entry, void* Buffer, int64 BufferSize, const FAES::FAESKey& InKey) 213 | { 214 | // Align down 215 | BufferSize = BufferSize & ~(FAES::AESBlockSize - 1); 216 | int64 RemainingSizeToCopy = Entry.Size; 217 | while (RemainingSizeToCopy > 0) 218 | { 219 | const int64 SizeToCopy = FMath::Min(BufferSize, RemainingSizeToCopy); 220 | // If file is encrypted so we need to account for padding 221 | int64 SizeToRead = Entry.IsEncrypted() ? Align(SizeToCopy, FAES::AESBlockSize) : SizeToCopy; 222 | 223 | Source.Serialize(Buffer, SizeToRead); 224 | if (Entry.IsEncrypted()) 225 | { 226 | FAES::DecryptData((uint8*)Buffer, SizeToRead, InKey); 227 | } 228 | Dest.Serialize(Buffer, SizeToCopy); 229 | RemainingSizeToCopy -= SizeToRead; 230 | } 231 | return true; 232 | } 233 | 234 | bool FExtractThreadWorker::UncompressCopyFile(FArchive& Dest, FArchive& Source, const FPakEntry& Entry, uint8*& PersistentBuffer, int64& BufferSize, const FAES::FAESKey& InKey, FName InCompressionMethod, bool bHasRelativeCompressedChunkOffsets) 235 | { 236 | if (Entry.UncompressedSize == 0) 237 | { 238 | return false; 239 | } 240 | 241 | // The compression block size depends on the bit window that the PAK file was originally created with. Since this isn't stored in the PAK file itself, 242 | // we can use FCompression::CompressMemoryBound as a guideline for the max expected size to avoid unncessary reallocations, but we need to make sure 243 | // that we check if the actual size is not actually greater (eg. UE-59278). 244 | int32 MaxCompressionBlockSize = FCompression::CompressMemoryBound(InCompressionMethod, Entry.CompressionBlockSize); 245 | for (const FPakCompressedBlock& Block : Entry.CompressionBlocks) 246 | { 247 | MaxCompressionBlockSize = FMath::Max(MaxCompressionBlockSize, Block.CompressedEnd - Block.CompressedStart); 248 | } 249 | 250 | int64 WorkingSize = Entry.CompressionBlockSize + MaxCompressionBlockSize; 251 | if (BufferSize < WorkingSize) 252 | { 253 | PersistentBuffer = (uint8*)FMemory::Realloc(PersistentBuffer, WorkingSize); 254 | BufferSize = WorkingSize; 255 | } 256 | 257 | uint8* UncompressedBuffer = PersistentBuffer + MaxCompressionBlockSize; 258 | 259 | for (uint32 BlockIndex = 0, BlockIndexNum = Entry.CompressionBlocks.Num(); BlockIndex < BlockIndexNum; ++BlockIndex) 260 | { 261 | uint32 CompressedBlockSize = Entry.CompressionBlocks[BlockIndex].CompressedEnd - Entry.CompressionBlocks[BlockIndex].CompressedStart; 262 | uint32 UncompressedBlockSize = (uint32)FMath::Min(Entry.UncompressedSize - Entry.CompressionBlockSize * BlockIndex, Entry.CompressionBlockSize); 263 | Source.Seek(Entry.CompressionBlocks[BlockIndex].CompressedStart + (bHasRelativeCompressedChunkOffsets ? Entry.Offset : 0)); 264 | uint32 SizeToRead = Entry.IsEncrypted() ? Align(CompressedBlockSize, FAES::AESBlockSize) : CompressedBlockSize; 265 | Source.Serialize(PersistentBuffer, SizeToRead); 266 | 267 | if (Entry.IsEncrypted()) 268 | { 269 | FAES::DecryptData(PersistentBuffer, SizeToRead, InKey); 270 | } 271 | 272 | if (!FCompression::UncompressMemory(InCompressionMethod, UncompressedBuffer, UncompressedBlockSize, PersistentBuffer, CompressedBlockSize)) 273 | { 274 | return false; 275 | } 276 | 277 | Dest.Serialize(UncompressedBuffer, UncompressedBlockSize); 278 | } 279 | 280 | return true; 281 | } 282 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/ExtractThreadWorker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "HAL/Runnable.h" 5 | #include "Misc/AES.h" 6 | 7 | #include "Misc/Guid.h" 8 | #include "PakFileEntry.h" 9 | 10 | class FExtractThreadWorker : public FRunnable 11 | { 12 | public: 13 | DECLARE_DELEGATE_FourParams(FOnUpdateExtractProgress, const FGuid& /*WorkerGuid*/, int32 /*CompleteCount*/, int32 /*ErrorCount*/, int32 /*TotalCount*/); 14 | 15 | public: 16 | FExtractThreadWorker(); 17 | ~FExtractThreadWorker(); 18 | 19 | virtual bool Init() override; 20 | virtual uint32 Run() override; 21 | virtual void Stop() override; 22 | virtual void Exit() override; 23 | 24 | void Shutdown(); 25 | void EnsureCompletion(); 26 | void StartExtract(const TArray& InSummaries, const FString& InOutputPath); 27 | void InitTaskFiles(TArray& InFiles); 28 | 29 | FOnUpdateExtractProgress& GetOnUpdateExtractProgressDelegate(); 30 | 31 | static bool BufferedCopyFile(FArchive& Dest, FArchive& Source, const FPakEntry& Entry, void* Buffer, int64 BufferSize, const FAES::FAESKey& InKey); 32 | static bool UncompressCopyFile(FArchive& Dest, FArchive& Source, const FPakEntry& Entry, uint8*& PersistentBuffer, int64& BufferSize, const FAES::FAESKey& InKey, FName InCompressionMethod, bool bHasRelativeCompressedChunkOffsets); 33 | 34 | protected: 35 | class FRunnableThread* Thread; 36 | FGuid Guid; 37 | FThreadSafeCounter StopTaskCounter; 38 | 39 | TArray Files; 40 | TArray Summaries; 41 | FString OutputPath; 42 | 43 | FOnUpdateExtractProgress OnUpdateExtractProgress; 44 | }; 45 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/FolderAnalyzer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "FolderAnalyzer.h" 4 | 5 | #include "AssetRegistry/ARFilter.h" 6 | #include "AssetRegistry/AssetData.h" 7 | #include "AssetRegistry/AssetRegistryState.h" 8 | #include "HAL/FileManager.h" 9 | #include "HAL/PlatformFile.h" 10 | #include "HAL/PlatformMisc.h" 11 | #include "HAL/UnrealMemory.h" 12 | #include "Json.h" 13 | #include "Misc/Base64.h" 14 | #include "Misc/Paths.h" 15 | #include "Misc/ScopeLock.h" 16 | #include "Serialization/Archive.h" 17 | #include "Serialization/MemoryWriter.h" 18 | 19 | #include "AssetParseThreadWorker.h" 20 | #include "CommonDefines.h" 21 | 22 | FFolderAnalyzer::FFolderAnalyzer() 23 | { 24 | Reset(); 25 | InitializeAssetParseWorker(); 26 | } 27 | 28 | FFolderAnalyzer::~FFolderAnalyzer() 29 | { 30 | ShutdownAssetParseWorker(); 31 | Reset(); 32 | } 33 | 34 | bool FFolderAnalyzer::LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex) 35 | { 36 | const FString InPakPath = InPakPaths.Num() > 0 ? InPakPaths[0] : TEXT(""); 37 | if (InPakPath.IsEmpty()) 38 | { 39 | UE_LOG(LogPakAnalyzer, Error, TEXT("Open folder failed! Folder path is empty!")); 40 | return false; 41 | } 42 | 43 | UE_LOG(LogPakAnalyzer, Log, TEXT("Start open folder: %s."), *InPakPath); 44 | 45 | IPlatformFile& PlatformFile = IPlatformFile::GetPlatformPhysical(); 46 | if (!PlatformFile.DirectoryExists(*InPakPath)) 47 | { 48 | FPakAnalyzerDelegates::OnLoadPakFailed.ExecuteIfBound(FString::Printf(TEXT("Open folder failed! Folder not exists! Path: %s."), *InPakPath)); 49 | UE_LOG(LogPakAnalyzer, Error, TEXT("Open folder failed! Folder not exists! Path: %s."), *InPakPath); 50 | return false; 51 | } 52 | 53 | Reset(); 54 | 55 | FPakFileSumaryPtr Summary = MakeShared(); 56 | PakFileSummaries.Add(Summary); 57 | 58 | // Save pak sumary 59 | Summary->MountPoint = InPakPath; 60 | FMemory::Memset(&Summary->PakInfo, 0, sizeof(Summary->PakInfo)); 61 | Summary->PakFilePath = InPakPath; 62 | 63 | ShutdownAssetParseWorker(); 64 | 65 | // Make tree root 66 | FPakTreeEntryPtr TreeRoot = MakeShared(*FPaths::GetCleanFilename(InPakPath), Summary->MountPoint, true); 67 | 68 | TArray FoundFiles; 69 | PlatformFile.FindFilesRecursively(FoundFiles, *InPakPath, TEXT("")); 70 | 71 | int64 TotalSize = 0; 72 | for (const FString& File : FoundFiles) 73 | { 74 | FPakEntry Entry; 75 | Entry.Offset = 0; 76 | Entry.UncompressedSize = PlatformFile.FileSize(*File); 77 | Entry.Size = Entry.UncompressedSize; 78 | 79 | TotalSize += Entry.UncompressedSize; 80 | 81 | FString RelativeFilename = File; 82 | RelativeFilename.RemoveFromStart(InPakPath); 83 | 84 | InsertFileToTree(TreeRoot, *Summary, RelativeFilename, Entry); 85 | 86 | if (File.Contains(TEXT("DevelopmentAssetRegistry.bin"))) 87 | { 88 | AssetRegistryPath = File; 89 | } 90 | } 91 | 92 | Summary->PakFileSize = TotalSize; 93 | Summary->FileCount = TreeRoot->FileCount; 94 | 95 | RefreshTreeNode(TreeRoot); 96 | RefreshTreeNodeSizePercent(TreeRoot, TreeRoot); 97 | 98 | PakTreeRoots.Add(TreeRoot); 99 | 100 | if (!AssetRegistryPath.IsEmpty()) 101 | { 102 | LoadAssetRegistry(AssetRegistryPath); 103 | } 104 | 105 | ParseAssetFile(TreeRoot); 106 | 107 | UE_LOG(LogPakAnalyzer, Log, TEXT("Finish load pak file: %s."), *InPakPath); 108 | 109 | FPakAnalyzerDelegates::OnPakLoadFinish.Broadcast(); 110 | 111 | return true; 112 | } 113 | 114 | void FFolderAnalyzer::ExtractFiles(const FString& InOutputPath, TArray& InFiles) 115 | { 116 | } 117 | 118 | void FFolderAnalyzer::CancelExtract() 119 | { 120 | } 121 | 122 | void FFolderAnalyzer::SetExtractThreadCount(int32 InThreadCount) 123 | { 124 | } 125 | 126 | void FFolderAnalyzer::ParseAssetFile(FPakTreeEntryPtr InRoot) 127 | { 128 | if (AssetParseWorker.IsValid()) 129 | { 130 | TArray UAssetFiles; 131 | RetriveUAssetFiles(InRoot, UAssetFiles); 132 | 133 | TArray Summaries = { *PakFileSummaries[0] }; 134 | AssetParseWorker->StartParse(UAssetFiles, Summaries); 135 | } 136 | } 137 | 138 | void FFolderAnalyzer::InitializeAssetParseWorker() 139 | { 140 | UE_LOG(LogPakAnalyzer, Log, TEXT("Initialize asset parse worker.")); 141 | 142 | if (!AssetParseWorker.IsValid()) 143 | { 144 | AssetParseWorker = MakeShared(); 145 | AssetParseWorker->OnReadAssetContent.BindRaw(this, &FFolderAnalyzer::OnReadAssetContent); 146 | AssetParseWorker->OnParseFinish.BindRaw(this, &FFolderAnalyzer::OnAssetParseFinish); 147 | } 148 | } 149 | 150 | void FFolderAnalyzer::ShutdownAssetParseWorker() 151 | { 152 | if (AssetParseWorker.IsValid()) 153 | { 154 | AssetParseWorker->Shutdown(); 155 | } 156 | } 157 | 158 | void FFolderAnalyzer::OnReadAssetContent(FPakFileEntryPtr InFile, bool& bOutSuccess, TArray& OutContent) 159 | { 160 | bOutSuccess = false; 161 | OutContent.Empty(); 162 | 163 | if (!InFile.IsValid()) 164 | { 165 | return; 166 | } 167 | 168 | const FString FilePath = PakFileSummaries[0]->MountPoint / InFile->Path; 169 | bOutSuccess = FFileHelper::LoadFileToArray(OutContent, *FilePath); 170 | } 171 | 172 | void FFolderAnalyzer::OnAssetParseFinish(bool bCancel, const TMap& ClassMap) 173 | { 174 | if (bCancel) 175 | { 176 | return; 177 | } 178 | 179 | DefaultClassMap = ClassMap; 180 | const bool bRefreshClass = ClassMap.Num() > 0; 181 | 182 | FFunctionGraphTask::CreateAndDispatchWhenReady([this, bRefreshClass]() 183 | { 184 | if (bRefreshClass) 185 | { 186 | for (const FPakTreeEntryPtr& PakTreeRoot : PakTreeRoots) 187 | { 188 | RefreshClassMap(PakTreeRoot, PakTreeRoot); 189 | } 190 | } 191 | 192 | FPakAnalyzerDelegates::OnAssetParseFinish.Broadcast(); 193 | }, 194 | TStatId(), nullptr, ENamedThreads::GameThread); 195 | } 196 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/FolderAnalyzer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "HAL/CriticalSection.h" 8 | #include "IPlatformFilePak.h" 9 | #include "Misc/AES.h" 10 | #include "Misc/Guid.h" 11 | #include "Misc/SecureHash.h" 12 | #include "Serialization/ArrayReader.h" 13 | 14 | #include "BaseAnalyzer.h" 15 | 16 | struct FPakEntry; 17 | 18 | class FFolderAnalyzer : public FBaseAnalyzer, public TSharedFromThis 19 | { 20 | public: 21 | FFolderAnalyzer(); 22 | virtual ~FFolderAnalyzer(); 23 | 24 | virtual bool LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex = 0) override; 25 | virtual void ExtractFiles(const FString& InOutputPath, TArray& InFiles) override; 26 | virtual void CancelExtract() override; 27 | virtual void SetExtractThreadCount(int32 InThreadCount) override; 28 | 29 | protected: 30 | void ParseAssetFile(FPakTreeEntryPtr InRoot); 31 | void InitializeAssetParseWorker(); 32 | void ShutdownAssetParseWorker(); 33 | void OnReadAssetContent(FPakFileEntryPtr InFile, bool& bOutSuccess, TArray& OutContent); 34 | void OnAssetParseFinish(bool bCancel, const TMap& ClassMap); 35 | 36 | protected: 37 | TSharedPtr AssetParseWorker; 38 | }; 39 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/IoStoreAnalyzer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "CommonDefines.h" 8 | 9 | #if ENABLE_IO_STORE_ANALYZER 10 | 11 | #include "Async/Async.h" 12 | #include "HAL/ThreadSafeBool.h" 13 | #include "IO/IoDispatcher.h" 14 | #include "Misc/AES.h" 15 | #include "Misc/Guid.h" 16 | #include "Misc/SecureHash.h" 17 | #include "IO/PackageId.h" 18 | #include "Templates/SharedPointer.h" 19 | 20 | #include "BaseAnalyzer.h" 21 | #include "IoStoreDefines.h" 22 | 23 | class FIoStoreAnalyzer : public FBaseAnalyzer, public TSharedFromThis 24 | { 25 | public: 26 | FIoStoreAnalyzer(); 27 | virtual ~FIoStoreAnalyzer(); 28 | 29 | virtual bool LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex = 0) override; 30 | virtual void ExtractFiles(const FString& InOutputPath, TArray& InFiles) override; 31 | virtual void CancelExtract() override; 32 | virtual void SetExtractThreadCount(int32 InThreadCount) override; 33 | virtual void Reset() override; 34 | 35 | protected: 36 | TSharedPtr CreateIoStoreReader(const FString& InPath, const FString& InDefaultAESKey, FString& OutDecryptKey); 37 | 38 | bool InitializeGlobalReader(const FString& InPakPath); 39 | bool InitializeReaders(const TArray& InPaks, const TArray& InDefaultAESKeys); 40 | bool PreLoadIoStore(const FString& InTocPath, const FString& InCasPath, const FString& InDefaultAESKey, TMap& OutKeys, FString& OutDecryptKey); 41 | bool TryDecryptIoStore(const FIoStoreTocResourceInfo& TocResource, const FIoOffsetAndLength& OffsetAndLength, const FIoStoreTocEntryMeta& Meta, const FString& InCasPath, const FString& InKey, FAES::FAESKey& OutAESKey); 42 | bool FillPackageInfo(const FIoStoreTocResourceInfo& TocResource, FStorePackageInfo& OutPackageInfo); 43 | void OnExtractFiles(); 44 | void StopExtract(); 45 | void UpdateExtractProgress(int32 InTotal, int32 InComplete, int32 InError); 46 | void ParseChunkInfo(const FIoChunkId& InChunkId, FPackageId& OutPackageId, EIoChunkType& OutChunkType); 47 | FName FindObjectName(FPackageObjectIndex Index, const FStorePackageInfo* PackageInfo); 48 | 49 | protected: 50 | TSharedPtr GlobalIoStoreReader; 51 | TArray GlobalNameMap; 52 | TArray StoreContainers; 53 | TArray PackageInfos; 54 | TMap FileToPackageIndex; 55 | 56 | TArray DefaultAESKeys; 57 | 58 | TArray PendingExtracePackages; 59 | TArray> ExtractThread; 60 | FThreadSafeBool IsStopExtract; 61 | FString ExtractOutputPath; 62 | 63 | TMap ScriptObjectByGlobalIdMap; 64 | TMap TocResources; 65 | TMap ExportByGlobalIdMap; 66 | }; 67 | 68 | #endif // ENABLE_IO_STORE_ANALYZER 69 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/IoStoreDefines.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "CommonDefines.h" 6 | 7 | #if ENABLE_IO_STORE_ANALYZER 8 | 9 | #include "IO/IoDispatcher.h" 10 | #include "IO/IoContainerHeader.h" 11 | #include "Serialization/AsyncLoading2.h" 12 | 13 | #include "IO/PackageStore.h" 14 | 15 | #include "PakFileEntry.h" 16 | 17 | /** 18 | * I/O store container format version 19 | */ 20 | enum class EIoStoreTocVersion : uint8 21 | { 22 | Invalid = 0, 23 | Initial, 24 | DirectoryIndex, 25 | PartitionSize, 26 | PerfectHash, 27 | PerfectHashWithOverflow, 28 | OnDemandMetaData, 29 | RemovedOnDemandMetaData, 30 | ReplaceIoChunkHashWithIoHash, 31 | LatestPlusOne, 32 | Latest = LatestPlusOne - 1 33 | }; 34 | 35 | 36 | /** 37 | * I/O Store TOC header. 38 | */ 39 | struct FIoStoreTocHeader 40 | { 41 | static constexpr char TocMagicImg[] = "-==--==--==--==-"; 42 | 43 | uint8 TocMagic[16]; 44 | uint8 Version; 45 | uint8 Reserved0 = 0; 46 | uint16 Reserved1 = 0; 47 | uint32 TocHeaderSize; 48 | uint32 TocEntryCount; 49 | uint32 TocCompressedBlockEntryCount; 50 | uint32 TocCompressedBlockEntrySize; // For sanity checking 51 | uint32 CompressionMethodNameCount; 52 | uint32 CompressionMethodNameLength; 53 | uint32 CompressionBlockSize; 54 | uint32 DirectoryIndexSize; 55 | uint32 PartitionCount = 0; 56 | FIoContainerId ContainerId; 57 | FGuid EncryptionKeyGuid; 58 | EIoContainerFlags ContainerFlags; 59 | uint8 Reserved3 = 0; 60 | uint16 Reserved4 = 0; 61 | uint32 TocChunkPerfectHashSeedsCount = 0; 62 | uint64 PartitionSize = 0; 63 | uint32 TocChunksWithoutPerfectHashCount = 0; 64 | uint32 Reserved7 = 0; 65 | uint64 Reserved8[5] = { 0 }; 66 | 67 | void MakeMagic() 68 | { 69 | FMemory::Memcpy(TocMagic, TocMagicImg, sizeof TocMagic); 70 | } 71 | 72 | bool CheckMagic() const 73 | { 74 | return FMemory::Memcmp(TocMagic, TocMagicImg, sizeof TocMagic) == 0; 75 | } 76 | }; 77 | 78 | /** 79 | * Compression block entry. 80 | */ 81 | struct FIoStoreTocCompressedBlockEntry 82 | { 83 | static constexpr uint32 OffsetBits = 40; 84 | static constexpr uint64 OffsetMask = (1ull << OffsetBits) - 1ull; 85 | static constexpr uint32 SizeBits = 24; 86 | static constexpr uint32 SizeMask = (1 << SizeBits) - 1; 87 | static constexpr uint32 SizeShift = 8; 88 | 89 | inline uint64 GetOffset() const 90 | { 91 | const uint64* Offset = reinterpret_cast(Data); 92 | return *Offset & OffsetMask; 93 | } 94 | 95 | inline void SetOffset(uint64 InOffset) 96 | { 97 | uint64* Offset = reinterpret_cast(Data); 98 | *Offset = InOffset & OffsetMask; 99 | } 100 | 101 | inline uint32 GetCompressedSize() const 102 | { 103 | const uint32* Size = reinterpret_cast(Data) + 1; 104 | return (*Size >> SizeShift) & SizeMask; 105 | } 106 | 107 | inline void SetCompressedSize(uint32 InSize) 108 | { 109 | uint32* Size = reinterpret_cast(Data) + 1; 110 | *Size |= (uint32(InSize) << SizeShift); 111 | } 112 | 113 | inline uint32 GetUncompressedSize() const 114 | { 115 | const uint32* UncompressedSize = reinterpret_cast(Data) + 2; 116 | return *UncompressedSize & SizeMask; 117 | } 118 | 119 | inline void SetUncompressedSize(uint32 InSize) 120 | { 121 | uint32* UncompressedSize = reinterpret_cast(Data) + 2; 122 | *UncompressedSize = InSize & SizeMask; 123 | } 124 | 125 | inline uint8 GetCompressionMethodIndex() const 126 | { 127 | const uint32* Index = reinterpret_cast(Data) + 2; 128 | return static_cast(*Index >> SizeBits); 129 | } 130 | 131 | inline void SetCompressionMethodIndex(uint8 InIndex) 132 | { 133 | uint32* Index = reinterpret_cast(Data) + 2; 134 | *Index |= uint32(InIndex) << SizeBits; 135 | } 136 | 137 | private: 138 | /* 5 bytes offset, 3 bytes for size / uncompressed size and 1 byte for compresseion method. */ 139 | uint8 Data[5 + 3 + 3 + 1]; 140 | }; 141 | 142 | /** 143 | * Combined offset and length. 144 | */ 145 | struct FIoOffsetAndLength 146 | { 147 | public: 148 | inline uint64 GetOffset() const 149 | { 150 | return OffsetAndLength[4] 151 | | (uint64(OffsetAndLength[3]) << 8) 152 | | (uint64(OffsetAndLength[2]) << 16) 153 | | (uint64(OffsetAndLength[1]) << 24) 154 | | (uint64(OffsetAndLength[0]) << 32) 155 | ; 156 | } 157 | 158 | inline uint64 GetLength() const 159 | { 160 | return OffsetAndLength[9] 161 | | (uint64(OffsetAndLength[8]) << 8) 162 | | (uint64(OffsetAndLength[7]) << 16) 163 | | (uint64(OffsetAndLength[6]) << 24) 164 | | (uint64(OffsetAndLength[5]) << 32) 165 | ; 166 | } 167 | 168 | inline void SetOffset(uint64 Offset) 169 | { 170 | OffsetAndLength[0] = uint8(Offset >> 32); 171 | OffsetAndLength[1] = uint8(Offset >> 24); 172 | OffsetAndLength[2] = uint8(Offset >> 16); 173 | OffsetAndLength[3] = uint8(Offset >> 8); 174 | OffsetAndLength[4] = uint8(Offset >> 0); 175 | } 176 | 177 | inline void SetLength(uint64 Length) 178 | { 179 | OffsetAndLength[5] = uint8(Length >> 32); 180 | OffsetAndLength[6] = uint8(Length >> 24); 181 | OffsetAndLength[7] = uint8(Length >> 16); 182 | OffsetAndLength[8] = uint8(Length >> 8); 183 | OffsetAndLength[9] = uint8(Length >> 0); 184 | } 185 | 186 | private: 187 | // We use 5 bytes for offset and size, this is enough to represent 188 | // an offset and size of 1PB 189 | uint8 OffsetAndLength[5 + 5]; 190 | }; 191 | 192 | enum class FIoStoreTocEntryMetaFlags : uint8 193 | { 194 | None, 195 | Compressed = (1 << 0), 196 | MemoryMapped = (1 << 1) 197 | }; 198 | 199 | ENUM_CLASS_FLAGS(FIoStoreTocEntryMetaFlags); 200 | 201 | 202 | /** 203 | * TOC entry meta data 204 | */ 205 | struct FIoStoreTocEntryMeta 206 | { 207 | // Source data hash (i.e. not the on disk data) 208 | FIoHash ChunkHash; 209 | FIoStoreTocEntryMetaFlags Flags = FIoStoreTocEntryMetaFlags::None; 210 | uint8 Pad[3] = { 0 }; 211 | }; 212 | 213 | 214 | /** 215 | * Container TOC data. 216 | */ 217 | struct FIoStoreTocResourceInfo 218 | { 219 | enum { CompressionMethodNameLen = 32 }; 220 | 221 | FIoStoreTocHeader Header; 222 | int64 TocFileSize = 0; 223 | 224 | TArray ChunkIds; 225 | TArray ChunkOffsetLengths; // Added field 226 | TArray ChunkPerfectHashSeeds; // Used for Perfect Hash lookups 227 | TArray ChunkIndicesWithoutPerfectHash; // Fallback lookup 228 | 229 | TArray CompressionBlocks; 230 | TArray CompressionMethods; 231 | 232 | FSHAHash SignatureHash; // Added field 233 | TArray ChunkBlockSignatures; 234 | 235 | TArray DirectoryIndexBuffer; 236 | TArray ChunkMetas; 237 | 238 | static int32 HashChunkIdWithSeed(const FIoChunkId& ChunkId, const TArray& Seeds) 239 | { 240 | if (Seeds.Num() == 0) 241 | { 242 | return INDEX_NONE; 243 | } 244 | 245 | uint32 HashValue = GetTypeHash(ChunkId); // Hash the chunk ID 246 | return HashValue % Seeds.Num(); // Get index using modulus 247 | } 248 | }; 249 | 250 | 251 | struct FScriptObjectDesc 252 | { 253 | FName Name; 254 | FName FullName; 255 | FPackageObjectIndex GlobalImportIndex; 256 | FPackageObjectIndex OuterIndex; 257 | }; 258 | 259 | struct FPackageStoreExportEntry 260 | { 261 | FPackageStoreExportEntry(const FFilePackageStoreEntry& InEntry) 262 | //: ExportBundlesSize(InEntry.ExportBundlesSize) 263 | // : ExportCount(InEntry.ExportCount) 264 | // , ExportBundleCount(InEntry.ExportBundleCount) 265 | //, LoadOrder(InEntry.LoadOrder) 266 | //, Pad(InEntry.Pad) 267 | { 268 | DependencyPackages.SetNum(InEntry.ImportedPackages.Num()); 269 | for (uint32 i = 0; i < InEntry.ImportedPackages.Num(); ++i) 270 | { 271 | DependencyPackages[i] = InEntry.ImportedPackages[i]; 272 | } 273 | } 274 | 275 | //uint64 ExportBundlesSize = 0; 276 | int32 ExportCount = 0; 277 | int32 ExportBundleCount = 0; 278 | //uint32 LoadOrder = 0; 279 | //uint32 Pad = 0; 280 | TArray DependencyPackages; 281 | }; 282 | 283 | struct FContainerInfo 284 | { 285 | FIoContainerId Id; 286 | FGuid EncryptionKeyGuid; 287 | bool bCompressed; 288 | bool bSigned; 289 | bool bEncrypted; 290 | bool bIndexed; 291 | 292 | FPakFileSumary Summary; 293 | TSharedPtr Reader; 294 | 295 | TMap StoreEntryMap; 296 | }; 297 | 298 | struct FIoStoreExport 299 | { 300 | FName Name; 301 | FName FullName; 302 | uint64 PublicExportHash = 0; 303 | FPackageObjectIndex OuterIndex; 304 | FPackageObjectIndex ClassIndex; 305 | FPackageObjectIndex SuperIndex; 306 | FPackageObjectIndex TemplateIndex; 307 | //FPackageObjectIndex GlobalImportIndex; 308 | uint64 SerialOffset = 0; 309 | uint64 SerialSize = 0; 310 | EObjectFlags ObjectFlags = EObjectFlags::RF_NoFlags; 311 | EExportFilterFlags FilterFlags = EExportFilterFlags::None; 312 | struct FStorePackageInfo* Package = nullptr; 313 | }; 314 | 315 | struct FIoStoreImport 316 | { 317 | FName Name; 318 | FPackageObjectIndex GlobalImportIndex; 319 | }; 320 | 321 | struct FStorePackageInfo 322 | { 323 | FName PackageName; 324 | FPackageId PackageId; 325 | int32 ContainerIndex; 326 | FIoChunkId ChunkId; 327 | EIoChunkType ChunkType; 328 | FIoStoreTocChunkInfo ChunkInfo; 329 | uint32 CookedHeaderSize = 0; 330 | uint32 SerializeSize = 0; 331 | uint32 CompressionBlockSize = 0; 332 | uint32 CompressionBlockCount = 0; 333 | FName CompressionMethod; 334 | FString ChunkHash; 335 | FName Extension; 336 | TArray Imports; 337 | TArray Exports; 338 | FAssetSummaryPtr AssetSummary; 339 | TArray DependencyPackages; 340 | FName DefaultClassName; 341 | TArray ImportedPublicExportHashes; 342 | 343 | inline bool operator ==(const FStorePackageInfo& Rhs) const 344 | { 345 | return ChunkId == Rhs.ChunkId; 346 | } 347 | 348 | inline bool operator !=(const FStorePackageInfo& Rhs) const 349 | { 350 | return !(*this == Rhs); 351 | } 352 | }; 353 | 354 | #endif // ENABLE_IO_STORE_ANALYZER 355 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/PakAnalyzer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "HAL/CriticalSection.h" 8 | #include "IPlatformFilePak.h" 9 | #include "Misc/AES.h" 10 | #include "Misc/Guid.h" 11 | #include "Misc/SecureHash.h" 12 | #include "Serialization/ArrayReader.h" 13 | 14 | #include "BaseAnalyzer.h" 15 | 16 | struct FPakEntry; 17 | 18 | class FPakAnalyzer : public FBaseAnalyzer, public TSharedFromThis 19 | { 20 | public: 21 | FPakAnalyzer(); 22 | virtual ~FPakAnalyzer(); 23 | 24 | virtual bool LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex = 0) override; 25 | virtual void ExtractFiles(const FString& InOutputPath, TArray& InFiles) override; 26 | virtual void CancelExtract() override; 27 | virtual void SetExtractThreadCount(int32 InThreadCount) override; 28 | virtual void Reset() override; 29 | 30 | protected: 31 | FPakTreeEntryPtr LoadPakFile(const FString& InPakPath, const FString& InDefaultAESKey = TEXT("")); 32 | bool LoadAssetRegistryFromPak(FPakFile* InPakFile, FPakFileEntryPtr InPakFileEntry, const FAES::FAESKey& DecryptAESKey); 33 | 34 | bool PreLoadPak(const FString& InPakPath, const FString& InDefaultAESKey, FString& OutDecryptKey); 35 | bool ValidateEncryptionKey(TArray& IndexData, const FSHAHash& InExpectedHash, const FAES::FAESKey& InAESKey); 36 | bool TryDecryptPak(FArchive* InReader, const FPakInfo& InPakInfo, const FString& InKey, bool bShowWarning); 37 | 38 | void InitializeExtractWorker(); 39 | void ShutdownAllExtractWorker(); 40 | 41 | void ParseAssetFile(); 42 | void InitializeAssetParseWorker(); 43 | void ShutdownAssetParseWorker(); 44 | void OnAssetParseFinish(bool bCancel, const TMap& ClassMap); 45 | 46 | // Extract progress 47 | void OnUpdateExtractProgress(const FGuid& WorkerGuid, int32 CompleteCount, int32 ErrorCount, int32 TotalCount); 48 | 49 | struct FExtractProgress 50 | { 51 | int32 CompleteCount; 52 | int32 ErrorCount; 53 | int32 TotalCount; 54 | }; 55 | 56 | void ResetProgress(); 57 | 58 | protected: 59 | int32 ExtractWorkerCount; 60 | TArray> ExtractWorkers; 61 | TMap ExtractWorkerProgresses; 62 | 63 | TArray DefaultAESKeys; 64 | 65 | TSharedPtr AssetParseWorker; 66 | }; 67 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/PakAnalyzerModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "PakAnalyzerModule.h" 4 | 5 | #include "HAL/PlatformFile.h" 6 | #include "Misc/Paths.h" 7 | #include "Modules/ModuleManager.h" 8 | 9 | #include "BaseAnalyzer.h" 10 | #include "CommonDefines.h" 11 | #include "FolderAnalyzer.h" 12 | #include "PakAnalyzer.h" 13 | #include "IoStoreAnalyzer.h" 14 | #include "UnrealAnalyzer.h" 15 | 16 | DEFINE_LOG_CATEGORY(LogPakAnalyzer); 17 | 18 | FPakAnalyzerDelegates::FOnGetAESKey FPakAnalyzerDelegates::OnGetAESKey; 19 | FPakAnalyzerDelegates::FOnLoadPakFailed FPakAnalyzerDelegates::OnLoadPakFailed; 20 | FPakAnalyzerDelegates::FOnUpdateExtractProgress FPakAnalyzerDelegates::OnUpdateExtractProgress; 21 | FPakAnalyzerDelegates::FOnExtractStart FPakAnalyzerDelegates::OnExtractStart; 22 | FPakAnalyzerDelegates::FOnAssetParseFinish FPakAnalyzerDelegates::OnAssetParseFinish; 23 | FPakAnalyzerDelegates::FOnPakLoadFinish FPakAnalyzerDelegates::OnPakLoadFinish; 24 | 25 | class FPakAnalyzerModule : public IPakAnalyzerModule 26 | { 27 | public: 28 | // Begin IModuleInterface 29 | virtual void StartupModule() override; 30 | virtual void ShutdownModule() override; 31 | 32 | virtual void InitializeAnalyzerBackend(const FString& InFullPath) override; 33 | virtual IPakAnalyzer* GetPakAnalyzer() override; 34 | 35 | protected: 36 | TSharedPtr AnalyzerInstance; 37 | }; 38 | 39 | IMPLEMENT_MODULE(FPakAnalyzerModule, PakAnalyzer); 40 | 41 | void FPakAnalyzerModule::StartupModule() 42 | { 43 | AnalyzerInstance = MakeShared(); 44 | } 45 | 46 | void FPakAnalyzerModule::ShutdownModule() 47 | { 48 | AnalyzerInstance.Reset(); 49 | } 50 | 51 | void FPakAnalyzerModule::InitializeAnalyzerBackend(const FString& InFullPath) 52 | { 53 | IPlatformFile& PlatformFile = IPlatformFile::GetPlatformPhysical(); 54 | 55 | const FString Extension = FPaths::GetExtension(InFullPath); 56 | 57 | if (PlatformFile.DirectoryExists(*InFullPath)) 58 | { 59 | AnalyzerInstance = MakeShared(); 60 | } 61 | else 62 | { 63 | AnalyzerInstance = MakeShared(); 64 | } 65 | } 66 | 67 | IPakAnalyzer* FPakAnalyzerModule::GetPakAnalyzer() 68 | { 69 | return AnalyzerInstance.IsValid() ? AnalyzerInstance.Get() : nullptr; 70 | } 71 | -------------------------------------------------------------------------------- /PakAnalyzer/Private/PakFileEntry.cpp: -------------------------------------------------------------------------------- 1 | #include "PakFileEntry.h" -------------------------------------------------------------------------------- /PakAnalyzer/Private/UnrealAnalyzer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealAnalyzer.h" 4 | 5 | FUnrealAnalyzer::FUnrealAnalyzer() 6 | { 7 | IoStoreAnalyzer = MakeShared(); 8 | PakAnalyzer = MakeShared(); 9 | 10 | Reset(); 11 | } 12 | 13 | FUnrealAnalyzer::~FUnrealAnalyzer() 14 | { 15 | Reset(); 16 | 17 | IoStoreAnalyzer.Reset(); 18 | PakAnalyzer.Reset(); 19 | } 20 | 21 | bool FUnrealAnalyzer::LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex) 22 | { 23 | bool bResult = true; 24 | 25 | if (PakAnalyzer) 26 | { 27 | bResult &= PakAnalyzer->LoadPakFiles(InPakPaths, InDefaultAESKeys); 28 | PakTreeRoots = PakAnalyzer->GetPakTreeRootNode(); 29 | PakFileSummaries = PakAnalyzer->GetPakFileSumary(); 30 | } 31 | 32 | if (IoStoreAnalyzer) 33 | { 34 | bResult &= IoStoreAnalyzer->LoadPakFiles(InPakPaths, InDefaultAESKeys, PakAnalyzer ? PakAnalyzer->GetPakFileSumary().Num() : 0); 35 | PakTreeRoots += IoStoreAnalyzer->GetPakTreeRootNode(); 36 | PakFileSummaries += IoStoreAnalyzer->GetPakFileSumary(); 37 | } 38 | 39 | FPakAnalyzerDelegates::OnPakLoadFinish.Broadcast(); 40 | 41 | return bResult; 42 | } 43 | 44 | void FUnrealAnalyzer::ExtractFiles(const FString& InOutputPath, TArray& InFiles) 45 | { 46 | // if (IoStoreAnalyzer) 47 | // { 48 | // IoStoreAnalyzer->ExtractFiles(InOutputPath, InFiles); 49 | // } 50 | 51 | if (PakAnalyzer) 52 | { 53 | PakAnalyzer->ExtractFiles(InOutputPath, InFiles); 54 | } 55 | } 56 | 57 | void FUnrealAnalyzer::CancelExtract() 58 | { 59 | // if (IoStoreAnalyzer) 60 | // { 61 | // IoStoreAnalyzer->CancelExtract(); 62 | // } 63 | 64 | if (PakAnalyzer) 65 | { 66 | PakAnalyzer->CancelExtract(); 67 | } 68 | } 69 | 70 | void FUnrealAnalyzer::SetExtractThreadCount(int32 InThreadCount) 71 | { 72 | // if (IoStoreAnalyzer) 73 | // { 74 | // IoStoreAnalyzer->SetExtractThreadCount(InThreadCount); 75 | // } 76 | 77 | if (PakAnalyzer) 78 | { 79 | PakAnalyzer->SetExtractThreadCount(InThreadCount); 80 | } 81 | } 82 | 83 | void FUnrealAnalyzer::Reset() 84 | { 85 | if (IoStoreAnalyzer) 86 | { 87 | IoStoreAnalyzer->Reset(); 88 | } 89 | 90 | if (PakAnalyzer) 91 | { 92 | PakAnalyzer->Reset(); 93 | } 94 | } -------------------------------------------------------------------------------- /PakAnalyzer/Private/UnrealAnalyzer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "HAL/CriticalSection.h" 8 | #include "IPlatformFilePak.h" 9 | #include "Misc/AES.h" 10 | #include "Misc/Guid.h" 11 | #include "Misc/SecureHash.h" 12 | #include "Serialization/ArrayReader.h" 13 | 14 | #include "BaseAnalyzer.h" 15 | #include "IoStoreAnalyzer.h" 16 | #include "PakAnalyzer.h" 17 | 18 | struct FPakEntry; 19 | 20 | class FUnrealAnalyzer : public FBaseAnalyzer, public TSharedFromThis 21 | { 22 | public: 23 | FUnrealAnalyzer(); 24 | virtual ~FUnrealAnalyzer(); 25 | 26 | virtual bool LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex = 0) override; 27 | virtual void ExtractFiles(const FString& InOutputPath, TArray& InFiles) override; 28 | virtual void CancelExtract() override; 29 | virtual void SetExtractThreadCount(int32 InThreadCount) override; 30 | virtual void Reset() override; 31 | 32 | protected: 33 | TSharedPtr PakAnalyzer; 34 | TSharedPtr IoStoreAnalyzer; 35 | }; 36 | -------------------------------------------------------------------------------- /PakAnalyzer/Public/CommonDefines.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Logging/LogMacros.h" 7 | 8 | #define ENABLE_IO_STORE_ANALYZER 1 9 | 10 | DECLARE_LOG_CATEGORY_EXTERN(LogPakAnalyzer, Log, All); 11 | 12 | class FPakAnalyzerDelegates 13 | { 14 | public: 15 | DECLARE_DELEGATE_RetVal_ThreeParams(FString, FOnGetAESKey, const FString&/* PakPath*/, const FGuid&/* Guid*/, bool& /*bCancel*/); 16 | DECLARE_DELEGATE_OneParam(FOnLoadPakFailed, const FString&) 17 | DECLARE_DELEGATE_ThreeParams(FOnUpdateExtractProgress, int32 /*CompleteCount*/, int32 /*ErrorCount*/, int32 /*TotalCount*/); 18 | DECLARE_DELEGATE(FOnExtractStart); 19 | DECLARE_MULTICAST_DELEGATE(FOnAssetParseFinish); 20 | DECLARE_MULTICAST_DELEGATE(FOnPakLoadFinish); 21 | 22 | public: 23 | static FOnGetAESKey OnGetAESKey; 24 | static FOnLoadPakFailed OnLoadPakFailed; 25 | static FOnUpdateExtractProgress OnUpdateExtractProgress; 26 | static FOnExtractStart OnExtractStart; 27 | static FOnAssetParseFinish OnAssetParseFinish; 28 | static FOnPakLoadFinish OnPakLoadFinish; 29 | }; 30 | -------------------------------------------------------------------------------- /PakAnalyzer/Public/IPakAnalyzer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "PakFileEntry.h" 8 | 9 | struct FPakEntry; 10 | 11 | static const int32 DEFAULT_EXTRACT_THREAD_COUNT = 4; 12 | 13 | class IPakAnalyzer 14 | { 15 | public: 16 | IPakAnalyzer() {} 17 | virtual ~IPakAnalyzer() {} 18 | 19 | virtual bool LoadPakFiles(const TArray& InPakPaths, const TArray& InDefaultAESKeys, int32 ContainerStartIndex = 0) = 0; 20 | virtual void GetFiles(const FString& InFilterText, const TMap& InClassFilterMap, const TMap& InPakIndexFilter, TArray& OutFiles) const = 0; 21 | virtual const TArray& GetPakFileSumary() const = 0; 22 | virtual const TArray& GetPakTreeRootNode() const = 0; 23 | virtual void ExtractFiles(const FString& InOutputPath, TArray& InFiles) = 0; 24 | virtual void CancelExtract() = 0; 25 | virtual bool ExportToJson(const FString& InOutputPath, const TArray& InFiles) = 0; 26 | virtual bool ExportToCsv(const FString& InOutputPath, const TArray& InFiles) = 0; 27 | virtual void SetExtractThreadCount(int32 InThreadCount) = 0; 28 | virtual bool LoadAssetRegistry(const FString& InRegristryPath) = 0; 29 | virtual FString GetAssetRegistryPath() const = 0; 30 | }; 31 | -------------------------------------------------------------------------------- /PakAnalyzer/Public/PakAnalyzerModule.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | #include "IPakAnalyzer.h" 9 | 10 | /** 11 | * The public interface to this module 12 | */ 13 | class IPakAnalyzerModule : public IModuleInterface 14 | { 15 | public: 16 | /** 17 | * Singleton-like access to this module's interface. This is just for convenience! 18 | * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. 19 | * 20 | * @return Returns singleton instance, loading the module on demand if needed 21 | */ 22 | static inline IPakAnalyzerModule& Get() 23 | { 24 | return FModuleManager::LoadModuleChecked("PakAnalyzer"); 25 | } 26 | 27 | /** 28 | * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. 29 | * 30 | * @return True if the module is loaded and ready to use 31 | */ 32 | static inline bool IsAvailable() 33 | { 34 | return FModuleManager::Get().IsModuleLoaded("PakAnalyzer"); 35 | } 36 | 37 | virtual void InitializeAnalyzerBackend(const FString& InFullPath) = 0; 38 | 39 | virtual IPakAnalyzer* GetPakAnalyzer() = 0; 40 | }; 41 | -------------------------------------------------------------------------------- /PakAnalyzer/Public/PakFileEntry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "IPlatformFilePak.h" 5 | #include "Misc/AES.h" 6 | #include "UObject/ObjectResource.h" 7 | #include "UObject/PackageFileSummary.h" 8 | 9 | typedef TSharedPtr FPakClassEntryPtr; 10 | typedef TSharedPtr FNamePtrType; 11 | typedef TSharedPtr FObjectExportPtrType; 12 | typedef TSharedPtr FObjectImportPtrType; 13 | typedef TSharedPtr FAssetSummaryPtr; 14 | typedef TSharedPtr FPakFileEntryPtr; 15 | typedef TSharedPtr FPakTreeEntryPtr; 16 | typedef TSharedPtr FPackageInfoPtr; 17 | typedef TSharedPtr FPakFileSumaryPtr; 18 | 19 | struct FPakClassEntry 20 | { 21 | FPakClassEntry(FName InClassName, int64 InSize, int64 InCompressedSize, int32 InFileCount) 22 | : Class(InClassName) 23 | , Size(InSize) 24 | , CompressedSize(InCompressedSize) 25 | , FileCount(InFileCount) 26 | , PercentOfTotal(1.f) 27 | , PercentOfParent(1.f) 28 | { 29 | 30 | } 31 | 32 | FName Class; 33 | int64 Size; 34 | int64 CompressedSize; 35 | int32 FileCount; 36 | float PercentOfTotal; 37 | float PercentOfParent; 38 | }; 39 | 40 | struct FObjectExportEx 41 | { 42 | FName ObjectName; 43 | uint64 SerialSize = 0; 44 | uint64 SerialOffset = 0; 45 | bool bIsAsset = false; 46 | bool bNotForClient = false; 47 | bool bNotForServer = false; 48 | int32 Index = 0; 49 | FName ObjectPath; 50 | FName ClassName; 51 | FName TemplateObject; 52 | FName Super; 53 | TArray DependencyList; 54 | }; 55 | 56 | struct FObjectImportEx 57 | { 58 | int32 Index = 0; 59 | FName ClassPackage; 60 | FName ClassName; 61 | FName ObjectName; 62 | FName ObjectPath; 63 | }; 64 | 65 | struct FPackageInfo 66 | { 67 | FName PackageName; 68 | FName ExtraInfo; 69 | }; 70 | 71 | struct FAssetSummary 72 | { 73 | FPackageFileSummary PackageSummary; 74 | TArray Names; 75 | TArray ObjectExports; 76 | TArray ObjectImports; 77 | TArray DependencyList; // this asset depends on 78 | TArray DependentList; // assets depends on this 79 | }; 80 | 81 | struct FPakFileEntry : TSharedFromThis 82 | { 83 | FPakFileEntry(FName InFilename, const FString& InPath) 84 | : Filename(InFilename) 85 | , Path(InPath) 86 | { 87 | 88 | } 89 | 90 | FPakEntry PakEntry; 91 | FName Filename; 92 | FString Path; 93 | FName CompressionMethod; 94 | FName Class; 95 | FName PackagePath; 96 | FAssetSummaryPtr AssetSummary; 97 | int16 OwnerPakIndex = 0; 98 | }; 99 | 100 | struct FPakTreeEntry : public FPakFileEntry 101 | { 102 | int32 FileCount; 103 | int64 Size; 104 | int64 CompressedSize; 105 | float CompressedSizePercentOfTotal; 106 | float CompressedSizePercentOfParent; 107 | 108 | bool bIsDirectory; 109 | TMap> ChildrenMap; 110 | TMap FileClassMap; 111 | 112 | FPakTreeEntry(FName InFilename, const FString& InPath, bool bInIsDirectory) 113 | : FPakFileEntry(InFilename, InPath) 114 | , FileCount(0) 115 | , Size(0) 116 | , CompressedSize(0) 117 | , CompressedSizePercentOfTotal(1.f) 118 | , CompressedSizePercentOfParent(1.f) 119 | , bIsDirectory(bInIsDirectory) 120 | { 121 | 122 | } 123 | }; 124 | 125 | struct FPakFileSumary 126 | { 127 | FPakInfo PakInfo; 128 | FString MountPoint; 129 | FString PakFilePath; 130 | int64 PakFileSize = 0; 131 | FString CompressionMethods; 132 | FString DecryptAESKeyStr; 133 | FAES::FAESKey DecryptAESKey; 134 | int32 FileCount = 0; 135 | }; 136 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | # UnrealPakViewer ## 2 | 3 | Allows the user to view the contents of .pak files. Currently supports the following features: 4 | 5 | * Tree view and list view to view files in the pak/ucas 6 | * Open multiple pak/ucas files at the same time 7 | * Search, filter, sort in list view 8 | * View the specific content and composition information of the UAsset file 9 | * Display the size of each folder, file, and file type as a percentage 10 | * Multi-threaded decompression of Pak files 11 | * Loading the AssetRegistry.bin resource registry 12 | 13 | ## Function ## 14 | 15 | ### Opening the .pak file ### 16 | 17 | ![OpenPak.png](Resources/Images/OpenPak.png) 18 | 19 | Directly drag the Pak file to the UnrealPakViewer window to open it, if the Pak file is encrypted, a password input box will pop up 20 | 21 | ![AESKey.png](Resources/Images/AESKey.png) 22 | 23 | After entering the Base64 format of the corresponding AES key, you can open the Pak file 24 | 25 | ### Viewing Pak file summary information ### 26 | 27 | ![PakSummary.png](Resources/Images/PakSummary.png) 28 | 29 | ### Loading the resource registry ### 30 | 31 | ![LoadAssetRegistry.png](Resources/Images/LoadAssetRegistry.png) 32 | 33 | After cooking completes, it will dump resource registry information in *Saved/Cooked/[Platform]/[Project]/Metadata/DevelopmentAssetRegistry.bin*, which contains resource type, reference relationship and other information. 34 | 35 | It can be loaded through Load Asset Registry to analyze the size ratio information of each resource type 36 | 37 | ### File tree view ### 38 | 39 | ![TreeView.png](Resources/Images/TreeView.png) 40 | 41 | Lists the directories and files contained in the Pak in a tree structure, as well as the ratio of the directory size to the total size 42 | 43 | #### After selecting a directory, you can view the details of the directory on the right #### 44 | 45 | ![FolderDetail.png](Resources/Images/FolderDetail.png) 46 | 47 | And the proportion information of each file type in the directory (you will need to load the AssetRegistry.bin registry) 48 | 49 | ![FolderDetailClass.png](Resources/Images/FolderDetailClass.png) 50 | 51 | #### After selecting the file, you can view the file details on the right #### 52 | 53 | ![FileDetail.png](Resources/Images/FileDetail.png) 54 | 55 | Compared to the contents, there is more information 56 | 57 | If you select a .uasset or .umap file, you can also view the serialization information inside the file 58 | 59 | ![AssetSummary.png](Resources/Images/AssetSummary.png) 60 | 61 | ![ImportObjects.png](Resources/Images/ImportObjects.png) 62 | 63 | ![ExportObjects.png](Resources/Images/ExportObjects.png) 64 | 65 | ![ObjectDependencies.png](Resources/Images/ObjectDependencies.png) 66 | 67 | ![DependencyPackages.png](Resources/Images/DependencyPackages.png) 68 | 69 | ![DependentPackages.png](Resources/Images/DependentPackages.png) 70 | 71 | ![Names.png](Resources/Images/Names.png) 72 | 73 | #### Right-click menu #### 74 | 75 | ![TreeViewContext.png](Resources/Images/TreeViewContext.png) 76 | 77 | ### File list view ### 78 | 79 | ![ListView.png](Resources/Images/ListView.png) 80 | 81 | The file list view displays the file information in the Pak in a table format, and supports sorting by clicking on the column headings 82 | 83 | #### Type filtering #### 84 | 85 | ![ClassFilter.png](Resources/Images/ClassFilter.png) 86 | 87 | Filter files in the list by type 88 | 89 | #### File name filtering #### 90 | 91 | ![NameFilter.png](Resources/Images/NameFilter.png) 92 | 93 | Filter the files in the list by file name 94 | 95 | #### Right-click menu #### 96 | 97 | ![ListViewContext.png](Resources/Images/ListViewContext.png) 98 | 99 | ## Compiling ## 100 | 101 | Clone the code to the *Engine\Source\Programs* directory, open the solution and compile it 102 | 103 | * The versions of the engine that have been compiled 104 | * 4.24 105 | * 4.25 106 | * 4.26 107 | * 4.27 108 | * 4.28 109 | 110 | ## TODO ## 111 | 112 | * commandline application 113 | * Pak compare visiualize 114 | * resource preview 115 | * resource load heat map 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnrealPakViewer ## 2 | 3 | Find the english translation of the README [here](README-EN.md) 4 | 5 | 可视化查看 Pak 文件内容,支持以下特性 6 | 7 | * 支持树形视图和列表视图查看 pak/ucas 中的文件 8 | * 支持同时打开多个 pak/ucas 文件 9 | * 列表视图中支持搜索,过滤,排序 10 | * 支持查看 UAsset 文件的具体内容组成信息 11 | * 以百分比显示各个文件夹,文件,文件类型的大小占比 12 | * 支持多线程解压 Pak 文件 13 | * 支持加载 AssetRegistry.bin 资源注册表 14 | 15 | ## 功能 ## 16 | 17 | ### 打开 Pak 文件 ### 18 | 19 | ![OpenPak.png](Resources/Images/OpenPak.png) 20 | 21 | 或者直接拖动 Pak 文件到 UnrealPakViewer 窗口中即可打开,如果 Pak 文件是加密的的,则会弹出密码输入框 22 | 23 | ![AESKey.png](Resources/Images/AESKey.png) 24 | 25 | 输入对应的 AES 密钥的 Base64 格式后即可打开 Pak 文件 26 | 27 | ### 查看 Pak 文件摘要信息 ### 28 | 29 | ![PakSummary.png](Resources/Images/PakSummary.png) 30 | 31 | * Mount Point: 默认挂载点 32 | * Pak Version: Pak 文件版本号 33 | * Pak File Size: Pak 文件大小 34 | * Pak File Count: Pak 内打包的文件数量 35 | * Pak Header Size: Pak 文件头大小 36 | * Pak Index Size: Pak 索引区大小 37 | * Pak Index Hash: Pak 索引区哈希值 38 | * Pak Index Is Encrypted: Pak 索引区是否加密 39 | * Pak File Content Size: Pak 文件内容区大小 40 | * Pak Compression Methods: Pak 中文件所使用的压缩算法 41 | 42 | ### 加载资源注册表 ### 43 | 44 | ![LoadAssetRegistry.png](Resources/Images/LoadAssetRegistry.png) 45 | 46 | Cook 完成后都会在 *Saved/Cooked/[Platform]/[Project]/Metadata/DevelopmentAssetRegistry.bin* 路径生成一份资源注册表信息,里面包含资源类型,引用关系等信息,可以通过 Load Asset Registry 加载进来分析各个资源类型的大小占比信息 47 | 48 | ### 文件树视图 ### 49 | 50 | ![TreeView.png](Resources/Images/TreeView.png) 51 | 52 | 以树形结构列出 Pak 内包含的目录和文件,以及目录大小占总大小的比例信息 53 | 54 | #### 选中某个目录后可以在右边查看该目录详情 #### 55 | 56 | ![FolderDetail.png](Resources/Images/FolderDetail.png) 57 | 58 | * Name: 目录名称 59 | * Path: 目录路径 60 | * Size: 目录解压后大小 61 | * Compressed Size: 目录压缩后大小 62 | * Compressed Size Of Total: 目录压缩大小占总 Pak 大小的比例 63 | * Compressed Size Of Parent: 目录压缩大小占上级目录的比例 64 | * File Count: 目录中文件数量 65 | 66 | 以及该目录中各个文件类型的占比信息(需要加载 AssetRegistry.bin 注册表) 67 | 68 | ![FolderDetailClass.png](Resources/Images/FolderDetailClass.png) 69 | 70 | #### 选中文件后可以在右边查看该文件详情 #### 71 | 72 | ![FileDetail.png](Resources/Images/FileDetail.png) 73 | 74 | 相比目录,额外多一些信息 75 | 76 | * Class: 文件类型 77 | * Offset: 文件在 Pak 中序列化的起始位置 78 | * Compression Block Count: 压缩分块数量 79 | * Compression Block Size: 压缩分块大小 80 | * Compression Method: 文件压缩算法 81 | * SHA1: 文件哈希值 82 | * IsEncrypted: 文件是否加密 83 | 84 | 如果选中的是 .uasset 或者 .umap 文件,还能查看该文件内部的序列化信息 85 | 86 | ![AssetSummary.png](Resources/Images/AssetSummary.png) 87 | 88 | * Guid: 该资源的 Guid 89 | * bUnversioned: 序列化时是否带引擎版本信息 90 | * FileVersionUE4: 文件格式版本号 91 | * FileVersionLicenseeUE4: 文件格式版本号(授权) 92 | * TotalHeaderSize: uasset 的文件头大小 93 | * PackageFlags: uasset 包标志 94 | * ImportObjects: 导入表信息(引用的外部对象信息) 95 | ![ImportObjects.png](Resources/Images/ImportObjects.png) 96 | * Index: 对象在导入表中的索引 97 | * ObjectName: 对象名称 98 | * ClassName: 对象类型 99 | * ClassPackage: 对象类型所在的包 100 | * FullPath: 对象完整路径 101 | * ExportObjects: 导出表信息(该资源内部有哪些对象),导出表的序列化大小即是对应的 .uexp 文件大小,可点击 SerialSize 和 SerialOffset 列进行排序 102 | ![ExportObjects.png](Resources/Images/ExportObjects.png) 103 | * Index: 对象在导出表中的索引 104 | * ObjectName: 对象名称 105 | * ClassName: 对象类型 106 | * SerialSize: 对象的序列化大小 107 | * SerialOffset: 对象的序列化偏移 108 | * FullPath: 对象在包内的完整路径 109 | * bIsAsset: 110 | * bNotForClient: 非客户端资源 111 | * bNotForServer: 非服务器资源 112 | * TemplateObject: 该对象的模板对象 113 | * Super: 父类对象 114 | * Dependencies: 该对象引用的具体对象信息,冒号前为引用类型,后为引用的具体对象路径 115 | ![ObjectDependencies.png](Resources/Images/ObjectDependencies.png) 116 | * Serialization Before Serialization: 序列化前要完成序列化的对象 117 | * Create Before Serialization: 序列化前要完成创建的对象 118 | * Serialization Before Create: 创建前要完成序列化的对象 119 | * Create Before Create: 创建前要完成创建的对象 120 | * Dependency packages: 该资源依赖的资源 121 | ![DependencyPackages.png](Resources/Images/DependencyPackages.png) 122 | * Dependent packages: 依赖该资源的资源,这个是在当前 Pak 内搜索,如果分包了则结果可能会缺失 123 | ![DependentPackages.png](Resources/Images/DependentPackages.png) 124 | * Names: 该资源相关联的所有 FName 信息 125 | ![Names.png](Resources/Images/Names.png) 126 | 127 | #### 右键菜单 #### 128 | 129 | ![TreeViewContext.png](Resources/Images/TreeViewContext.png) 130 | 131 | 右键目录或者文件,会弹出右键菜单,功能如下 132 | 133 | * Extract: 解压选中的目录或者文件 134 | * Export To Json: 将选中的目录或文件信息导出到 Json 文件 135 | * Export To Json: 将选中的目录或文件信息导出到 Csv 文件 136 | * Show In File View: 如果选中的是文件,则跳转到该文件在文件列表中的对应位置 137 | 138 | ### 文件列表视图 ### 139 | 140 | ![ListView.png](Resources/Images/ListView.png) 141 | 142 | 文件列表视图以表格形式显示 Pak 中的文件信息,支持点击列标题进行排序 143 | 144 | #### 类型过滤 #### 145 | 146 | ![ClassFilter.png](Resources/Images/ClassFilter.png) 147 | 148 | 按类型过滤列表中的文件 149 | 150 | #### 文件名过滤 #### 151 | 152 | ![NameFilter.png](Resources/Images/NameFilter.png) 153 | 154 | 按文件名过滤列表中的文件 155 | 156 | #### 右键菜单 #### 157 | 158 | ![ListViewContext.png](Resources/Images/ListViewContext.png) 159 | 160 | 选中文件后右键弹出右键菜单,功能如下 161 | 162 | * Extract: 解压选中文件 163 | * Export To Json: 将选中的文件信息导出到 Json 文件 164 | * Export To Json: 将选中的文件信息导出到 Csv 文件 165 | * Show In Tree View: 如果选中单文件,则跳转到树形视图中 166 | * Copy Columns: 复制对应的列信息到剪贴板中 167 | * View Column: 隐藏/显示列 168 | * Show All Columns: 显示所有列 169 | 170 | ## 编译 ## 171 | 172 | 将代码克隆到 *Engine\Source\Programs* 目录下,重新生成解决方案编译即可 173 | 174 | * 已编译通过的引擎版本 175 | * 4.24 176 | * 4.25 177 | * 4.26 178 | * 4.27 179 | * 4.28 180 | 181 | ## TODO ## 182 | 183 | * commandline application 184 | * Pak compare visiualize 185 | * resource preview 186 | * resource load heat map 187 | -------------------------------------------------------------------------------- /Resources/Icons/Edit/icon_Edit_Copy_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/Edit/icon_Edit_Copy_40x.png -------------------------------------------------------------------------------- /Resources/Icons/FolderClosed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/FolderClosed.png -------------------------------------------------------------------------------- /Resources/Icons/FolderOpen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/FolderOpen.png -------------------------------------------------------------------------------- /Resources/Icons/icon_Blueprint_Find_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_Blueprint_Find_40px.png -------------------------------------------------------------------------------- /Resources/Icons/icon_ContentBrowser_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_ContentBrowser_40x.png -------------------------------------------------------------------------------- /Resources/Icons/icon_FontEd_Export_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_FontEd_Export_40x.png -------------------------------------------------------------------------------- /Resources/Icons/icon_Persona_Skeleton_Tree_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_Persona_Skeleton_Tree_16x.png -------------------------------------------------------------------------------- /Resources/Icons/icon_StaticMeshEd_Collision_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_StaticMeshEd_Collision_40x.png -------------------------------------------------------------------------------- /Resources/Icons/icon_file_open_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_file_open_40x.png -------------------------------------------------------------------------------- /Resources/Icons/icon_levels_visible_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_levels_visible_40x.png -------------------------------------------------------------------------------- /Resources/Icons/icon_source_control_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Icons/icon_source_control_40x.png -------------------------------------------------------------------------------- /Resources/Images/AESKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/AESKey.png -------------------------------------------------------------------------------- /Resources/Images/AssetSummary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/AssetSummary.png -------------------------------------------------------------------------------- /Resources/Images/ClassFilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/ClassFilter.png -------------------------------------------------------------------------------- /Resources/Images/DependencyPackages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/DependencyPackages.png -------------------------------------------------------------------------------- /Resources/Images/DependentPackages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/DependentPackages.png -------------------------------------------------------------------------------- /Resources/Images/ExportObjects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/ExportObjects.png -------------------------------------------------------------------------------- /Resources/Images/FileDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/FileDetail.png -------------------------------------------------------------------------------- /Resources/Images/FolderDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/FolderDetail.png -------------------------------------------------------------------------------- /Resources/Images/FolderDetailClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/FolderDetailClass.png -------------------------------------------------------------------------------- /Resources/Images/ImportObjects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/ImportObjects.png -------------------------------------------------------------------------------- /Resources/Images/ListView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/ListView.png -------------------------------------------------------------------------------- /Resources/Images/ListViewContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/ListViewContext.png -------------------------------------------------------------------------------- /Resources/Images/LoadAssetRegistry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/LoadAssetRegistry.png -------------------------------------------------------------------------------- /Resources/Images/NameFilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/NameFilter.png -------------------------------------------------------------------------------- /Resources/Images/Names.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/Names.png -------------------------------------------------------------------------------- /Resources/Images/ObjectDependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/ObjectDependencies.png -------------------------------------------------------------------------------- /Resources/Images/OpenPak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/OpenPak.png -------------------------------------------------------------------------------- /Resources/Images/PakSummary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/PakSummary.png -------------------------------------------------------------------------------- /Resources/Images/TreeView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/TreeView.png -------------------------------------------------------------------------------- /Resources/Images/TreeViewContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Images/TreeViewContext.png -------------------------------------------------------------------------------- /Resources/Windows/RCa27464: -------------------------------------------------------------------------------- 1 | #line 1"E:\\Workspace\\UnrealEngine\\Games\\Crazy\\Source\\Programs\\UnrealPakViewer\\Resources\\Windows\\Resource.rc" 2 | #line 1 3 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 4 | #line 3 5 | #include <windows.h> 6 | #include "Runtime/Launch/Resources/Version.h" 7 | #include "Runtime/Launch/Resources/Windows/resource.h" 8 | #line 8 9 | ///////////////////////////////////////////////////////////////////////////// 10 | // 11 | // Version 12 | // 13 | #line 13 14 | VS_VERSION_INFO VERSIONINFO 15 | FILEVERSION ENGINE_MAJOR_VERSION,ENGINE_MINOR_VERSION,ENGINE_PATCH_VERSION,0 16 | PRODUCTVERSION ENGINE_MAJOR_VERSION,ENGINE_MINOR_VERSION,ENGINE_PATCH_VERSION,0 17 | FILEFLAGSMASK 0x17L 18 | #ifdef _DEBUG 19 | FILEFLAGS 0x1L 20 | #else 21 | FILEFLAGS 0x0L 22 | #endif 23 | FILEOS 0x4L 24 | FILETYPE 0x2L 25 | FILESUBTYPE 0x0L 26 | BEGIN 27 | BLOCK "StringFileInfo" 28 | BEGIN 29 | BLOCK "040904b0" 30 | BEGIN 31 | VALUE "CompanyName", EPIC_COMPANY_NAME 32 | VALUE "LegalCopyright", EPIC_COPYRIGHT_STRING 33 | VALUE "ProductName", EPIC_PRODUCT_NAME 34 | VALUE "ProductVersion", ENGINE_VERSION_STRING 35 | VALUE "FileDescription", "UnrealPakViewer" 36 | VALUE "InternalName", "UnrealPakViewer" 37 | VALUE "OriginalFilename", "UnrealPakViewer.exe" 38 | END 39 | END 40 | BLOCK "VarFileInfo" 41 | BEGIN 42 | VALUE "Translation", 0x409, 1200 43 | END 44 | END 45 | #line 46 46 | ///////////////////////////////////////////////////////////////////////////// 47 | // 48 | // Icon 49 | // 50 | #line 51 51 | // Icon with lowest ID value placed first to ensure application icon 52 | // remains consistent on all systems. 53 | IDICON_UE4Game ICON "UnrealPakViewer.ico" 54 | -------------------------------------------------------------------------------- /Resources/Windows/Resource.rc: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include 4 | #include "Runtime/Launch/Resources/Version.h" 5 | #include "Runtime/Launch/Resources/Windows/resource.h" 6 | 7 | 8 | ///////////////////////////////////////////////////////////////////////////// 9 | // 10 | // Version 11 | // 12 | 13 | VS_VERSION_INFO VERSIONINFO 14 | FILEVERSION ENGINE_MAJOR_VERSION,ENGINE_MINOR_VERSION,ENGINE_PATCH_VERSION,0 15 | PRODUCTVERSION ENGINE_MAJOR_VERSION,ENGINE_MINOR_VERSION,ENGINE_PATCH_VERSION,0 16 | FILEFLAGSMASK 0x17L 17 | #ifdef _DEBUG 18 | FILEFLAGS 0x1L 19 | #else 20 | FILEFLAGS 0x0L 21 | #endif 22 | FILEOS 0x4L 23 | FILETYPE 0x2L 24 | FILESUBTYPE 0x0L 25 | BEGIN 26 | BLOCK "StringFileInfo" 27 | BEGIN 28 | BLOCK "040904b0" 29 | BEGIN 30 | VALUE "CompanyName", EPIC_COMPANY_NAME 31 | VALUE "LegalCopyright", EPIC_COPYRIGHT_STRING 32 | VALUE "ProductName", EPIC_PRODUCT_NAME 33 | VALUE "ProductVersion", ENGINE_VERSION_STRING 34 | VALUE "FileDescription", "UnrealPakViewer" 35 | VALUE "InternalName", "UnrealPakViewer" 36 | VALUE "OriginalFilename", "UnrealPakViewer.exe" 37 | END 38 | END 39 | BLOCK "VarFileInfo" 40 | BEGIN 41 | VALUE "Translation", 0x409, 1200 42 | END 43 | END 44 | 45 | 46 | ///////////////////////////////////////////////////////////////////////////// 47 | // 48 | // Icon 49 | // 50 | 51 | // Icon with lowest ID value placed first to ensure application icon 52 | // remains consistent on all systems. 53 | IDICON_UE4Game ICON "UnrealPakViewer.ico" -------------------------------------------------------------------------------- /Resources/Windows/UnrealPakViewer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashking/UnrealPakViewer/b25b5e7fc9cc1555e56c97ea49ef7d37ce1a0e2c/Resources/Windows/UnrealPakViewer.ico -------------------------------------------------------------------------------- /UnrealPakViewer.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | [SupportedPlatforms(UnrealPlatformClass.Desktop)] 7 | public class UnrealPakViewerTarget : TargetRules 8 | { 9 | public UnrealPakViewerTarget(TargetInfo Target) : base(Target) 10 | { 11 | Type = TargetType.Program; 12 | LinkType = TargetLinkType.Monolithic; 13 | LaunchModuleName = "UnrealPakViewer"; 14 | SolutionDirectory = "ExternalPrograms"; 15 | DefaultBuildSettings = BuildSettingsVersion.Latest; 16 | 17 | IncludeOrderVersion = EngineIncludeOrderVersion.Latest; 18 | //ExtraModuleNames.Add("EditorStyle"); 19 | 20 | // Lean and mean 21 | bBuildDeveloperTools = true; 22 | 23 | // Currently this app is not linking against the engine, so we'll compile out references from Core to the rest of the engine 24 | bCompileAgainstEngine = false; 25 | bCompileAgainstCoreUObject = true; 26 | 27 | bUseLoggingInShipping = true; 28 | bCompileWithPluginSupport = false; 29 | 30 | bHasExports = false; 31 | 32 | GlobalDefinitions.Add("NOINITCRASHREPORTER=1"); 33 | GlobalDefinitions.Add("WITH_CASE_PRESERVING_NAME=0"); 34 | GlobalDefinitions.Add(string.Format("UNREAL_PAK_VIEWER_VERSION=TEXT(\"{0}\")", "1.5")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/PlatformMain/Linux/UnrealPakViewerMainLinux.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealPakViewerMain.h" 4 | #include "UnixCommonStartup.h" 5 | 6 | /** 7 | * main(), called when the application is started 8 | */ 9 | int main(int argc, const char *argv[]) 10 | { 11 | return CommonUnixMain(argc, argv, &UnrealPakViewerMain); 12 | } 13 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/PlatformMain/Mac/UnrealPakViewerMainMac.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealPakViewerMain.h" 4 | #include "HAL/ExceptionHandling.h" 5 | #include "LaunchEngineLoop.h" 6 | #include "Mac/CocoaThread.h" 7 | 8 | static FString GSavedCommandLine; 9 | 10 | @interface UE4AppDelegate : NSObject 11 | { 12 | } 13 | 14 | @end 15 | 16 | 17 | @implementation UE4AppDelegate 18 | 19 | //handler for the quit apple event used by the Dock menu 20 | - (void)handleQuitEvent:(NSAppleEventDescriptor*)Event withReplyEvent : (NSAppleEventDescriptor*)ReplyEvent 21 | { 22 | [self requestQuit : self]; 23 | } 24 | 25 | -(IBAction)requestQuit : (id)Sender 26 | { 27 | RequestEngineExit(TEXT("requestQuit")); 28 | } 29 | 30 | -(void)runGameThread : (id)Arg 31 | { 32 | FPlatformMisc::SetGracefulTerminationHandler(); 33 | FPlatformMisc::SetCrashHandler(nullptr); 34 | 35 | #if !UE_BUILD_SHIPPING 36 | if (FParse::Param(*GSavedCommandLine, TEXT("crashreports"))) 37 | { 38 | GAlwaysReportCrash = true; 39 | } 40 | #endif 41 | 42 | #if UE_BUILD_DEBUG 43 | if (!GAlwaysReportCrash) 44 | #else 45 | if (FPlatformMisc::IsDebuggerPresent() && !GAlwaysReportCrash) 46 | #endif 47 | { 48 | UnrealPakViewerMain(*GSavedCommandLine); 49 | } 50 | else 51 | { 52 | GIsGuarded = 1; 53 | UnrealPakViewerMain(*GSavedCommandLine); 54 | GIsGuarded = 0; 55 | } 56 | 57 | FEngineLoop::AppExit(); 58 | 59 | [NSApp terminate : self]; 60 | } 61 | 62 | -(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)Sender; 63 | { 64 | if (!IsEngineExitRequested() || ([NSThread gameThread] && [NSThread gameThread] != [NSThread mainThread])) 65 | { 66 | [self requestQuit : self]; 67 | return NSTerminateLater; 68 | } 69 | else 70 | { 71 | return NSTerminateNow; 72 | } 73 | } 74 | 75 | -(void)applicationDidFinishLaunching:(NSNotification *)Notification 76 | { 77 | //install the custom quit event handler 78 | NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager]; 79 | [appleEventManager setEventHandler : self andSelector : @selector(handleQuitEvent : withReplyEvent : ) forEventClass:kCoreEventClass andEventID : kAEQuitApplication]; 80 | 81 | RunGameThread(self, @selector(runGameThread:)); 82 | } 83 | 84 | @end 85 | 86 | 87 | int main(int argc, char *argv[]) 88 | { 89 | for (int32 Option = 1; Option < argc; Option++) 90 | { 91 | GSavedCommandLine += TEXT(" "); 92 | FString Argument(ANSI_TO_TCHAR(argv[Option])); 93 | if (Argument.Contains(TEXT(" "))) 94 | { 95 | if (Argument.Contains(TEXT("="))) 96 | { 97 | FString ArgName; 98 | FString ArgValue; 99 | Argument.Split(TEXT("="), &ArgName, &ArgValue); 100 | Argument = FString::Printf(TEXT("%s=\"%s\""), *ArgName, *ArgValue); 101 | } 102 | else 103 | { 104 | Argument = FString::Printf(TEXT("\"%s\""), *Argument); 105 | } 106 | } 107 | GSavedCommandLine += Argument; 108 | } 109 | 110 | SCOPED_AUTORELEASE_POOL; 111 | [NSApplication sharedApplication]; 112 | [NSApp setDelegate : [UE4AppDelegate new]]; 113 | [NSApp run]; 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/PlatformMain/Windows/UnrealPakViewerMainWindows.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealPakViewerMain.h" 4 | #include "HAL/ExceptionHandling.h" 5 | #include "Windows/WindowsHWrapper.h" 6 | #include "LaunchEngineLoop.h" 7 | #include "Misc/CommandLine.h" 8 | #include "Misc/OutputDeviceError.h" 9 | 10 | /** 11 | * The main application entry point for Windows platforms. 12 | * 13 | * @param hInInstance Handle to the current instance of the application. 14 | * @param hPrevInstance Handle to the previous instance of the application (always NULL). 15 | * @param lpCmdLine Command line for the application. 16 | * @param nShowCmd Specifies how the window is to be shown. 17 | * @return Application's exit value. 18 | */ 19 | int32 WINAPI WinMain(HINSTANCE hInInstance, HINSTANCE hPrevInstance, char* lpCmdLine, int32 nShowCmd) 20 | { 21 | hInstance = hInInstance; 22 | 23 | const TCHAR* CmdLine = ::GetCommandLineW(); 24 | CmdLine = FCommandLine::RemoveExeName(CmdLine); 25 | 26 | #if !UE_BUILD_SHIPPING 27 | if (FParse::Param(CmdLine, TEXT("crashreports"))) 28 | { 29 | GAlwaysReportCrash = true; 30 | } 31 | #endif 32 | 33 | int32 ErrorLevel = 0; 34 | 35 | #if UE_BUILD_DEBUG 36 | if (!GAlwaysReportCrash) 37 | #else 38 | if (FPlatformMisc::IsDebuggerPresent() && !GAlwaysReportCrash) 39 | #endif 40 | { 41 | ErrorLevel = UnrealPakViewerMain(CmdLine); 42 | } 43 | else 44 | { 45 | #if !PLATFORM_SEH_EXCEPTIONS_DISABLED 46 | __try 47 | #endif 48 | { 49 | GIsGuarded = 1; 50 | ErrorLevel = UnrealPakViewerMain(CmdLine); 51 | GIsGuarded = 0; 52 | } 53 | #if !PLATFORM_SEH_EXCEPTIONS_DISABLED 54 | __except (ReportCrash(GetExceptionInformation())) 55 | { 56 | ErrorLevel = 1; 57 | GError->HandleError(); 58 | FPlatformMisc::RequestExit(true); 59 | } 60 | #endif 61 | } 62 | 63 | FEngineLoop::AppExit(); 64 | 65 | return ErrorLevel; 66 | } 67 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/UnrealPakViewerApplication.cpp: -------------------------------------------------------------------------------- 1 | #include "UnrealPakViewerApplication.h" 2 | 3 | #include "Async/TaskGraphInterfaces.h" 4 | #include "Containers/Ticker.h" 5 | #include "Framework/Application/SlateApplication.h" 6 | #include "HAL/PlatformProcess.h" 7 | #include "HAL/PlatformTime.h" 8 | #include "Interfaces/IPluginManager.h" 9 | #include "Modules/ModuleManager.h" 10 | #include "StandaloneRenderer.h" 11 | #include "Stats/Stats2.h" 12 | #include "Styling/CoreStyle.h" 13 | 14 | #include "UnrealPakViewerStyle.h" 15 | #include "Widgets/SMainWindow.h" 16 | 17 | //////////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | #define IDEAL_FRAMERATE 60 20 | 21 | //////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | void FUnrealPakViewerApplication::Exec() 24 | { 25 | InitializeApplication(); 26 | 27 | // Enter main loop. 28 | double DeltaTime = 0.0; 29 | double LastTime = FPlatformTime::Seconds(); 30 | const float IdealFrameTime = 1.0f / IDEAL_FRAMERATE; 31 | 32 | while (!IsEngineExitRequested()) 33 | { 34 | FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); 35 | 36 | FSlateApplication::Get().PumpMessages(); 37 | FSlateApplication::Get().Tick(); 38 | FTSTicker::GetCoreTicker().Tick(DeltaTime); 39 | 40 | // Throttle frame rate. 41 | FPlatformProcess::Sleep(FMath::Max(0.0f, IdealFrameTime - (FPlatformTime::Seconds() - LastTime))); 42 | 43 | double CurrentTime = FPlatformTime::Seconds(); 44 | DeltaTime = CurrentTime - LastTime; 45 | LastTime = CurrentTime; 46 | 47 | FStats::AdvanceFrame(false); 48 | 49 | FCoreDelegates::OnEndFrame.Broadcast(); 50 | GLog->FlushThreadedLogs(); //im: ??? 51 | 52 | GFrameCounter++; 53 | } 54 | 55 | ShutdownApplication(); 56 | } 57 | 58 | void FUnrealPakViewerApplication::InitializeApplication() 59 | { 60 | //FCoreStyle::ResetToDefault(); 61 | 62 | // Crank up a normal Slate application using the platform's standalone renderer. 63 | FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer()); 64 | 65 | // Load required modules. 66 | //FModuleManager::Get().LoadModuleChecked("EditorStyle"); 67 | 68 | // Load plug-ins. 69 | // @todo: allow for better plug-in support in standalone Slate applications 70 | IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault); 71 | IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default); 72 | 73 | // Load optional modules. 74 | if (FModuleManager::Get().ModuleExists(TEXT("SettingsEditor"))) 75 | { 76 | FModuleManager::Get().LoadModule("SettingsEditor"); 77 | } 78 | 79 | FSlateApplication::InitHighDPI(true); 80 | FUnrealPakViewerStyle::Initialize(); 81 | 82 | FSlateApplication::Get().AddWindow(SNew(SMainWindow)); 83 | } 84 | 85 | void FUnrealPakViewerApplication::ShutdownApplication() 86 | { 87 | FUnrealPakViewerStyle::Shutdown(); 88 | 89 | // Shut down application. 90 | FSlateApplication::Shutdown(); 91 | } 92 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/UnrealPakViewerApplication.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | class FUnrealPakViewerApplication 8 | { 9 | public: 10 | 11 | /** Executes the application. */ 12 | static void Exec(); 13 | 14 | protected: 15 | 16 | /** 17 | * Initializes the application. 18 | * 19 | */ 20 | static void InitializeApplication(); 21 | 22 | /** 23 | * Shuts down the application. 24 | * 25 | */ 26 | static void ShutdownApplication(); 27 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/UnrealPakViewerMain.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealPakViewerMain.h" 4 | 5 | #include "RequiredProgramMainCPPInclude.h" 6 | 7 | #include "UnrealPakViewerApplication.h" 8 | 9 | IMPLEMENT_APPLICATION(UnrealPakViewer, "UnrealPakViewer"); 10 | 11 | /** 12 | * Platform agnostic implementation of the main entry point. 13 | */ 14 | int32 UnrealPakViewerMain(const TCHAR* CommandLine) 15 | { 16 | FTaskTagScope Scope(ETaskTag::EGameThread); 17 | 18 | #if defined(ParentProjectName) 19 | const FString ProjectDir = FString::Printf(TEXT("../../../%s/Programs/%s/"), ParentProjectName, FApp::GetProjectName()); 20 | FPlatformMisc::SetOverrideProjectDir(ProjectDir); 21 | #endif 22 | 23 | // Override the stack size for the thread pool. 24 | FQueuedThreadPool::OverrideStackSize = 256 * 1024; 25 | 26 | //im: ??? 27 | FCommandLine::Set(CommandLine); 28 | 29 | // Initialize core. 30 | GEngineLoop.PreInit(CommandLine); 31 | 32 | // Tell the module manager it may now process newly-loaded UObjects when new C++ modules are loaded. 33 | FModuleManager::Get().StartProcessingNewlyLoadedObjects(); 34 | 35 | // Run application 36 | FUnrealPakViewerApplication::Exec(); 37 | 38 | // Shut down. 39 | FEngineLoop::AppPreExit(); //im: ??? 40 | 41 | FModuleManager::Get().UnloadModulesAtShutdown(); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/UnrealPakViewerMain.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | /** 8 | * The application's main function. 9 | * 10 | * @param CommandLine The application command line. 11 | * @return Application's exit value. 12 | */ 13 | int32 UnrealPakViewerMain(const TCHAR* CommandLine); -------------------------------------------------------------------------------- /UnrealPakViewer/Private/UnrealPakViewerStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "UnrealPakViewerStyle.h" 2 | 3 | #include "Brushes/SlateImageBrush.h" 4 | #include "Misc/Paths.h" 5 | #include "Styling/SlateStyleRegistry.h" 6 | 7 | #define IMAGE_BRUSH(ResourceDir, RelativePath, ...) FSlateImageBrush (ResourceDir / RelativePath + TEXT(".png"), __VA_ARGS__) 8 | 9 | TSharedPtr FUnrealPakViewerStyle::StyleInstance; 10 | 11 | void FUnrealPakViewerStyle::Initialize() 12 | { 13 | if (!StyleInstance.IsValid()) 14 | { 15 | StyleInstance = Create(); 16 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); 17 | } 18 | } 19 | 20 | void FUnrealPakViewerStyle::Shutdown() 21 | { 22 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); 23 | StyleInstance.Reset(); 24 | } 25 | 26 | FName FUnrealPakViewerStyle::GetStyleSetName() 27 | { 28 | static FName StyleSetName(TEXT("UnrealPakViewerStyle")); 29 | return StyleSetName; 30 | } 31 | 32 | const ISlateStyle& FUnrealPakViewerStyle::Get() 33 | { 34 | return *StyleInstance; 35 | } 36 | 37 | TSharedRef FUnrealPakViewerStyle::Create() 38 | { 39 | TSharedRef StyleRef = MakeShareable(new FSlateStyleSet(FUnrealPakViewerStyle::GetStyleSetName())); 40 | 41 | //const FString ResourceDir = FPaths::ProjectDir() / TEXT("Resources"); 42 | const FString ResourceDir = FPaths::EngineContentDir() / TEXT("Editor/Slate"); 43 | 44 | FSlateStyleSet& Style = StyleRef.Get(); 45 | 46 | Style.Set("FolderClosed", new IMAGE_BRUSH(ResourceDir, "/Icons/FolderClosed", FVector2D(18.f, 16.f))); 47 | Style.Set("FolderOpen", new IMAGE_BRUSH(ResourceDir, "/Icons/FolderOpen", FVector2D(18.f, 16.f))); 48 | Style.Set("Extract", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_FontEd_Export_40x", FVector2D(40.f, 40.f))); 49 | Style.Set("Find", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_Blueprint_Find_40px", FVector2D(40.f, 40.f))); 50 | Style.Set("LoadPak", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_file_open_40x", FVector2D(40.f, 40.f))); 51 | Style.Set("Copy", new IMAGE_BRUSH(ResourceDir, "/Icons/Edit/icon_Edit_Copy_40x", FVector2D(40.f, 40.f))); 52 | Style.Set("View", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_levels_visible_40x", FVector2D(40.f, 40.f))); 53 | Style.Set("Export", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_ContentBrowser_40x", FVector2D(40.f, 40.f))); 54 | Style.Set("Tab.Tree", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_Persona_Skeleton_Tree_16x", FVector2D(20.f, 20.f))); 55 | Style.Set("Tab.File", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_source_control_40x", FVector2D(20.f, 20.f))); 56 | Style.Set("Tab.Summary", new IMAGE_BRUSH(ResourceDir, "/Icons/icon_StaticMeshEd_Collision_40x", FVector2D(20.f, 20.f))); 57 | 58 | return StyleRef; 59 | } 60 | 61 | #undef IMAGE_BRUSH 62 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/UnrealPakViewerStyle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Styling/SlateStyle.h" 5 | 6 | class FUnrealPakViewerStyle 7 | { 8 | public: 9 | static void Initialize(); 10 | static void Shutdown(); 11 | static FName GetStyleSetName(); 12 | 13 | static const ISlateStyle& Get(); 14 | 15 | protected: 16 | static TSharedRef Create(); 17 | 18 | protected: 19 | static TSharedPtr StyleInstance; 20 | }; 21 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/ClassColumn.cpp: -------------------------------------------------------------------------------- 1 | #include "ClassColumn.h" 2 | 3 | const FName FClassColumn::ClassColumnName(TEXT("Class")); 4 | const FName FClassColumn::FileCountColumnName(TEXT("FileCount")); 5 | const FName FClassColumn::SizeColumnName(TEXT("Size")); 6 | const FName FClassColumn::CompressedSizeColumnName(TEXT("CompressedSize")); 7 | const FName FClassColumn::PercentOfTotalColumnName(TEXT("PercentOfTotal")); 8 | const FName FClassColumn::PercentOfParentColumnName(TEXT("PercentOfParent")); 9 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/ClassColumn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "PakFileEntry.h" 6 | 7 | class FClassColumn 8 | { 9 | public: 10 | static const FName ClassColumnName; 11 | static const FName FileCountColumnName; 12 | static const FName SizeColumnName; 13 | static const FName CompressedSizeColumnName; 14 | static const FName PercentOfTotalColumnName; 15 | static const FName PercentOfParentColumnName; 16 | 17 | FClassColumn() = delete; 18 | FClassColumn(int32 InIndex, const FName InId, const FText& InTitleName, const FText& InDescription, float InInitialWidth) 19 | : Index(InIndex) 20 | , Id(InId) 21 | , TitleName(InTitleName) 22 | , Description(InDescription) 23 | , InitialWidth(InInitialWidth) 24 | { 25 | } 26 | 27 | int32 GetIndex() const { return Index; } 28 | const FName& GetId() const { return Id; } 29 | const FText& GetTitleName() const { return TitleName; } 30 | const FText& GetDescription() const { return Description; } 31 | 32 | float GetInitialWidth() const { return InitialWidth; } 33 | 34 | static FLinearColor GetColorByClass(const TCHAR* const InClassName) 35 | { 36 | uint32 Hash = 0; 37 | for (const TCHAR* c = InClassName; *c; ++c) 38 | { 39 | Hash = (Hash + *c) * 0x2c2c57ed; 40 | } 41 | 42 | // Divided by 128.0 in order to force bright colors. 43 | return FLinearColor(((Hash >> 16) & 0xFF) / 128.0f, ((Hash >> 8) & 0xFF) / 128.0f, (Hash & 0xFF) / 128.0f, 1.0f); 44 | } 45 | 46 | protected: 47 | int32 Index; 48 | FName Id; 49 | FText TitleName; 50 | FText Description; 51 | float InitialWidth; 52 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/FileColumn.cpp: -------------------------------------------------------------------------------- 1 | #include "FileColumn.h" 2 | 3 | const FName FFileColumn::NameColumnName(TEXT("Name")); 4 | const FName FFileColumn::PathColumnName(TEXT("Path")); 5 | const FName FFileColumn::ClassColumnName(TEXT("Class")); 6 | const FName FFileColumn::OffsetColumnName(TEXT("Offset")); 7 | const FName FFileColumn::SizeColumnName(TEXT("Size")); 8 | const FName FFileColumn::CompressedSizeColumnName(TEXT("CompressedSize")); 9 | const FName FFileColumn::CompressionBlockCountColumnName(TEXT("CompressionBlockCount")); 10 | const FName FFileColumn::CompressionBlockSizeColumnName(TEXT("CompressionBlockSize")); 11 | const FName FFileColumn::CompressionMethodColumnName(TEXT("CompressionMethod")); 12 | const FName FFileColumn::SHA1ColumnName(TEXT("SHA1")); 13 | const FName FFileColumn::IsEncryptedColumnName(TEXT("IsEncrypted")); 14 | const FName FFileColumn::OwnerPakColumnName(TEXT("OwnerPak")); 15 | const FName FFileColumn::DependencyCountColumnName(TEXT("DependencyCount")); 16 | const FName FFileColumn::DependentCountColumnName(TEXT("DependentCount")); 17 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/FileColumn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "PakFileEntry.h" 6 | 7 | enum class EFileColumnFlags : uint32 8 | { 9 | None = 0, 10 | 11 | ShouldBeVisible = (1 << 0), 12 | CanBeHidden = (1 << 1), 13 | CanBeFiltered = (1 << 2), 14 | }; 15 | ENUM_CLASS_FLAGS(EFileColumnFlags); 16 | 17 | class FFileColumn 18 | { 19 | public: 20 | typedef TFunction FFileCompareFunc; 21 | 22 | static const FName NameColumnName; 23 | static const FName PathColumnName; 24 | static const FName ClassColumnName; 25 | static const FName OffsetColumnName; 26 | static const FName SizeColumnName; 27 | static const FName CompressedSizeColumnName; 28 | static const FName CompressionBlockCountColumnName; 29 | static const FName CompressionBlockSizeColumnName; 30 | static const FName CompressionMethodColumnName; 31 | static const FName SHA1ColumnName; 32 | static const FName IsEncryptedColumnName; 33 | static const FName OwnerPakColumnName; 34 | static const FName DependencyCountColumnName; 35 | static const FName DependentCountColumnName; 36 | 37 | FFileColumn() = delete; 38 | FFileColumn(int32 InIndex, const FName InId, const FText& InTitleName, const FText& InDescription, float InFillWidth, const EFileColumnFlags& InFlags, FFileCompareFunc InAscendingCompareDelegate = nullptr, FFileCompareFunc InDescendingCompareDelegate = nullptr) 39 | : Index(InIndex) 40 | , Id(InId) 41 | , TitleName(InTitleName) 42 | , Description(InDescription) 43 | , FillWidth(InFillWidth) 44 | , Flags(InFlags) 45 | , AscendingCompareDelegate(InAscendingCompareDelegate) 46 | , DescendingCompareDelegate(InDescendingCompareDelegate) 47 | , bIsVisible(EnumHasAnyFlags(Flags, EFileColumnFlags::ShouldBeVisible)) 48 | { 49 | } 50 | 51 | int32 GetIndex() const { return Index; } 52 | const FName& GetId() const { return Id; } 53 | const FText& GetTitleName() const { return TitleName; } 54 | const FText& GetDescription() const { return Description; } 55 | 56 | bool IsVisible() const { return bIsVisible; } 57 | void Show() { bIsVisible = true; } 58 | void Hide() { bIsVisible = false; } 59 | void ToggleVisibility() { bIsVisible = !bIsVisible; } 60 | void SetVisibilityFlag(bool bOnOff) { bIsVisible = bOnOff; } 61 | 62 | float GetFillWidth() const { return FillWidth; } 63 | 64 | /** Whether this column should be initially visible. */ 65 | bool ShouldBeVisible() const { return EnumHasAnyFlags(Flags, EFileColumnFlags::ShouldBeVisible); } 66 | 67 | /** Whether this column can be hidden. */ 68 | bool CanBeHidden() const { return EnumHasAnyFlags(Flags, EFileColumnFlags::CanBeHidden); } 69 | 70 | /** Whether this column can be used for filtering displayed results. */ 71 | bool CanBeFiltered() const { return EnumHasAnyFlags(Flags, EFileColumnFlags::CanBeFiltered); } 72 | 73 | /** Whether this column can be used for sort displayed results. */ 74 | bool CanBeSorted() const { return AscendingCompareDelegate && DescendingCompareDelegate; } 75 | 76 | void SetAscendingCompareDelegate(FFileCompareFunc InCompareDelegate) { AscendingCompareDelegate = InCompareDelegate; } 77 | void SetDescendingCompareDelegate(FFileCompareFunc InCompareDelegate) { DescendingCompareDelegate = InCompareDelegate; } 78 | 79 | FFileCompareFunc GetAscendingCompareDelegate() const { return AscendingCompareDelegate; } 80 | FFileCompareFunc GetDescendingCompareDelegate() const { return DescendingCompareDelegate; } 81 | 82 | protected: 83 | int32 Index; 84 | FName Id; 85 | FText TitleName; 86 | FText Description; 87 | float FillWidth; 88 | EFileColumnFlags Flags; 89 | FFileCompareFunc AscendingCompareDelegate; 90 | FFileCompareFunc DescendingCompareDelegate; 91 | 92 | bool bIsVisible; 93 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/FileSortAndFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "FileSortAndFilter.h" 2 | 3 | #include "Misc/ScopeLock.h" 4 | #include "PakAnalyzerModule.h" 5 | #include "ViewModels/FileColumn.h" 6 | #include "Widgets/SPakFileView.h" 7 | 8 | void FFileSortAndFilterTask::DoWork() 9 | { 10 | TSharedPtr PakFileViewPin = WeakPakFileView.Pin(); 11 | if (!PakFileViewPin.IsValid()) 12 | { 13 | return; 14 | } 15 | 16 | TArray FilterResult; 17 | IPakAnalyzerModule::Get().GetPakAnalyzer()->GetFiles(CurrentSearchText, ClassFilterMap, IndexFilterMap, FilterResult); 18 | 19 | const FFileColumn* Column = PakFileViewPin->FindCoulum(CurrentSortedColumn); 20 | if (!Column) 21 | { 22 | return; 23 | } 24 | 25 | if (!Column->CanBeSorted()) 26 | { 27 | return; 28 | } 29 | 30 | if (CurrentSortMode == EColumnSortMode::Ascending) 31 | { 32 | FilterResult.Sort(Column->GetAscendingCompareDelegate()); 33 | } 34 | else 35 | { 36 | FilterResult.Sort(Column->GetDescendingCompareDelegate()); 37 | } 38 | 39 | { 40 | FScopeLock Lock(&CriticalSection); 41 | Result = MoveTemp(FilterResult); 42 | } 43 | 44 | OnWorkFinished.ExecuteIfBound(CurrentSortedColumn, CurrentSortMode, CurrentSearchText); 45 | } 46 | 47 | void FFileSortAndFilterTask::SetWorkInfo(FName InSortedColumn, EColumnSortMode::Type InSortMode, const FString& InSearchText, const TMap& InClassFilterMap, const TMap& InIndexFilterMap) 48 | { 49 | CurrentSortedColumn = InSortedColumn; 50 | CurrentSortMode = InSortMode; 51 | CurrentSearchText = InSearchText; 52 | ClassFilterMap = InClassFilterMap; 53 | IndexFilterMap = InIndexFilterMap; 54 | } 55 | 56 | void FFileSortAndFilterTask::RetriveResult(TArray& OutResult) 57 | { 58 | FScopeLock Lock(&CriticalSection); 59 | OutResult = MoveTemp(Result); 60 | } 61 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/FileSortAndFilter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Async/AsyncWork.h" 4 | #include "CoreMinimal.h" 5 | #include "HAL/CriticalSection.h" 6 | #include "Stats/Stats.h" 7 | 8 | #include "PakFileEntry.h" 9 | 10 | DECLARE_DELEGATE_ThreeParams(FOnSortAndFilterFinished, const FName, EColumnSortMode::Type, const FString&); 11 | 12 | class SPakFileView; 13 | 14 | class FFileSortAndFilterTask : public FNonAbandonableTask 15 | { 16 | public: 17 | FFileSortAndFilterTask(FName InSortedColumn, EColumnSortMode::Type InSortMode, TSharedPtr InPakFileView) 18 | : CurrentSortedColumn(InSortedColumn) 19 | , CurrentSortMode(InSortMode) 20 | , CurrentSearchText(TEXT("")) 21 | , WeakPakFileView(InPakFileView) 22 | { 23 | 24 | } 25 | 26 | void DoWork(); 27 | void SetWorkInfo(FName InSortedColumn, EColumnSortMode::Type InSortMode, const FString& InSearchText, const TMap& InClassFilterMap, const TMap& InIndexFilterMap); 28 | FOnSortAndFilterFinished& GetOnSortAndFilterFinishedDelegate() { return OnWorkFinished; } 29 | 30 | FORCEINLINE TStatId GetStatId() const 31 | { 32 | RETURN_QUICK_DECLARE_CYCLE_STAT(STAT_FFileSortAndFilterTask, STATGROUP_ThreadPoolAsyncTasks); 33 | } 34 | 35 | void RetriveResult(TArray& OutResult); 36 | 37 | protected: 38 | FName CurrentSortedColumn; 39 | EColumnSortMode::Type CurrentSortMode; 40 | FString CurrentSearchText; 41 | 42 | /** Shared pointer to parent PakFileView widget. Used for accesing the cache and to check if cancel is requested. */ 43 | TWeakPtr WeakPakFileView; 44 | 45 | FOnSortAndFilterFinished OnWorkFinished; 46 | 47 | FCriticalSection CriticalSection; 48 | TArray Result; 49 | 50 | TMap ClassFilterMap; 51 | TMap IndexFilterMap; 52 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/WidgetDelegates.cpp: -------------------------------------------------------------------------------- 1 | #include "WidgetDelegates.h" 2 | 3 | FOnSwitchToTreeView& FWidgetDelegates::GetOnSwitchToTreeViewDelegate() 4 | { 5 | static FOnSwitchToTreeView Delegate; 6 | return Delegate; 7 | } 8 | 9 | FOnSwitchToFileView& FWidgetDelegates::GetOnSwitchToFileViewDelegate() 10 | { 11 | static FOnSwitchToFileView Delegate; 12 | return Delegate; 13 | } 14 | 15 | FOnLoadAssetRegistryFinished& FWidgetDelegates::GetOnLoadAssetRegistryFinishedDelegate() 16 | { 17 | static FOnLoadAssetRegistryFinished Delegate; 18 | return Delegate; 19 | } -------------------------------------------------------------------------------- /UnrealPakViewer/Private/ViewModels/WidgetDelegates.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | DECLARE_MULTICAST_DELEGATE_TwoParams(FOnSwitchToTreeView, const FString&, int32); 6 | DECLARE_MULTICAST_DELEGATE_TwoParams(FOnSwitchToFileView, const FString&, int32); 7 | DECLARE_MULTICAST_DELEGATE(FOnLoadAssetRegistryFinished); 8 | 9 | class FWidgetDelegates 10 | { 11 | public: 12 | static FOnSwitchToTreeView& GetOnSwitchToTreeViewDelegate(); 13 | static FOnSwitchToFileView& GetOnSwitchToFileViewDelegate(); 14 | static FOnLoadAssetRegistryFinished& GetOnLoadAssetRegistryFinishedDelegate(); 15 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SAboutWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "SAboutWindow.h" 2 | 3 | //#include "EditorStyleSet.h" 4 | #include "Framework/Application/SlateApplication.h" 5 | #include "HAL/PlatformApplicationMisc.h" 6 | #include "HAL/PlatformProcess.h" 7 | #include "Runtime/Launch/Resources/Version.h" 8 | #include "Widgets/Input/SHyperlink.h" 9 | 10 | #include "SKeyValueRow.h" 11 | 12 | #define LOCTEXT_NAMESPACE "SAboutWindow" 13 | 14 | #define GITHUB_HOME TEXT("https://github.com/jashking/UnrealPakViewer") 15 | 16 | SAboutWindow::SAboutWindow() 17 | { 18 | } 19 | 20 | SAboutWindow::~SAboutWindow() 21 | { 22 | } 23 | 24 | void SAboutWindow::Construct(const FArguments& Args) 25 | { 26 | const float DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); 27 | const FVector2D InitialWindowDimensions(360, 80); 28 | 29 | SWindow::Construct(SWindow::FArguments() 30 | .Title(LOCTEXT("WindowTitle", "About")) 31 | .HasCloseButton(true) 32 | .SupportsMaximize(false) 33 | .SupportsMinimize(false) 34 | .SizingRule(ESizingRule::FixedSize) 35 | .ClientSize(InitialWindowDimensions * DPIScaleFactor) 36 | [ 37 | SNew(SBorder) 38 | //.BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) 39 | .Padding(FMargin(5.f, 5.f)) 40 | [ 41 | SNew(SVerticalBox) 42 | 43 | #if defined(UNREAL_PAK_VIEWER_VERSION) 44 | +SVerticalBox::Slot() 45 | .Padding(0.f, 2.f) 46 | .AutoHeight() 47 | [ 48 | SNew(SKeyValueRow).KeyText(LOCTEXT("Version_Text", "Version:")).ValueText(FText::FromString(UNREAL_PAK_VIEWER_VERSION)) 49 | ] 50 | #endif 51 | 52 | + SVerticalBox::Slot() 53 | .Padding(0.f, 2.f) 54 | .AutoHeight() 55 | [ 56 | SNew(SKeyValueRow).KeyText(LOCTEXT("Engine_Text", "Engine:")).ValueText(FText::Format(LOCTEXT("Engine_Version_Text", "{0}.{1}.{2}"), ENGINE_MAJOR_VERSION, ENGINE_MINOR_VERSION, ENGINE_PATCH_VERSION)) 57 | ] 58 | 59 | + SVerticalBox::Slot() 60 | .Padding(0.f, 2.f) 61 | .AutoHeight() 62 | [ 63 | SNew(SKeyValueRow).KeyText(LOCTEXT("Github_Text", "Github:")) 64 | .ValueContent() 65 | [ 66 | SNew(SHyperlink).Text(FText::FromString(GITHUB_HOME)) 67 | .OnNavigate_Lambda([]() 68 | { 69 | FPlatformProcess::LaunchURL(GITHUB_HOME, NULL, NULL); 70 | }) 71 | ] 72 | ] 73 | ] 74 | ] 75 | ); 76 | } 77 | 78 | #undef LOCTEXT_NAMESPACE 79 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SAboutWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SWindow.h" 5 | 6 | class SAboutWindow : public SWindow 7 | { 8 | public: 9 | SLATE_BEGIN_ARGS(SAboutWindow) 10 | { 11 | } 12 | SLATE_END_ARGS() 13 | 14 | SAboutWindow(); 15 | virtual ~SAboutWindow(); 16 | 17 | /** Widget constructor */ 18 | void Construct(const FArguments& Args); 19 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SAssetSummaryView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | #include "Widgets/Views/SListView.h" 6 | 7 | #include "PakFileEntry.h" 8 | 9 | #define DECLARE_GET_MEMBER_FUNCTION(MemberName) FORCEINLINE FText Get##MemberName() const 10 | 11 | class SHeaderRow; 12 | 13 | /** Implements the Pak Info window. */ 14 | class SAssetSummaryView : public SCompoundWidget 15 | { 16 | public: 17 | /** Default constructor. */ 18 | SAssetSummaryView(); 19 | 20 | /** Virtual destructor. */ 21 | virtual ~SAssetSummaryView(); 22 | 23 | SLATE_BEGIN_ARGS(SAssetSummaryView) {} 24 | SLATE_END_ARGS() 25 | 26 | /** Constructs this widget. */ 27 | void Construct(const FArguments& InArgs); 28 | 29 | void SetViewingPackage(FPakFileEntryPtr InPackage); 30 | 31 | protected: 32 | DECLARE_GET_MEMBER_FUNCTION(Guid); 33 | DECLARE_GET_MEMBER_FUNCTION(IsUnversioned); 34 | DECLARE_GET_MEMBER_FUNCTION(FileVersionUE4); 35 | DECLARE_GET_MEMBER_FUNCTION(FileVersionUE5); 36 | DECLARE_GET_MEMBER_FUNCTION(FileVersionLicenseeUE); 37 | DECLARE_GET_MEMBER_FUNCTION(TotalHeaderSize); 38 | DECLARE_GET_MEMBER_FUNCTION(NameCount); 39 | DECLARE_GET_MEMBER_FUNCTION(NameOffset); 40 | DECLARE_GET_MEMBER_FUNCTION(ExportCount); 41 | DECLARE_GET_MEMBER_FUNCTION(ExportOffset); 42 | DECLARE_GET_MEMBER_FUNCTION(ExportSize); 43 | DECLARE_GET_MEMBER_FUNCTION(ExportSizeTooltip); 44 | DECLARE_GET_MEMBER_FUNCTION(ImportCount); 45 | DECLARE_GET_MEMBER_FUNCTION(ImportOffset); 46 | DECLARE_GET_MEMBER_FUNCTION(PackageFlags); 47 | DECLARE_GET_MEMBER_FUNCTION(GatherableTextDataCount); 48 | DECLARE_GET_MEMBER_FUNCTION(GatherableTextDataOffset); 49 | DECLARE_GET_MEMBER_FUNCTION(DependsOffset); 50 | DECLARE_GET_MEMBER_FUNCTION(SoftPackageReferencesCount); 51 | DECLARE_GET_MEMBER_FUNCTION(SoftPackageReferencesOffset); 52 | DECLARE_GET_MEMBER_FUNCTION(SearchableNamesOffset); 53 | DECLARE_GET_MEMBER_FUNCTION(ThumbnailTableOffset); 54 | DECLARE_GET_MEMBER_FUNCTION(AssetRegistryDataOffset); 55 | DECLARE_GET_MEMBER_FUNCTION(BulkDataStartOffset); 56 | DECLARE_GET_MEMBER_FUNCTION(WorldTileInfoDataOffset); 57 | DECLARE_GET_MEMBER_FUNCTION(PreloadDependencyCount); 58 | DECLARE_GET_MEMBER_FUNCTION(PreloadDependencyOffset); 59 | DECLARE_GET_MEMBER_FUNCTION(DependencyCount); 60 | DECLARE_GET_MEMBER_FUNCTION(DependentCount); 61 | 62 | TSharedRef OnGenerateNameRow(FNamePtrType InName, const TSharedRef& OwnerTable); 63 | TSharedRef OnGenerateImportObjectRow(FObjectImportPtrType InObject, const TSharedRef& OwnerTable); 64 | TSharedRef OnGenerateExportObjectRow(FObjectExportPtrType InObject, const TSharedRef& OwnerTable); 65 | TSharedRef OnGenerateDependsRow(FPackageInfoPtr InDepends, const TSharedRef& OwnerTable); 66 | 67 | void InsertColumn(TSharedPtr InHeader, FName InId, const FString& InCloumnName = TEXT("")); 68 | void InsertSortableColumn(TSharedPtr InHeader, FName InId, const FString& InCloumnName = TEXT("")); 69 | void OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode); 70 | EColumnSortMode::Type GetSortModeForColumn(const FName ColumnId) const; 71 | void OnSortExportObjects(); 72 | 73 | protected: 74 | FPakFileEntryPtr ViewingPackage; 75 | 76 | TSharedPtr> NamesListView; 77 | TArray PackageNames; 78 | 79 | TSharedPtr ImportObjectHeaderRow; 80 | TSharedPtr> ImportObjectListView; 81 | TArray ImportObjects; 82 | 83 | TSharedPtr ExportObjectHeaderRow; 84 | TSharedPtr> ExportObjectListView; 85 | TArray ExportObjects; 86 | int64 TotalExportSize; 87 | 88 | FName LastSortColumn = "SerialOffset"; 89 | EColumnSortMode::Type LastSortMode = EColumnSortMode::Ascending; 90 | 91 | //TSharedPtr> PreloadDependencyListView; 92 | //TArray PreloadDependency; 93 | 94 | TSharedPtr> DependencyListView; 95 | TSharedPtr> DependentListView; 96 | TArray DependencyList; 97 | TArray DependentList; 98 | }; 99 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SExtractProgressWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "SExtractProgressWindow.h" 2 | 3 | //#include "EditorStyle.h" 4 | #include "HAL/PlatformApplicationMisc.h" 5 | #include "Misc/Timespan.h" 6 | #include "Widgets/Notifications/SProgressBar.h" 7 | 8 | #include "CommonDefines.h" 9 | #include "PakAnalyzerModule.h" 10 | #include "SKeyValueRow.h" 11 | 12 | #define LOCTEXT_NAMESPACE "SExtractProgressWindow" 13 | 14 | SExtractProgressWindow::SExtractProgressWindow() 15 | : CompleteCount(0) 16 | , TotalCount(0) 17 | , ErrorCount(0) 18 | , bExtractFinished(false) 19 | { 20 | FPakAnalyzerDelegates::OnUpdateExtractProgress.BindRaw(this, &SExtractProgressWindow::OnUpdateExtractProgress); 21 | } 22 | 23 | SExtractProgressWindow::~SExtractProgressWindow() 24 | { 25 | 26 | } 27 | 28 | void SExtractProgressWindow::Construct(const FArguments& Args) 29 | { 30 | const float DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); 31 | const FVector2D InitialWindowDimensions(600, 60); 32 | 33 | SWindow::Construct(SWindow::FArguments() 34 | .Title(LOCTEXT("WindowTitle", "Extracting...")) 35 | .HasCloseButton(true) 36 | .SupportsMaximize(false) 37 | .SupportsMinimize(false) 38 | .SizingRule(ESizingRule::FixedSize) 39 | .ClientSize(InitialWindowDimensions * DPIScaleFactor) 40 | [ 41 | SNew(SBorder) 42 | //.BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) 43 | .Padding(FMargin(5.f, 10.f)) 44 | [ 45 | SNew(SVerticalBox) 46 | 47 | + SVerticalBox::Slot() 48 | .AutoHeight() 49 | [ 50 | SNew(SHorizontalBox) 51 | 52 | + SHorizontalBox::Slot() 53 | .AutoWidth() 54 | .HAlign(EHorizontalAlignment::HAlign_Left) 55 | .VAlign(EVerticalAlignment::VAlign_Center) 56 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 57 | [ 58 | SNew(STextBlock).Text(LOCTEXT("ExtractText", "Extract progress:")) 59 | ] 60 | 61 | + SHorizontalBox::Slot() 62 | .FillWidth(1.f) 63 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 64 | [ 65 | SNew(SOverlay) 66 | 67 | + SOverlay::Slot() 68 | [ 69 | SNew(SProgressBar).Percent(this, &SExtractProgressWindow::GetExtractProgress) 70 | ] 71 | 72 | + SOverlay::Slot() 73 | .HAlign(HAlign_Center) 74 | [ 75 | SNew(STextBlock) 76 | .Text(this, &SExtractProgressWindow::GetExtractProgressText) 77 | .ColorAndOpacity(FLinearColor::Black) 78 | ] 79 | ] 80 | ] 81 | 82 | + SVerticalBox::Slot() 83 | .AutoHeight() 84 | .Padding(0.f, 4.f) 85 | [ 86 | SNew(SHorizontalBox) 87 | 88 | + SHorizontalBox::Slot() 89 | .FillWidth(1.f) 90 | [ 91 | SNew(SKeyValueRow).KeyStretchCoefficient(1.f).KeyText(LOCTEXT("CompleteText", "Complete:")).ValueText(this, &SExtractProgressWindow::GetCompleteCount) 92 | ] 93 | 94 | + SHorizontalBox::Slot() 95 | .FillWidth(1.f) 96 | [ 97 | SNew(SKeyValueRow).KeyStretchCoefficient(1.f).KeyText(LOCTEXT("ErrorText", "Error:")).ValueText(this, &SExtractProgressWindow::GetErrorCount) 98 | ] 99 | 100 | + SHorizontalBox::Slot() 101 | .FillWidth(1.f) 102 | [ 103 | SNew(SKeyValueRow).KeyStretchCoefficient(1.f).KeyText(LOCTEXT("Total", "Total:")).ValueText(this, &SExtractProgressWindow::GetTotalCount) 104 | ] 105 | 106 | + SHorizontalBox::Slot() 107 | .FillWidth(1.f) 108 | [ 109 | SNew(SKeyValueRow).KeyStretchCoefficient(0.8f).KeyText(LOCTEXT("Time", "Time:")).ValueText(this, &SExtractProgressWindow::GetTimeElapsed) 110 | ] 111 | ] 112 | ] 113 | ] 114 | ); 115 | 116 | OnWindowClosed.BindRaw(this, &SExtractProgressWindow::OnExit); 117 | StartTime = Args._StartTime; 118 | LastTime = Args._StartTime.Get(); 119 | bExtractFinished = false; 120 | 121 | SetCanTick(true); 122 | } 123 | 124 | void SExtractProgressWindow::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) 125 | { 126 | SWindow::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); 127 | 128 | bExtractFinished = CompleteCount == TotalCount; 129 | if (!bExtractFinished) 130 | { 131 | LastTime = FDateTime::Now(); 132 | } 133 | } 134 | 135 | FORCEINLINE FText SExtractProgressWindow::GetCompleteCount() const 136 | { 137 | return FText::AsNumber(CompleteCount); 138 | } 139 | 140 | FORCEINLINE FText SExtractProgressWindow::GetErrorCount() const 141 | { 142 | return FText::AsNumber(ErrorCount); 143 | } 144 | 145 | FORCEINLINE FText SExtractProgressWindow::GetTotalCount() const 146 | { 147 | return FText::AsNumber(TotalCount); 148 | } 149 | 150 | FORCEINLINE TOptional SExtractProgressWindow::GetExtractProgress() const 151 | { 152 | return TotalCount > 0 ? (float)CompleteCount / TotalCount : 0.f; 153 | } 154 | 155 | FORCEINLINE FText SExtractProgressWindow::GetExtractProgressText() const 156 | { 157 | return TotalCount > 0 ? FText::FromString(FString::Printf(TEXT("%.2f%%"), (float)CompleteCount / TotalCount * 100)) : FText(); 158 | } 159 | 160 | FORCEINLINE FText SExtractProgressWindow::GetTimeElapsed() const 161 | { 162 | const FTimespan ElapsedTime = LastTime - StartTime.Get(); 163 | 164 | return FText::FromString(ElapsedTime.ToString()); 165 | } 166 | 167 | void SExtractProgressWindow::OnExit(const TSharedRef& InWindow) 168 | { 169 | IPakAnalyzerModule::Get().GetPakAnalyzer()->CancelExtract(); 170 | } 171 | 172 | void SExtractProgressWindow::OnUpdateExtractProgress(int32 InCompleteCount, int32 InErrorCount, int32 InTotalCount) 173 | { 174 | CompleteCount = InCompleteCount; 175 | ErrorCount = InErrorCount; 176 | TotalCount = InTotalCount; 177 | } 178 | 179 | #undef LOCTEXT_NAMESPACE 180 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SExtractProgressWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Misc/DateTime.h" 5 | #include "Widgets/SWindow.h" 6 | 7 | class SExtractProgressWindow : public SWindow 8 | { 9 | public: 10 | SLATE_BEGIN_ARGS(SExtractProgressWindow) 11 | { 12 | } 13 | SLATE_ATTRIBUTE(FDateTime, StartTime) 14 | SLATE_END_ARGS() 15 | 16 | SExtractProgressWindow(); 17 | virtual ~SExtractProgressWindow(); 18 | 19 | /** Widget constructor */ 20 | void Construct(const FArguments& Args); 21 | 22 | /** 23 | * Ticks this widget. Override in derived classes, but always call the parent implementation. 24 | * 25 | * @param AllottedGeometry - The space allotted for this widget 26 | * @param InCurrentTime - Current absolute real time 27 | * @param InDeltaTime - Real time passed since last tick 28 | */ 29 | virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; 30 | 31 | protected: 32 | FORCEINLINE FText GetCompleteCount() const; 33 | FORCEINLINE FText GetErrorCount() const; 34 | FORCEINLINE FText GetTotalCount() const; 35 | FORCEINLINE TOptional GetExtractProgress() const; 36 | FORCEINLINE FText GetExtractProgressText() const; 37 | FORCEINLINE FText GetTimeElapsed() const; 38 | 39 | void OnExit(const TSharedRef& InWindow); 40 | void OnUpdateExtractProgress(int32 InCompleteCount, int32 InErrorCount, int32 InTotalCount); 41 | 42 | protected: 43 | int32 CompleteCount; 44 | int32 TotalCount; 45 | int32 ErrorCount; 46 | TAttribute StartTime; 47 | FDateTime LastTime; 48 | bool bExtractFinished; 49 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SKeyInputWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "SKeyInputWindow.h" 2 | 3 | //#include "EditorStyle.h" 4 | #include "HAL/PlatformApplicationMisc.h" 5 | #include "Misc/Base64.h" 6 | #include "Misc/Char.h" 7 | #include "Misc/Paths.h" 8 | 9 | #define LOCTEXT_NAMESPACE "SKeyInputWindow" 10 | 11 | SKeyInputWindow::SKeyInputWindow() 12 | { 13 | 14 | } 15 | 16 | SKeyInputWindow::~SKeyInputWindow() 17 | { 18 | 19 | } 20 | 21 | void SKeyInputWindow::Construct(const FArguments& Args) 22 | { 23 | OnConfirm = Args._OnConfirm; 24 | PakPath = Args._PakPath; 25 | PakGuid = Args._PakGuid; 26 | 27 | const float DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); 28 | const FVector2D InitialWindowDimensions(600, 70); 29 | 30 | SWindow::Construct(SWindow::FArguments() 31 | .Title(LOCTEXT("WindowTitle", "Please input encryption key(Base64 string or Hex string:")) 32 | .HasCloseButton(true) 33 | .SupportsMaximize(false) 34 | .SupportsMinimize(false) 35 | .SizingRule(ESizingRule::FixedSize) 36 | .ClientSize(InitialWindowDimensions * DPIScaleFactor) 37 | [ 38 | SNew(SBorder) 39 | //.BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) 40 | .Padding(FMargin(5.f, 10.f)) 41 | [ 42 | SNew(SVerticalBox) 43 | 44 | + SVerticalBox::Slot() 45 | . AutoHeight() 46 | .HAlign(EHorizontalAlignment::HAlign_Left) 47 | .VAlign(EVerticalAlignment::VAlign_Center) 48 | .Padding(FMargin(0.f, 0.f, 0.f, 5.f)) 49 | [ 50 | SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("%s(Guid: %s):"), *FPaths::GetCleanFilename(PakPath.Get(TEXT(""))), *LexToString(PakGuid.Get(FGuid()))))) 51 | ] 52 | 53 | + SVerticalBox::Slot() 54 | .AutoHeight() 55 | [ 56 | SNew(SHorizontalBox) 57 | 58 | + SHorizontalBox::Slot() 59 | .FillWidth(1.f) 60 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 61 | [ 62 | SAssignNew(EncryptionKeyBox, SEditableTextBox) 63 | ] 64 | 65 | + SHorizontalBox::Slot() 66 | .AutoWidth() 67 | .HAlign(EHorizontalAlignment::HAlign_Right) 68 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 69 | [ 70 | SNew(SBox) 71 | .MinDesiredWidth(60.f) 72 | [ 73 | SNew(SButton) 74 | .Text(LOCTEXT("ConfirmButtonText", "OK")) 75 | .HAlign(EHorizontalAlignment::HAlign_Center) 76 | .VAlign(EVerticalAlignment::VAlign_Center) 77 | .OnClicked(this, &SKeyInputWindow::OnConfirmButtonClicked) 78 | ] 79 | ] 80 | ] 81 | ] 82 | ] 83 | ); 84 | } 85 | 86 | FReply SKeyInputWindow::OnConfirmButtonClicked() 87 | { 88 | FString InputString = EncryptionKeyBox->GetText().ToString(); 89 | if (InputString.StartsWith(TEXT("0x"))) 90 | { 91 | InputString.RemoveAt(0, 2); 92 | } 93 | 94 | bool bIsPureHexDigit = true; 95 | const TArray& CharArray = InputString.GetCharArray(); 96 | for (int32 i = 0; i < CharArray.Num() - 1; ++i) 97 | { 98 | if (!FChar::IsHexDigit(CharArray[i])) 99 | { 100 | bIsPureHexDigit = false; 101 | break; 102 | } 103 | } 104 | 105 | uint8 Hexs[32] = { 0 }; 106 | if (bIsPureHexDigit && FString::ToHexBlob(InputString, Hexs, sizeof(Hexs))) 107 | { 108 | InputString = FBase64::Encode(Hexs, sizeof(Hexs)); 109 | } 110 | 111 | OnConfirm.ExecuteIfBound(InputString); 112 | 113 | RequestDestroyWindow(); 114 | 115 | return FReply::Handled(); 116 | } 117 | 118 | #undef LOCTEXT_NAMESPACE 119 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SKeyInputWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SWindow.h" 5 | 6 | DECLARE_DELEGATE_OneParam(FOnConfirm, const FString&); 7 | 8 | class SKeyInputWindow : public SWindow 9 | { 10 | public: 11 | SLATE_BEGIN_ARGS(SKeyInputWindow) 12 | { 13 | } 14 | 15 | SLATE_EVENT(FOnConfirm, OnConfirm) 16 | SLATE_ATTRIBUTE(FString, PakPath) 17 | SLATE_ATTRIBUTE(FGuid, PakGuid) 18 | SLATE_END_ARGS() 19 | 20 | SKeyInputWindow(); 21 | virtual ~SKeyInputWindow(); 22 | 23 | /** Widget constructor */ 24 | void Construct(const FArguments& Args); 25 | 26 | protected: 27 | FReply OnConfirmButtonClicked(); 28 | 29 | protected: 30 | FOnConfirm OnConfirm; 31 | TAttribute PakPath; 32 | TAttribute PakGuid; 33 | 34 | TSharedPtr EncryptionKeyBox; 35 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SKeyValueRow.cpp: -------------------------------------------------------------------------------- 1 | #include "SKeyValueRow.h" 2 | 3 | void SKeyValueRow::Construct(const FArguments& InArgs) 4 | { 5 | KeyText = InArgs._KeyText; 6 | KeyToolTipText = InArgs._KeyToolTipText; 7 | ValueText = InArgs._ValueText; 8 | ValueToolTipText = InArgs._ValueToolTipText; 9 | KeyStretchCoefficient = InArgs._KeyStretchCoefficient; 10 | 11 | TSharedRef KeyContent = InArgs._KeyContent.Widget; 12 | if (KeyContent == SNullWidget::NullWidget) 13 | { 14 | KeyContent = SNew(STextBlock).Text(KeyText).ToolTipText(KeyToolTipText).ColorAndOpacity(FLinearColor::Green).ShadowOffset(FVector2D(1.f, 1.f)); 15 | } 16 | 17 | TSharedRef ValueContent = InArgs._ValueContent.Widget; 18 | if (ValueContent == SNullWidget::NullWidget) 19 | { 20 | ValueContent = SNew(STextBlock).Text(ValueText).ToolTipText(ValueToolTipText); 21 | } 22 | 23 | ChildSlot 24 | [ 25 | SNew(SHorizontalBox) 26 | 27 | + SHorizontalBox::Slot() 28 | .FillWidth(KeyStretchCoefficient) 29 | //.Padding(2.0f) 30 | [ 31 | KeyContent 32 | ] 33 | 34 | + SHorizontalBox::Slot() 35 | .FillWidth(1.f) 36 | //.Padding(2.0f) 37 | [ 38 | ValueContent 39 | ] 40 | ]; 41 | } 42 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SKeyValueRow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | 6 | class SKeyValueRow : public SCompoundWidget 7 | { 8 | public: 9 | /** Default constructor. */ 10 | SKeyValueRow() {} 11 | 12 | /** Virtual destructor. */ 13 | virtual ~SKeyValueRow() {} 14 | 15 | SLATE_BEGIN_ARGS(SKeyValueRow) 16 | : _KeyStretchCoefficient(0.4f) 17 | {} 18 | SLATE_ATTRIBUTE(FText, KeyText) 19 | SLATE_ATTRIBUTE(FText, KeyToolTipText) 20 | SLATE_ATTRIBUTE(FText, ValueText) 21 | SLATE_ATTRIBUTE(FText, ValueToolTipText) 22 | SLATE_ATTRIBUTE(float, KeyStretchCoefficient) 23 | SLATE_NAMED_SLOT(FArguments, KeyContent) 24 | SLATE_NAMED_SLOT(FArguments, ValueContent) 25 | SLATE_END_ARGS() 26 | 27 | /** Constructs this widget. */ 28 | void Construct(const FArguments& InArgs); 29 | 30 | protected: 31 | TAttribute KeyText; 32 | TAttribute KeyToolTipText; 33 | TAttribute ValueText; 34 | TAttribute ValueToolTipText; 35 | TAttribute KeyStretchCoefficient; 36 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SMainWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SWindow.h" 5 | 6 | class SMainWindow : public SWindow 7 | { 8 | public: 9 | SLATE_BEGIN_ARGS(SMainWindow) 10 | { 11 | } 12 | SLATE_END_ARGS() 13 | 14 | SMainWindow(); 15 | virtual ~SMainWindow(); 16 | 17 | /** Widget constructor */ 18 | void Construct(const FArguments& Args); 19 | 20 | protected: 21 | TSharedRef MakeMainMenu(); 22 | void FillFileMenu(class FMenuBuilder& MenuBuilder); 23 | void FillViewsMenu(class FMenuBuilder& MenuBuilder); 24 | 25 | TSharedRef OnSpawnTab_SummaryView(const FSpawnTabArgs& Args); 26 | TSharedRef OnSpawnTab_TreeView(const FSpawnTabArgs& Args); 27 | TSharedRef OnSpawnTab_FileView(const FSpawnTabArgs& Args); 28 | 29 | void OnExit(const TSharedRef& InWindow); 30 | void OnLoadPakFile(); 31 | void OnLoadAllFilesInFolder(); 32 | void OnLoadFolder(); 33 | void OnLoadPakFailed(const FString& InReason); 34 | FString OnGetAESKey(const FString& InPakPath, const FGuid& PakGuid, bool& bCancel); 35 | void OnSwitchToTreeView(const FString& InPath, int32 PakIndex); 36 | void OnSwitchToFileView(const FString& InPath, int32 PakIndex); 37 | void OnExtractStart(); 38 | void OnLoadRecentFile(int32 InIndex); 39 | bool OnLoadRecentFileCanExecute(int32 InIndex) const; 40 | 41 | void OnOpenOptionsDialog(); 42 | void OnOpenAboutDialog(); 43 | 44 | /** 45 | * Called when the user is dropping something onto a widget; terminates drag and drop. 46 | * 47 | * @param MyGeometry The geometry of the widget receiving the event. 48 | * @param DragDropEvent The drag and drop event. 49 | * 50 | * @return A reply that indicated whether this event was handled. 51 | */ 52 | virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; 53 | 54 | /** 55 | * Called during drag and drop when the the mouse is being dragged over a widget. 56 | * 57 | * @param MyGeometry The geometry of the widget receiving the event. 58 | * @param DragDropEvent The drag and drop event. 59 | * 60 | * @return A reply that indicated whether this event was handled. 61 | */ 62 | virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; 63 | 64 | void LoadPakFile(const TArray& PakFilePaths); 65 | void RemoveRecentFile(const FString& InFullPath); 66 | void SaveConfig(); 67 | void LoadConfig(); 68 | FString FindExistingAESKey(const FString& InFullPath); 69 | 70 | protected: 71 | static const int32 WINDOW_WIDTH = 1200; 72 | static const int32 WINDOW_HEIGHT = 800; 73 | 74 | /** Holds the tab manager that manages the front-end's tabs. */ 75 | TSharedPtr TabManager; 76 | 77 | TArray RecentFiles; 78 | TMap AESKeyCaches; 79 | }; 80 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SOptionsWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "SOptionsWindow.h" 2 | 3 | //#include "EditorStyleSet.h" 4 | #include "HAL/PlatformMisc.h" 5 | #include "HAL/PlatformApplicationMisc.h" 6 | 7 | #include "CommonDefines.h" 8 | #include "PakAnalyzerModule.h" 9 | 10 | #define LOCTEXT_NAMESPACE "SOptionsWindow" 11 | 12 | SOptionsWindow::SOptionsWindow() 13 | { 14 | } 15 | 16 | SOptionsWindow::~SOptionsWindow() 17 | { 18 | } 19 | 20 | void SOptionsWindow::Construct(const FArguments& Args) 21 | { 22 | int32 DefaultThreadCount = DEFAULT_EXTRACT_THREAD_COUNT; 23 | GConfig->GetInt(TEXT("UnrealPakViewer"), TEXT("ExtractThreadCount"), DefaultThreadCount, GEngineIni); 24 | 25 | const float DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); 26 | const FVector2D InitialWindowDimensions(600, 70); 27 | 28 | SWindow::Construct(SWindow::FArguments() 29 | .Title(LOCTEXT("WindowTitle", "Options")) 30 | .HasCloseButton(true) 31 | .SupportsMaximize(false) 32 | .SupportsMinimize(false) 33 | .SizingRule(ESizingRule::FixedSize) 34 | .ClientSize(InitialWindowDimensions * DPIScaleFactor) 35 | [ 36 | SNew(SBorder) 37 | //.BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) 38 | .Padding(FMargin(5.f, 10.f)) 39 | [ 40 | SNew(SVerticalBox) 41 | 42 | + SVerticalBox::Slot() 43 | .AutoHeight() 44 | [ 45 | SNew(SHorizontalBox) 46 | 47 | + SHorizontalBox::Slot() 48 | .AutoWidth() 49 | .HAlign(EHorizontalAlignment::HAlign_Left) 50 | .VAlign(EVerticalAlignment::VAlign_Center) 51 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 52 | [ 53 | SNew(STextBlock).Text(LOCTEXT("ExtractThreadCountText", "Extract thread count:")) 54 | ] 55 | 56 | + SHorizontalBox::Slot() 57 | .FillWidth(1.f) 58 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 59 | [ 60 | SAssignNew(ThreadCountBox, SSpinBox).MinValue(1).MaxValue(FPlatformMisc::NumberOfCoresIncludingHyperthreads()).Value(DefaultThreadCount) 61 | ] 62 | 63 | + SHorizontalBox::Slot() 64 | .AutoWidth() 65 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 66 | [ 67 | SNew(STextBlock).Text(FText::Format(LOCTEXT("LimitRangeText", "(1 ~ {0})"), FPlatformMisc::NumberOfCoresIncludingHyperthreads())) 68 | ] 69 | ] 70 | 71 | + SVerticalBox::Slot() 72 | .AutoHeight() 73 | .HAlign(HAlign_Right) 74 | .Padding(0.f, 4.f) 75 | [ 76 | SNew(SHorizontalBox) 77 | 78 | + SHorizontalBox::Slot() 79 | .AutoWidth() 80 | .HAlign(HAlign_Right) 81 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 82 | [ 83 | SNew(SBox).MinDesiredWidth(50) 84 | [ 85 | SNew(SButton).HAlign(HAlign_Center).Text(LOCTEXT("ApplyText", "Apply")).OnClicked(this, &SOptionsWindow::OnApply) 86 | ] 87 | ] 88 | 89 | + SHorizontalBox::Slot() 90 | .AutoWidth() 91 | .HAlign(HAlign_Right) 92 | .Padding(FMargin(0.f, 0.f, 5.f, 0.f)) 93 | [ 94 | SNew(SBox).MinDesiredWidth(50) 95 | [ 96 | SNew(SButton).HAlign(HAlign_Center).Text(LOCTEXT("CancelText", "Cancel")).OnClicked(this, &SOptionsWindow::OnCancel) 97 | ] 98 | ] 99 | ] 100 | ] 101 | ] 102 | ); 103 | } 104 | 105 | FReply SOptionsWindow::OnApply() 106 | { 107 | const int32 ThreadCount = ThreadCountBox->GetValueAttribute().Get(); 108 | GConfig->SetInt(TEXT("UnrealPakViewer"), TEXT("ExtractThreadCount"), ThreadCount, GEngineIni); 109 | GConfig->Flush(false, GEngineIni); 110 | 111 | IPakAnalyzerModule::Get().GetPakAnalyzer()->SetExtractThreadCount(ThreadCount); 112 | 113 | RequestDestroyWindow(); 114 | 115 | return FReply::Handled(); 116 | } 117 | 118 | FReply SOptionsWindow::OnCancel() 119 | { 120 | RequestDestroyWindow(); 121 | 122 | return FReply::Handled(); 123 | } 124 | 125 | #undef LOCTEXT_NAMESPACE 126 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SOptionsWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/Input/SSpinBox.h" 5 | #include "Widgets/SWindow.h" 6 | 7 | class SOptionsWindow : public SWindow 8 | { 9 | public: 10 | SLATE_BEGIN_ARGS(SOptionsWindow) 11 | { 12 | } 13 | SLATE_END_ARGS() 14 | 15 | SOptionsWindow(); 16 | virtual ~SOptionsWindow(); 17 | 18 | /** Widget constructor */ 19 | void Construct(const FArguments& Args); 20 | 21 | protected: 22 | FReply OnApply(); 23 | FReply OnCancel(); 24 | 25 | protected: 26 | TSharedPtr> ThreadCountBox; 27 | }; -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SPakClassView.cpp: -------------------------------------------------------------------------------- 1 | #include "SPakClassView.h" 2 | 3 | //#include "EditorStyle.h" 4 | #include "Widgets/Layout/SBorder.h" 5 | #include "Widgets/Layout/SBox.h" 6 | #include "Widgets/Layout/SScrollBar.h" 7 | #include "Widgets/Notifications/SProgressBar.h" 8 | #include "Widgets/SBoxPanel.h" 9 | #include "Widgets/Views/STableRow.h" 10 | #include "Widgets/Views/STableViewBase.h" 11 | 12 | #include "UnrealPakViewerStyle.h" 13 | 14 | #define LOCTEXT_NAMESPACE "SPakClassView" 15 | 16 | //////////////////////////////////////////////////////////////////////////////////////////////////// 17 | // SPakClassRow 18 | //////////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | class SPakClassRow : public SMultiColumnTableRow 21 | { 22 | SLATE_BEGIN_ARGS(SPakClassRow) {} 23 | SLATE_END_ARGS() 24 | 25 | public: 26 | void Construct(const FArguments& InArgs, FPakClassEntryPtr InPakClassItem, const TSharedRef& InOwnerTableView) 27 | { 28 | if (!InPakClassItem.IsValid()) 29 | { 30 | return; 31 | } 32 | 33 | WeakPakClassItem = MoveTemp(InPakClassItem); 34 | 35 | SMultiColumnTableRow::Construct(FSuperRowType::FArguments(), InOwnerTableView); 36 | 37 | TSharedRef Row = ChildSlot.GetChildAt(0); 38 | 39 | ChildSlot 40 | [ 41 | SNew(SBorder) 42 | .BorderImage(new FSlateColorBrush(FLinearColor::White)) 43 | .BorderBackgroundColor(FSlateColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f))) 44 | [ 45 | Row 46 | ] 47 | ]; 48 | } 49 | 50 | virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override 51 | { 52 | if (ColumnName == FClassColumn::ClassColumnName) 53 | { 54 | return 55 | SNew(SBox).Padding(FMargin(4.0, 0.0)) 56 | [ 57 | SNew(STextBlock).Text(this, &SPakClassRow::GetName).ToolTipText(this, &SPakClassRow::GetName).ColorAndOpacity(this, &SPakClassRow::GetNameColor) 58 | ]; 59 | } 60 | else if (ColumnName == FClassColumn::SizeColumnName) 61 | { 62 | return 63 | SNew(SBox).Padding(FMargin(4.0, 0.0)) 64 | [ 65 | SNew(STextBlock).Text(this, &SPakClassRow::GetSize).ToolTipText(this, &SPakClassRow::GetSizeToolTip) 66 | ]; 67 | } 68 | else if (ColumnName == FClassColumn::CompressedSizeColumnName) 69 | { 70 | return 71 | SNew(SBox).Padding(FMargin(4.0, 0.0)) 72 | [ 73 | SNew(STextBlock).Text(this, &SPakClassRow::GetCompressedSize).ToolTipText(this, &SPakClassRow::GetCompressedSizeToolTip) 74 | ]; 75 | } 76 | else if (ColumnName == FClassColumn::FileCountColumnName) 77 | { 78 | return 79 | SNew(SBox).Padding(FMargin(4.0, 0.0)) 80 | [ 81 | SNew(STextBlock).Text(this, &SPakClassRow::GetFileCount) 82 | ]; 83 | } 84 | else if (ColumnName == FClassColumn::PercentOfTotalColumnName) 85 | { 86 | return 87 | SNew(SOverlay) 88 | 89 | + SOverlay::Slot() 90 | [ 91 | SNew(SProgressBar).Percent(this, &SPakClassRow::GetPercentOfTotal) 92 | ] 93 | 94 | + SOverlay::Slot() 95 | .HAlign(HAlign_Center) 96 | [ 97 | SNew(STextBlock) 98 | .Text(this, &SPakClassRow::GetPercentOfTotalText) 99 | .ColorAndOpacity(FLinearColor::Black) 100 | ]; 101 | } 102 | else if (ColumnName == FClassColumn::PercentOfParentColumnName) 103 | { 104 | return 105 | SNew(SOverlay) 106 | 107 | + SOverlay::Slot() 108 | [ 109 | SNew(SProgressBar).Percent(this, &SPakClassRow::GetPercentOfParent) 110 | ] 111 | 112 | + SOverlay::Slot() 113 | .HAlign(HAlign_Center) 114 | [ 115 | SNew(STextBlock) 116 | .Text(this, &SPakClassRow::GetPercentOfParentText) 117 | .ColorAndOpacity(FLinearColor::Black) 118 | ]; 119 | } 120 | else 121 | { 122 | return SNew(STextBlock).Text(LOCTEXT("UnknownColumn", "Unknown Column")); 123 | } 124 | } 125 | 126 | protected: 127 | FText GetName() const 128 | { 129 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 130 | if (PakClassItemPin.IsValid()) 131 | { 132 | return FText::FromName(PakClassItemPin->Class); 133 | } 134 | else 135 | { 136 | return FText(); 137 | } 138 | } 139 | 140 | FSlateColor GetNameColor() const 141 | { 142 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 143 | if (PakClassItemPin.IsValid()) 144 | { 145 | return FSlateColor(FClassColumn::GetColorByClass(*PakClassItemPin->Class.ToString())); 146 | } 147 | else 148 | { 149 | return FLinearColor::White; 150 | } 151 | } 152 | 153 | FText GetSize() const 154 | { 155 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 156 | if (PakClassItemPin.IsValid()) 157 | { 158 | return FText::AsMemory(PakClassItemPin->Size, EMemoryUnitStandard::IEC); 159 | } 160 | else 161 | { 162 | return FText(); 163 | } 164 | } 165 | 166 | FText GetSizeToolTip() const 167 | { 168 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 169 | if (PakClassItemPin.IsValid()) 170 | { 171 | return FText::AsNumber(PakClassItemPin->Size); 172 | } 173 | else 174 | { 175 | return FText(); 176 | } 177 | } 178 | 179 | FText GetCompressedSize() const 180 | { 181 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 182 | if (PakClassItemPin.IsValid()) 183 | { 184 | return FText::AsMemory(PakClassItemPin->CompressedSize, EMemoryUnitStandard::IEC); 185 | } 186 | else 187 | { 188 | return FText(); 189 | } 190 | } 191 | 192 | FText GetCompressedSizeToolTip() const 193 | { 194 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 195 | if (PakClassItemPin.IsValid()) 196 | { 197 | return FText::AsNumber(PakClassItemPin->CompressedSize); 198 | } 199 | else 200 | { 201 | return FText(); 202 | } 203 | } 204 | 205 | FText GetFileCount() const 206 | { 207 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 208 | if (PakClassItemPin.IsValid()) 209 | { 210 | return FText::AsNumber(PakClassItemPin->FileCount); 211 | } 212 | else 213 | { 214 | return FText(); 215 | } 216 | } 217 | 218 | TOptional GetPercentOfTotal() const 219 | { 220 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 221 | if (PakClassItemPin.IsValid()) 222 | { 223 | return PakClassItemPin->PercentOfTotal; 224 | } 225 | else 226 | { 227 | return TOptional(); 228 | } 229 | } 230 | 231 | FText GetPercentOfTotalText() const 232 | { 233 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 234 | if (PakClassItemPin.IsValid()) 235 | { 236 | return FText::FromString(FString::Printf(TEXT("%.2f%%"), PakClassItemPin->PercentOfTotal * 100)); 237 | } 238 | else 239 | { 240 | return FText(); 241 | } 242 | } 243 | 244 | TOptional GetPercentOfParent() const 245 | { 246 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 247 | if (PakClassItemPin.IsValid()) 248 | { 249 | return PakClassItemPin->PercentOfParent; 250 | } 251 | else 252 | { 253 | return TOptional(); 254 | } 255 | } 256 | 257 | FText GetPercentOfParentText() const 258 | { 259 | FPakClassEntryPtr PakClassItemPin = WeakPakClassItem.Pin(); 260 | if (PakClassItemPin.IsValid()) 261 | { 262 | return FText::FromString(FString::Printf(TEXT("%.2f%%"), PakClassItemPin->PercentOfParent * 100)); 263 | } 264 | else 265 | { 266 | return FText(); 267 | } 268 | } 269 | protected: 270 | TWeakPtr WeakPakClassItem; 271 | }; 272 | 273 | //////////////////////////////////////////////////////////////////////////////////////////////////// 274 | // SPakClassView 275 | //////////////////////////////////////////////////////////////////////////////////////////////////// 276 | 277 | SPakClassView::SPakClassView() 278 | { 279 | 280 | } 281 | 282 | SPakClassView::~SPakClassView() 283 | { 284 | } 285 | 286 | void SPakClassView::Construct(const FArguments& InArgs) 287 | { 288 | SAssignNew(ExternalScrollbar, SScrollBar).AlwaysShowScrollbar(false); 289 | 290 | ChildSlot 291 | [ 292 | SNew(SVerticalBox) 293 | 294 | + SVerticalBox::Slot().FillHeight(1.f) 295 | [ 296 | SNew(SBox).VAlign(VAlign_Fill).HAlign(HAlign_Fill) 297 | [ 298 | SNew(SHorizontalBox) 299 | 300 | + SHorizontalBox::Slot().FillWidth(1.f).Padding(0.f).VAlign(VAlign_Fill) 301 | [ 302 | SNew(SScrollBox).Orientation(Orient_Horizontal) 303 | 304 | + SScrollBox::Slot().VAlign(VAlign_Fill) 305 | [ 306 | SNew(SBorder)//.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) 307 | .Padding(0.f) 308 | [ 309 | SAssignNew(ClassListView, SListView) 310 | .ExternalScrollbar(ExternalScrollbar) 311 | .ItemHeight(20.f) 312 | .SelectionMode(ESelectionMode::Multi) 313 | .ListItemsSource(&ClassCache) 314 | .OnGenerateRow(this, &SPakClassView::OnGenerateClassRow) 315 | //.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible) 316 | .HeaderRow 317 | ( 318 | SAssignNew(ClassListHeaderRow, SHeaderRow).Visibility(EVisibility::Visible) 319 | ) 320 | ] 321 | ] 322 | ] 323 | 324 | + SHorizontalBox::Slot().AutoWidth().Padding(0.f) 325 | [ 326 | SNew(SBox).WidthOverride(FOptionalSize(13.f)) 327 | [ 328 | ExternalScrollbar.ToSharedRef() 329 | ] 330 | ] 331 | ] 332 | ] 333 | ]; 334 | 335 | InitializeAndShowHeaderColumns(); 336 | 337 | LastLoadGuid = LexToString(FGuid()); 338 | } 339 | 340 | void SPakClassView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) 341 | { 342 | SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); 343 | } 344 | 345 | void SPakClassView::Reload(FPakTreeEntryPtr InFolder) 346 | { 347 | ClassCache.Empty(); 348 | 349 | if (InFolder.IsValid()) 350 | { 351 | for (const auto& Pair : InFolder->FileClassMap) 352 | { 353 | ClassCache.Add(Pair.Value); 354 | } 355 | } 356 | 357 | Sort(); 358 | ClassListView->RebuildList(); 359 | } 360 | 361 | TSharedRef SPakClassView::OnGenerateClassRow(FPakClassEntryPtr InPakClassItem, const TSharedRef& OwnerTable) 362 | { 363 | return SNew(SPakClassRow, InPakClassItem, OwnerTable); 364 | } 365 | 366 | void SPakClassView::InitializeAndShowHeaderColumns() 367 | { 368 | ClassColumns.Empty(); 369 | 370 | ClassColumns.Emplace(FClassColumn::ClassColumnName, FClassColumn(0, FClassColumn::ClassColumnName, LOCTEXT("ClassColumn", "Class"), LOCTEXT("ClassColumnTip", "Class name in asset registry or file extension if not found"), 200.f)); 371 | ClassColumns.Emplace(FClassColumn::PercentOfTotalColumnName, FClassColumn(1, FClassColumn::PercentOfTotalColumnName, LOCTEXT("PercentOfTotalColumnName", "Percent Of Total"), LOCTEXT("PercentOfTotalColumnTip", "Percent of total compressed file size in pak"), 250.f)); 372 | ClassColumns.Emplace(FClassColumn::PercentOfParentColumnName, FClassColumn(2, FClassColumn::PercentOfParentColumnName, LOCTEXT("PercentOfParentColumnName", "Percent Of Parent"), LOCTEXT("PercentOfParentColumnTip", "Percent of parent folder's compressed file size in pak"), 250.f)); 373 | ClassColumns.Emplace(FClassColumn::CompressedSizeColumnName, FClassColumn(3, FClassColumn::CompressedSizeColumnName, LOCTEXT("CompressedSizeColumn", "Compressed Size"), LOCTEXT("CompressedSizeColumnTip", "Total compressed file size of this class in pak"), 150.f)); 374 | ClassColumns.Emplace(FClassColumn::SizeColumnName, FClassColumn(4, FClassColumn::SizeColumnName, LOCTEXT("SizeColumn", "Size"), LOCTEXT("SizeColumnTip", "Total original file size of this class"), 150.f)); 375 | ClassColumns.Emplace(FClassColumn::FileCountColumnName, FClassColumn(5, FClassColumn::FileCountColumnName, LOCTEXT("FileCountColumn", "File Count"), LOCTEXT("FileCountColumnTip", "Total file count of this class in pak"), 150.f)); 376 | 377 | // Show columns. 378 | for (const auto& ColumnPair : ClassColumns) 379 | { 380 | ShowColumn(ColumnPair.Key); 381 | } 382 | } 383 | 384 | void SPakClassView::ShowColumn(const FName ColumnId) 385 | { 386 | FClassColumn* Column = FindCoulum(ColumnId); 387 | if (!Column) 388 | { 389 | return; 390 | } 391 | 392 | SHeaderRow::FColumn::FArguments ColumnArgs; 393 | ColumnArgs 394 | .ColumnId(Column->GetId()) 395 | .DefaultLabel(Column->GetTitleName()) 396 | .HAlignHeader(HAlign_Fill) 397 | .VAlignHeader(VAlign_Fill) 398 | .HeaderContentPadding(FMargin(2.f)) 399 | .HAlignCell(HAlign_Fill) 400 | .VAlignCell(VAlign_Fill) 401 | .SortMode(this, &SPakClassView::GetSortModeForColumn, Column->GetId()) 402 | .OnSort(this, &SPakClassView::OnSortModeChanged) 403 | .ManualWidth(Column->GetInitialWidth()) 404 | .HeaderContent() 405 | [ 406 | SNew(SBox) 407 | .ToolTipText(Column->GetDescription()) 408 | .HAlign(HAlign_Center) 409 | .VAlign(VAlign_Center) 410 | [ 411 | SNew(STextBlock).Text(Column->GetTitleName()) 412 | ] 413 | ]; 414 | 415 | ClassListHeaderRow->InsertColumn(ColumnArgs, Column->GetIndex()); 416 | } 417 | 418 | FClassColumn* SPakClassView::FindCoulum(const FName ColumnId) 419 | { 420 | return ClassColumns.Find(ColumnId); 421 | } 422 | 423 | EColumnSortMode::Type SPakClassView::GetSortModeForColumn(const FName ColumnId) const 424 | { 425 | if (CurrentSortedColumn != ColumnId) 426 | { 427 | return EColumnSortMode::None; 428 | } 429 | 430 | return CurrentSortMode; 431 | } 432 | 433 | void SPakClassView::OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode) 434 | { 435 | FClassColumn* Column = FindCoulum(ColumnId); 436 | if (!Column) 437 | { 438 | return; 439 | } 440 | 441 | CurrentSortedColumn = ColumnId; 442 | CurrentSortMode = SortMode; 443 | Sort(); 444 | ClassListView->RebuildList(); 445 | } 446 | 447 | void SPakClassView::MarkDirty(bool bInIsDirty) 448 | { 449 | bIsDirty = bInIsDirty; 450 | } 451 | 452 | void SPakClassView::Sort() 453 | { 454 | ClassCache.Sort([this](const FPakClassEntryPtr& A, const FPakClassEntryPtr& B) -> bool 455 | { 456 | if (CurrentSortedColumn == FClassColumn::ClassColumnName) 457 | { 458 | if (CurrentSortMode == EColumnSortMode::Ascending) 459 | { 460 | return A->Class.LexicalLess(B->Class); 461 | } 462 | else 463 | { 464 | return !A->Class.LexicalLess(B->Class); 465 | } 466 | } 467 | else if (CurrentSortedColumn == FClassColumn::FileCountColumnName) 468 | { 469 | if (CurrentSortMode == EColumnSortMode::Ascending) 470 | { 471 | return A->FileCount < B->FileCount; 472 | } 473 | else 474 | { 475 | return A->FileCount > B->FileCount; 476 | } 477 | } 478 | else if (CurrentSortedColumn == FClassColumn::SizeColumnName) 479 | { 480 | if (CurrentSortMode == EColumnSortMode::Ascending) 481 | { 482 | return A->Size < B->Size; 483 | } 484 | else 485 | { 486 | return A->Size > B->Size; 487 | } 488 | } 489 | else if (CurrentSortedColumn == FClassColumn::CompressedSizeColumnName) 490 | { 491 | if (CurrentSortMode == EColumnSortMode::Ascending) 492 | { 493 | return A->CompressedSize < B->CompressedSize; 494 | } 495 | else 496 | { 497 | return A->CompressedSize > B->CompressedSize; 498 | } 499 | } 500 | else if (CurrentSortedColumn == FClassColumn::PercentOfTotalColumnName) 501 | { 502 | if (CurrentSortMode == EColumnSortMode::Ascending) 503 | { 504 | return A->PercentOfTotal < B->PercentOfTotal; 505 | } 506 | else 507 | { 508 | return A->PercentOfTotal > B->PercentOfTotal; 509 | } 510 | } 511 | else if (CurrentSortedColumn == FClassColumn::PercentOfParentColumnName) 512 | { 513 | if (CurrentSortMode == EColumnSortMode::Ascending) 514 | { 515 | return A->PercentOfParent < B->PercentOfParent; 516 | } 517 | else 518 | { 519 | return A->PercentOfParent > B->PercentOfParent; 520 | } 521 | } 522 | 523 | return false; 524 | }); 525 | } 526 | 527 | #undef LOCTEXT_NAMESPACE 528 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SPakClassView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | #include "Widgets/Views/SListView.h" 6 | 7 | #include "PakFileEntry.h" 8 | #include "ViewModels/ClassColumn.h" 9 | 10 | /** Implements the Pak Info window. */ 11 | class SPakClassView : public SCompoundWidget 12 | { 13 | public: 14 | /** Default constructor. */ 15 | SPakClassView(); 16 | 17 | /** Virtual destructor. */ 18 | virtual ~SPakClassView(); 19 | 20 | SLATE_BEGIN_ARGS(SPakClassView) {} 21 | SLATE_END_ARGS() 22 | 23 | /** Constructs this widget. */ 24 | void Construct(const FArguments& InArgs); 25 | 26 | /** 27 | * Ticks this widget. Override in derived classes, but always call the parent implementation. 28 | * 29 | * @param AllottedGeometry - The space allotted for this widget 30 | * @param InCurrentTime - Current absolute real time 31 | * @param InDeltaTime - Real time passed since last tick 32 | */ 33 | virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; 34 | 35 | void Reload(FPakTreeEntryPtr InFolder); 36 | 37 | protected: 38 | /** Generate a new list view row. */ 39 | TSharedRef OnGenerateClassRow(FPakClassEntryPtr InPakClassItem, const TSharedRef& OwnerTable); 40 | 41 | void InitializeAndShowHeaderColumns(); 42 | void ShowColumn(const FName ColumnId); 43 | FClassColumn* FindCoulum(const FName ColumnId); 44 | 45 | EColumnSortMode::Type GetSortModeForColumn(const FName ColumnId) const; 46 | void OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode); 47 | 48 | void MarkDirty(bool bInIsDirty); 49 | 50 | void Sort(); 51 | 52 | protected: 53 | /** External scrollbar used to synchronize file view position. */ 54 | TSharedPtr ExternalScrollbar; 55 | 56 | /** The list view widget. */ 57 | TSharedPtr> ClassListView; 58 | 59 | /** Holds the list view header row widget which display all columns in the list view. */ 60 | TSharedPtr ClassListHeaderRow; 61 | 62 | /** Manage show, hide and sort. */ 63 | TMap ClassColumns; 64 | 65 | /** List of classes to show in list view (i.e. filtered). */ 66 | TArray ClassCache; 67 | 68 | bool bIsDirty = false; 69 | FName CurrentSortedColumn = FClassColumn::CompressedSizeColumnName; 70 | EColumnSortMode::Type CurrentSortMode = EColumnSortMode::Descending; 71 | 72 | FString LastLoadGuid; 73 | }; 74 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SPakFileView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | #include "Widgets/Views/SListView.h" 6 | 7 | #include "ViewModels/FileColumn.h" 8 | #include "PakFileEntry.h" 9 | 10 | /** Implements the Pak Info window. */ 11 | class SPakFileView : public SCompoundWidget 12 | { 13 | public: 14 | /** Default constructor. */ 15 | SPakFileView(); 16 | 17 | /** Virtual destructor. */ 18 | virtual ~SPakFileView(); 19 | 20 | SLATE_BEGIN_ARGS(SPakFileView) {} 21 | SLATE_END_ARGS() 22 | 23 | /** Constructs this widget. */ 24 | void Construct(const FArguments& InArgs); 25 | 26 | /** 27 | * Ticks this widget. Override in derived classes, but always call the parent implementation. 28 | * 29 | * @param AllottedGeometry - The space allotted for this widget 30 | * @param InCurrentTime - Current absolute real time 31 | * @param InDeltaTime - Real time passed since last tick 32 | */ 33 | virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; 34 | 35 | const FFileColumn* FindCoulum(const FName ColumnId) const; 36 | FFileColumn* FindCoulum(const FName ColumnId); 37 | 38 | FText GetSearchText() const; 39 | 40 | FORCEINLINE void SetDelayHighlightItem(const FString& InPath, int32 PakIndex) { DelayHighlightItem = InPath, DelayHighlightItemPakIndex = PakIndex; } 41 | 42 | protected: 43 | bool SearchBoxIsEnabled() const; 44 | void OnSearchBoxTextChanged(const FText& InFilterText); 45 | 46 | /** Generate a new list view row. */ 47 | TSharedRef OnGenerateFileRow(FPakFileEntryPtr InPakFileItem, const TSharedRef& OwnerTable); 48 | 49 | /** Generate a right-click context menu. */ 50 | TSharedPtr OnGenerateContextMenu(); 51 | void OnBuildSortByMenu(FMenuBuilder& MenuBuilder); 52 | void OnBuildCopyColumnMenu(FMenuBuilder& MenuBuilder); 53 | void OnBuildViewColumnMenu(FMenuBuilder& MenuBuilder); 54 | 55 | //////////////////////////////////////////////////////////////////////////////////////////////////// 56 | // File List View - Class Filter 57 | TSharedRef OnBuildClassFilterMenu(); 58 | TSharedRef OnBuildPakFilterMenu(); 59 | 60 | void OnShowAllClassesExecute(); 61 | bool IsShowAllClassesChecked() const; 62 | void OnToggleClassesExecute(FName InClassName); 63 | bool IsClassesFilterChecked(FName InClassName) const; 64 | void FillClassesFilter(); 65 | 66 | void OnShowAllPaksExecute(); 67 | bool IsShowAllPaksChecked() const; 68 | void OnTogglePakExecute(int32 InPakIndex); 69 | bool IsPakFilterChecked(int32 InPakIndex) const; 70 | void FillPaksFilter(); 71 | 72 | //////////////////////////////////////////////////////////////////////////////////////////////////// 73 | // File List View - Columns 74 | void InitializeAndShowHeaderColumns(); 75 | 76 | EColumnSortMode::Type GetSortModeForColumn(const FName ColumnId) const; 77 | void OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode); 78 | 79 | // ShowColumn 80 | bool CanShowColumn(const FName ColumnId) const; 81 | void ShowColumn(const FName ColumnId); 82 | 83 | // HideColumn 84 | bool CanHideColumn(const FName ColumnId); 85 | void HideColumn(const FName ColumnId); 86 | 87 | // ToggleColumnVisibility 88 | bool IsColumnVisible(const FName ColumnId); 89 | bool CanToggleColumnVisibility(const FName ColumnId); 90 | void ToggleColumnVisibility(const FName ColumnId); 91 | 92 | // ShowAllColumns (ContextMenu) 93 | bool OnShowAllColumnsCanExecute() const; 94 | void OnShowAllColumnsExecute(); 95 | 96 | // CopyAllColumns (ContextMenu) 97 | bool HasOneFileSelected() const; 98 | bool HasFileSelected() const; 99 | void OnCopyAllColumnsExecute(); 100 | void OnCopyColumnExecute(const FName ColumnId); 101 | 102 | void OnJumpToTreeViewExecute(); 103 | 104 | void MarkDirty(bool bInIsDirty); 105 | void OnSortAndFilterFinihed(const FName InSortedColumn, EColumnSortMode::Type InSortMode, const FString& InSearchText); 106 | 107 | FText GetFileCount() const; 108 | 109 | // Export 110 | bool IsFileListEmpty() const; 111 | void OnExportToJson(); 112 | void OnExportToCsv(); 113 | void OnExtract(); 114 | 115 | void ScrollToItem(const FString& InPath, int32 PakIndex); 116 | 117 | void OnLoadAssetReigstryFinished(); 118 | void OnLoadPakFinished(); 119 | void OnParseAssetFinished(); 120 | 121 | void FillFilesSummary(); 122 | bool GetSelectedItems(TArray& OutSelectedItems) const; 123 | 124 | protected: 125 | /** The search box widget used to filter items displayed in the file view. */ 126 | TSharedPtr SearchBox; 127 | 128 | /** The list view widget. */ 129 | TSharedPtr> FileListView; 130 | 131 | /** Holds the list view header row widget which display all columns in the list view. */ 132 | TSharedPtr FileListHeaderRow; 133 | 134 | /** List of files to show in list view (i.e. filtered). */ 135 | TArray FileCache; 136 | 137 | /** Manage show, hide and sort. */ 138 | TMap FileColumns; 139 | 140 | /** The async task to sort and filter file on a worker thread */ 141 | TUniquePtr> SortAndFilterTask; 142 | FFileSortAndFilterTask* InnderTask; 143 | 144 | FName CurrentSortedColumn = FFileColumn::OffsetColumnName; 145 | EColumnSortMode::Type CurrentSortMode = EColumnSortMode::Ascending; 146 | FString CurrentSearchText; 147 | 148 | bool bIsDirty = false; 149 | 150 | FString DelayHighlightItem; 151 | int32 DelayHighlightItemPakIndex = -1; 152 | 153 | TMap ClassFilterMap; 154 | 155 | FPakFileEntryPtr FilesSummary; 156 | 157 | struct FPakFilterInfo 158 | { 159 | bool bShow; 160 | FName PakName; 161 | }; 162 | TArray PakFilterMap; 163 | }; 164 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SPakSummaryView.cpp: -------------------------------------------------------------------------------- 1 | #include "SPakSummaryView.h" 2 | 3 | #include "DesktopPlatformModule.h" 4 | //#include "EditorStyle.h" 5 | #include "Framework/Application/SlateApplication.h" 6 | #include "IPlatformFilePak.h" 7 | #include "Misc/Paths.h" 8 | #include "Styling/CoreStyle.h" 9 | #include "Widgets/Layout/SExpandableArea.h" 10 | #include "Widgets/Views/STableRow.h" 11 | 12 | #include "CommonDefines.h" 13 | #include "PakAnalyzerModule.h" 14 | #include "SKeyValueRow.h" 15 | #include "ViewModels/WidgetDelegates.h" 16 | 17 | #define LOCTEXT_NAMESPACE "SPakSummaryView" 18 | 19 | class SSummaryRow : public SMultiColumnTableRow 20 | { 21 | SLATE_BEGIN_ARGS(SSummaryRow) {} 22 | SLATE_END_ARGS() 23 | 24 | public: 25 | void Construct(const FArguments& InArgs, FPakFileSumaryPtr InSummary, const TSharedRef& InOwnerTableView) 26 | { 27 | if (!InSummary.IsValid()) 28 | { 29 | return; 30 | } 31 | 32 | WeakSummary = MoveTemp(InSummary); 33 | 34 | SMultiColumnTableRow::Construct(FSuperRowType::FArguments().Padding(FMargin(0.f, 2.f)), InOwnerTableView); 35 | } 36 | 37 | virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override 38 | { 39 | static const float LeftMargin = 4.f; 40 | 41 | FPakFileSumaryPtr Summary = WeakSummary.Pin(); 42 | if (!Summary.IsValid()) 43 | { 44 | return SNew(STextBlock).Text(LOCTEXT("NullColumn", "Null")).Margin(FMargin(LeftMargin, 0.f, 0.f, 0.f)); 45 | } 46 | 47 | TSharedRef RowContent = SNullWidget::NullWidget; 48 | 49 | if (ColumnName == "Name") 50 | { 51 | RowContent = SNew(STextBlock).Text(FText::FromString(FPaths::GetCleanFilename(Summary->PakFilePath))).ToolTipText(FText::FromString(Summary->PakFilePath)); 52 | } 53 | else if (ColumnName == "MountPoint") 54 | { 55 | RowContent = SNew(STextBlock).Text(FText::FromString(Summary->MountPoint)).ToolTipText(FText::FromString(Summary->MountPoint)).Margin(FMargin(LeftMargin, 0.f, 0.f, 0.f)); 56 | } 57 | else if (ColumnName == "Version") 58 | { 59 | RowContent = SNew(STextBlock).Text(FText::AsNumber(Summary->PakInfo.Version)).Justification(ETextJustify::Center); 60 | } 61 | else if (ColumnName == "FileCount") 62 | { 63 | RowContent = SNew(STextBlock).Text(FText::AsNumber(Summary->FileCount)).Justification(ETextJustify::Center); 64 | } 65 | else if (ColumnName == "PakSize") 66 | { 67 | RowContent = SNew(STextBlock).Text(FText::AsMemory(Summary->PakFileSize, EMemoryUnitStandard::IEC)).ToolTipText(FText::AsNumber(Summary->PakFileSize)).Justification(ETextJustify::Center); 68 | } 69 | else if (ColumnName == "ContentSize") 70 | { 71 | RowContent = SNew(STextBlock).Text(FText::AsMemory(Summary->PakInfo.IndexOffset, EMemoryUnitStandard::IEC)).ToolTipText(FText::AsNumber(Summary->PakInfo.IndexOffset)).Justification(ETextJustify::Center); 72 | } 73 | else if (ColumnName == "HeaderSize") 74 | { 75 | RowContent = SNew(STextBlock).Text(FText::AsMemory(Summary->PakFileSize - Summary->PakInfo.IndexOffset - Summary->PakInfo.IndexSize, EMemoryUnitStandard::IEC)).ToolTipText(FText::AsNumber(Summary->PakFileSize - Summary->PakInfo.IndexOffset - Summary->PakInfo.IndexSize)).Justification(ETextJustify::Center); 76 | } 77 | else if (ColumnName == "IndexSize") 78 | { 79 | RowContent = SNew(STextBlock).Text(FText::AsMemory(Summary->PakInfo.IndexSize, EMemoryUnitStandard::IEC)).ToolTipText(FText::AsNumber(Summary->PakInfo.IndexSize)).Justification(ETextJustify::Center); 80 | } 81 | else if (ColumnName == "IndexHash") 82 | { 83 | RowContent = SNew(STextBlock).Text(FText::FromString(LexToString(Summary->PakInfo.IndexHash))); 84 | } 85 | else if (ColumnName == "IndexEncrypted") 86 | { 87 | RowContent = SNew(STextBlock).Text(FText::FromString(Summary->PakInfo.bEncryptedIndex ? TEXT("True") : TEXT("False"))).Justification(ETextJustify::Center); 88 | } 89 | else if (ColumnName == "CompressionMethods") 90 | { 91 | RowContent = SNew(STextBlock).Text(FText::FromString(Summary->CompressionMethods)).ToolTipText(FText::FromString(Summary->CompressionMethods)); 92 | } 93 | else if (ColumnName == "DecryptAES") 94 | { 95 | RowContent = SNew(STextBlock).Text(FText::FromString(Summary->DecryptAESKeyStr)).ToolTipText(FText::FromString(Summary->DecryptAESKeyStr)).Margin(FMargin(LeftMargin, 0.f, 0.f, 0.f)); 96 | } 97 | else 98 | { 99 | RowContent = SNew(STextBlock).Text(LOCTEXT("UnknownColumn", "Unknown Column")).Margin(FMargin(LeftMargin, 0.f, 0.f, 0.f)); 100 | } 101 | 102 | return SNew(SBox).VAlign(VAlign_Center) 103 | [ 104 | RowContent 105 | ]; 106 | } 107 | 108 | protected: 109 | TWeakPtr WeakSummary; 110 | }; 111 | 112 | SPakSummaryView::SPakSummaryView() 113 | { 114 | FPakAnalyzerDelegates::OnPakLoadFinish.AddRaw(this, &SPakSummaryView::OnLoadPakFinished); 115 | } 116 | 117 | SPakSummaryView::~SPakSummaryView() 118 | { 119 | FPakAnalyzerDelegates::OnPakLoadFinish.RemoveAll(this); 120 | } 121 | 122 | void SPakSummaryView::Construct(const FArguments& InArgs) 123 | { 124 | ChildSlot 125 | [ 126 | SNew(SVerticalBox) 127 | 128 | + SVerticalBox::Slot() 129 | .AutoHeight() 130 | [ 131 | SNew(SExpandableArea) 132 | .InitiallyCollapsed(true) 133 | .AreaTitle(LOCTEXT("PakSumary", "Pak Summary")) 134 | .BodyContent() 135 | [ 136 | SAssignNew(SummaryListView, SListView) 137 | .ItemHeight(25.f) 138 | .SelectionMode(ESelectionMode::Single) 139 | .ListItemsSource(&Summaries) 140 | .OnGenerateRow(this, &SPakSummaryView::OnGenerateSummaryRow) 141 | .HeaderRow 142 | ( 143 | SNew(SHeaderRow).Visibility(EVisibility::Visible) 144 | 145 | + SHeaderRow::Column(FName("Name")) 146 | .FillWidth(1.f) 147 | .DefaultLabel(LOCTEXT("Package_Summary_Name", "Name")) 148 | 149 | + SHeaderRow::Column(FName("MountPoint")) 150 | .FillWidth(1.f) 151 | .DefaultLabel(LOCTEXT("Package_Summary_MountPoint", "Mount Point")) 152 | 153 | + SHeaderRow::Column(FName("Version")) 154 | .FillWidth(1.f) 155 | .DefaultLabel(LOCTEXT("Package_Summary_Version", "Version")) 156 | 157 | + SHeaderRow::Column(FName("FileCount")) 158 | .FillWidth(1.f) 159 | .DefaultLabel(LOCTEXT("Package_Summary_FileCount", "File Count")) 160 | 161 | + SHeaderRow::Column(FName("PakSize")) 162 | .FillWidth(1.f) 163 | .DefaultLabel(LOCTEXT("Package_Summary_PakSize", "Pak Size")) 164 | 165 | + SHeaderRow::Column(FName("ContentSize")) 166 | .FillWidth(1.f) 167 | .DefaultLabel(LOCTEXT("Package_Summary_ContentSize", "Content Size")) 168 | 169 | + SHeaderRow::Column(FName("HeaderSize")) 170 | .FillWidth(1.f) 171 | .DefaultLabel(LOCTEXT("Package_Summary_HeaderSize", "Header Size")) 172 | 173 | + SHeaderRow::Column(FName("IndexSize")) 174 | .FillWidth(1.f) 175 | .DefaultLabel(LOCTEXT("Package_Summary_IndexSize", "Index Size")) 176 | 177 | + SHeaderRow::Column(FName("IndexHash")) 178 | .FillWidth(1.f) 179 | .DefaultLabel(LOCTEXT("Package_Summary_IndexHash", "Index Hash")) 180 | 181 | + SHeaderRow::Column(FName("IndexEncrypted")) 182 | .FillWidth(1.f) 183 | .DefaultLabel(LOCTEXT("Package_Summary_IndexEncrypted", "Index Encrypted")) 184 | 185 | + SHeaderRow::Column(FName("CompressionMethods")) 186 | .FillWidth(1.f) 187 | .DefaultLabel(LOCTEXT("Package_Summary_CompressionMethods", "Compression Methods")) 188 | 189 | + SHeaderRow::Column(FName("DecryptAES")) 190 | .FillWidth(1.f) 191 | .DefaultLabel(LOCTEXT("Package_Summary_DecryptAES", "Decrypt AES")) 192 | ) 193 | ] 194 | ] 195 | 196 | + SVerticalBox::Slot() 197 | .AutoHeight() 198 | .Padding(2.f, 0.f) 199 | [ 200 | SNew(SHorizontalBox) 201 | 202 | + SHorizontalBox::Slot().AutoWidth().Padding(2.f, 0.f, 5.f, 0.f).VAlign(VAlign_Center) 203 | [ 204 | SNew(STextBlock).Text(LOCTEXT("AssetRegistryText", "AssetRegistry:")).ColorAndOpacity(FLinearColor::Green).ShadowOffset(FVector2D(1.f, 1.f)) 205 | ] 206 | 207 | + SHorizontalBox::Slot().FillWidth(1.f).Padding(0.f, 0.f, 5.f, 0.f) 208 | [ 209 | SNew(SEditableTextBox).IsReadOnly(true).Text(this, &SPakSummaryView::GetAssetRegistryPath) 210 | ] 211 | 212 | + SHorizontalBox::Slot().AutoWidth().Padding(0.f, 0.f, 0.f, 0.f).VAlign(VAlign_Center) 213 | [ 214 | SNew(SButton).Text(LOCTEXT("LoadAssetRegistryText", "Load Asset Registry")).OnClicked(this, &SPakSummaryView::OnLoadAssetRegistry).ToolTipText(LOCTEXT("LoadAssetRegistryTipText", "Default in the path: [Your Project Path]/Saved/Cooked/[PLATFORM]/ProjectName")) 215 | ] 216 | ] 217 | ]; 218 | } 219 | 220 | FORCEINLINE FText SPakSummaryView::GetAssetRegistryPath() const 221 | { 222 | IPakAnalyzer* PakAnalyzer = IPakAnalyzerModule::Get().GetPakAnalyzer(); 223 | 224 | return PakAnalyzer ? FText::FromString(PakAnalyzer->GetAssetRegistryPath()) : FText(); 225 | } 226 | 227 | void SPakSummaryView::OnLoadPakFinished() 228 | { 229 | IPakAnalyzer* PakAnalyzer = IPakAnalyzerModule::Get().GetPakAnalyzer(); 230 | if (PakAnalyzer) 231 | { 232 | Summaries = PakAnalyzer->GetPakFileSumary(); 233 | 234 | if (SummaryListView.IsValid()) 235 | { 236 | SummaryListView->RebuildList(); 237 | } 238 | } 239 | } 240 | 241 | FReply SPakSummaryView::OnLoadAssetRegistry() 242 | { 243 | IPakAnalyzer* PakAnalyzer = IPakAnalyzerModule::Get().GetPakAnalyzer(); 244 | if (!PakAnalyzer) 245 | { 246 | return FReply::Handled(); 247 | } 248 | 249 | TArray OutFiles; 250 | bool bOpened = false; 251 | 252 | IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); 253 | if (DesktopPlatform) 254 | { 255 | FSlateApplication::Get().CloseToolTip(); 256 | 257 | bOpened = DesktopPlatform->OpenFileDialog 258 | ( 259 | FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), 260 | LOCTEXT("LoadAssetRegistry_FileDesc", "Open asset registry file...").ToString(), 261 | TEXT(""), 262 | TEXT(""), 263 | LOCTEXT("LoadAssetRegistry_FileFilter", "Asset regirstry files (*.bin)|*.bin|All files (*.*)|*.*").ToString(), 264 | EFileDialogFlags::None, 265 | OutFiles 266 | ); 267 | } 268 | 269 | if (bOpened && OutFiles.Num() > 0) 270 | { 271 | if (PakAnalyzer->LoadAssetRegistry(OutFiles[0])) 272 | { 273 | FWidgetDelegates::GetOnLoadAssetRegistryFinishedDelegate().Broadcast(); 274 | } 275 | } 276 | return FReply::Handled(); 277 | } 278 | 279 | TSharedRef SPakSummaryView::OnGenerateSummaryRow(FPakFileSumaryPtr InSummary, const TSharedRef& OwnerTable) 280 | { 281 | return SNew(SSummaryRow, InSummary, OwnerTable); 282 | } 283 | 284 | #undef LOCTEXT_NAMESPACE 285 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SPakSummaryView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | #include "Widgets/Views/SListView.h" 6 | 7 | #include "PakFileEntry.h" 8 | 9 | /** Implements the Pak Info window. */ 10 | class SPakSummaryView : public SCompoundWidget 11 | { 12 | public: 13 | /** Default constructor. */ 14 | SPakSummaryView(); 15 | 16 | /** Virtual destructor. */ 17 | virtual ~SPakSummaryView(); 18 | 19 | SLATE_BEGIN_ARGS(SPakSummaryView) {} 20 | SLATE_END_ARGS() 21 | 22 | /** Constructs this widget. */ 23 | void Construct(const FArguments& InArgs); 24 | 25 | protected: 26 | FORCEINLINE FText GetAssetRegistryPath() const; 27 | 28 | void OnLoadPakFinished(); 29 | FReply OnLoadAssetRegistry(); 30 | 31 | TSharedRef OnGenerateSummaryRow(FPakFileSumaryPtr InSummary, const TSharedRef& OwnerTable); 32 | 33 | protected: 34 | TSharedPtr> SummaryListView; 35 | TArray Summaries; 36 | }; 37 | -------------------------------------------------------------------------------- /UnrealPakViewer/Private/Widgets/SPakTreeView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | 6 | #include "PakFileEntry.h" 7 | 8 | class SKeyValueRow; 9 | class SVerticalBox; 10 | 11 | /** Implements the Pak Info window. */ 12 | class SPakTreeView : public SCompoundWidget 13 | { 14 | public: 15 | /** Default constructor. */ 16 | SPakTreeView(); 17 | 18 | /** Virtual destructor. */ 19 | virtual ~SPakTreeView(); 20 | 21 | SLATE_BEGIN_ARGS(SPakTreeView) {} 22 | SLATE_END_ARGS() 23 | 24 | /** Constructs this widget. */ 25 | void Construct(const FArguments& InArgs); 26 | 27 | /** 28 | * Ticks this widget. Override in derived classes, but always call the parent implementation. 29 | * 30 | * @param AllottedGeometry - The space allotted for this widget 31 | * @param InCurrentTime - Current absolute real time 32 | * @param InDeltaTime - Real time passed since last tick 33 | */ 34 | virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; 35 | 36 | FORCEINLINE void SetDelayHighlightItem(const FString& InPath, int32 PakIndex) { DelayHighlightItem = InPath; DelayHighlightItemPakIndex = PakIndex; } 37 | 38 | protected: 39 | //////////////////////////////////////////////////////////////////////////////////////////////////// 40 | // Tree View - Table Row 41 | 42 | /** Called by STreeView to generate a table row for the specified item. */ 43 | TSharedRef OnGenerateTreeRow(FPakTreeEntryPtr TreeNode, const TSharedRef& OwnerTable); 44 | void OnGetTreeNodeChildren(FPakTreeEntryPtr InParent, TArray& OutChildren); 45 | 46 | /** Called by STreeView when selection has changed. */ 47 | void OnSelectionChanged(FPakTreeEntryPtr SelectedItem, ESelectInfo::Type SelectInfo); 48 | 49 | void ExpandTreeItem(const FString& InPath, int32 PakIndex); 50 | 51 | // Detail View 52 | FORCEINLINE FText GetSelectionName() const; 53 | FORCEINLINE FText GetSelectionPath() const; 54 | FORCEINLINE FText GetSelectionOffset() const; 55 | FORCEINLINE FText GetSelectionSize() const; 56 | FORCEINLINE FText GetSelectionSizeToolTip() const; 57 | FORCEINLINE FText GetSelectionCompressedSize() const; 58 | FORCEINLINE FText GetSelectionCompressedSizeToolTip() const; 59 | FORCEINLINE FText GetSelectionCompressedSizePercentOfTotal() const; 60 | FORCEINLINE FText GetSelectionCompressedSizePercentOfParent() const; 61 | FORCEINLINE FText GetSelectionCompressionBlockCount() const; 62 | FORCEINLINE FText GetSelectionCompressionBlockSize() const; 63 | FORCEINLINE FText GetSelectionCompressionBlockSizeToolTip() const; 64 | FORCEINLINE FText GetCompressionMethod() const; 65 | FORCEINLINE FText GetSelectionSHA1() const; 66 | FORCEINLINE FText GetSelectionIsEncrypted() const; 67 | FORCEINLINE FText GetSelectionOwnerPakName() const; 68 | FORCEINLINE FText GetSelectionOwnerPakPath() const; 69 | FORCEINLINE FText GetSelectionFileCount() const; 70 | FORCEINLINE const FSlateBrush* GetFolderImage(FPakTreeEntryPtr InTreeNode) const; 71 | FORCEINLINE FText GetSelectionClass() const; 72 | 73 | //////////////////////////////////////////////////////////////////////////////////////////////////// 74 | // Tree View - Context Menu 75 | TSharedPtr OnGenerateContextMenu(); 76 | 77 | void OnExtractExecute(); 78 | void OnJumpToFileViewExecute(); 79 | bool HasSelection() const; 80 | bool HasFileSelection() const; 81 | void OnExportToJson(); 82 | void OnExportToCsv(); 83 | 84 | void RetriveFiles(FPakTreeEntryPtr InRoot, TArray& OutFiles); 85 | 86 | void OnLoadPakFinished(); 87 | void OnLoadAssetReigstryFinished(); 88 | void OnParseAssetFinished(); 89 | 90 | protected: 91 | TSharedPtr> TreeView; 92 | FPakTreeEntryPtr CurrentSelectedItem; 93 | 94 | /** The root node(s) of the tree. */ 95 | TArray TreeNodes; 96 | 97 | TSharedPtr KeyValueBox; 98 | TSharedPtr OffsetRow; 99 | TSharedPtr CompressionBlockCountRow; 100 | TSharedPtr CompressionBlockSizeRow; 101 | TSharedPtr CompressionMethodRow; 102 | TSharedPtr SHA1Row; 103 | TSharedPtr IsEncryptedRow; 104 | TSharedPtr FileCountRow; 105 | TSharedPtr OwnerPakRow; 106 | TSharedPtr ClassView; 107 | TSharedPtr ClassRow; 108 | TSharedPtr AssetSummaryView; 109 | 110 | FString DelayHighlightItem; 111 | int32 DelayHighlightItemPakIndex = -1; 112 | }; 113 | -------------------------------------------------------------------------------- /UnrealPakViewer/UnrealPakViewer.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | #if UE_5_0_OR_LATER 5 | using EpicGames.Core; 6 | #else 7 | using Tools.DotNETCommon; 8 | #endif 9 | 10 | using System.IO; 11 | using Microsoft.Extensions.Logging; 12 | 13 | public class UnrealPakViewer : ModuleRules 14 | { 15 | public UnrealPakViewer(ReadOnlyTargetRules Target) : base(Target) 16 | { 17 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 18 | PublicIncludePaths.Add(Path.Combine(EngineDirectory, "Source/Runtime/Launch/Public")); 19 | PrivateIncludePaths.Add(Path.Combine(EngineDirectory, "Source/Runtime/Launch/Private")); // For LaunchEngineLoop.cpp include 20 | 21 | PrivateDependencyModuleNames.AddRange( 22 | new string[] 23 | { 24 | "AppFramework", 25 | "Core", 26 | "ApplicationCore", 27 | "Slate", 28 | "SlateCore", 29 | "StandaloneRenderer", 30 | "DesktopPlatform", 31 | "Projects", 32 | //"EditorStyle", 33 | "PakAnalyzer", 34 | "Json", 35 | } 36 | ); 37 | 38 | if (Target.Platform == UnrealTargetPlatform.Linux) 39 | { 40 | PrivateDependencyModuleNames.AddRange( 41 | new string[] { 42 | "UnixCommonStartup" 43 | } 44 | ); 45 | } 46 | 47 | DirectoryReference EngineSourceProgramsDirectory = new DirectoryReference(Path.Combine(EngineDirectory, "Source", "Programs")); 48 | FileReference CurrentModuleDirectory = new FileReference(ModuleDirectory); 49 | if (!CurrentModuleDirectory.IsUnderDirectory(EngineSourceProgramsDirectory)) 50 | { 51 | string ProjectName = Target.ProjectFile.GetFileNameWithoutExtension(); 52 | Logger.LogInformation("UnrealPakViewer is outside engine source directory, parent project is: {ProjectName}", ProjectName); 53 | 54 | PrivateDefinitions.Add(string.Format("ParentProjectName=TEXT(\"{0}\")", ProjectName)); 55 | } 56 | } 57 | } 58 | --------------------------------------------------------------------------------