using CUE4Parse.Compression; using CUE4Parse.Encryption.Aes; using CUE4Parse.FileProvider; using CUE4Parse.MappingsProvider.Usmap; using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Versions; using CUE4Parse.UE4.VirtualFileSystem; using CUE4Parse_Conversion.Textures; using Newtonsoft.Json; struct CharacterInfo { public string name; public int idx; public string iconFilePath; } struct ItemInfo { public string id; public string name; public string iconFilePath; } class Dumper { private DefaultFileProvider? _provider; private IAesVfsReader? _dataPak; private Logger _log; private string _outDir; private readonly Dictionary _characterMap = new(); private readonly Dictionary _itemMap = new(); public Dumper(string outDir) { _log = new Logger("Dumper"); if (string.IsNullOrWhiteSpace(outDir)) throw new ArgumentException("Output directory cannot be null or empty.", nameof(outDir)); string fullPath = Path.GetFullPath(outDir); if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath); _outDir = fullPath; } public bool Init() { /* * mapping */ string baseDir = AppDomain.CurrentDomain.BaseDirectory; string mappingPath = Path.GetFullPath(Path.Combine(baseDir, "mapping.usmap")); if (!File.Exists(mappingPath)) return false; /* * compression */ ZlibHelper.Initialize(); OodleHelper.Initialize(Path.Combine(baseDir, OodleHelper.OodleFileName)); /* * game path */ string? gamePath = Utils.GetGamePath(); if (gamePath == null) return false; string pakDir = Path.Combine(gamePath, "DeadByDaylight", "Content", "Paks"); if (!Directory.Exists(pakDir)) { Console.WriteLine("PAK dir does not exist. (Invalid install?)"); return false; } /* * file provider */ VersionContainer? version = new VersionContainer(EGame.GAME_DeadByDaylight, ETexturePlatform.DesktopMobile); _provider = new DefaultFileProvider(pakDir, SearchOption.TopDirectoryOnly, version) { MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath) }; _provider.Initialize(); _provider.SubmitKey(new FGuid(), new FAesKey(Constants.AESKey)); _provider.Mount(); _provider.PostMount(); if (!_provider.TryGetArchive("pakchunk4-WinGDK.utoc", out _dataPak)) // data { Console.WriteLine("Failed to load pakchunk4-WinGDK.utoc"); return false; } _provider.LoadLocalization(); return true; } /* * dumping functions */ public void DumpCharacters() { if (_dataPak == null || _provider == null) throw new InvalidOperationException("Attempted to call dump function without dumper initialization"); _log.Info("Dumping characters"); List charDbPaths = _dataPak.Files.Keys.Where(x => x.Contains("/CharacterDescriptionDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); foreach (string path in charDbPaths) { string cleanPath = path.Contains('.') ? path[..path.LastIndexOf('.')] : path; if (_provider.TryLoadPackageObject(cleanPath, out UDataTable? dataTable)) { foreach (KeyValuePair row in dataTable.RowMap) { List props = row.Value.Properties; if (!TryGetProp(props, "CharacterIndex", out int charIndex)) throw new KeyNotFoundException("CharacterIndex was not found."); if (charIndex == -1) continue; if (!TryGetStringProp(props, "DisplayName", out string charName) || !TryGetStringProp(props, "IconFilePath", out string charIconFilePath)) throw new KeyNotFoundException("DisplayName or IconFilePath was not found."); _characterMap[charIndex] = new CharacterInfo { name = charName, idx = charIndex, iconFilePath = charIconFilePath }; } } } _log.Info("Dumped {0} characters", _characterMap.Count); WriteJson("characters", _characterMap.Values); return; } public void DumpCharacterIcons() { if (_provider == null) throw new InvalidOperationException("Attempted to call dump function without dumper initialization"); _log.Info("Dumping character icons"); foreach (CharacterInfo info in _characterMap.Values) ExportIcon(info.iconFilePath, "/character-icons/"); _log.Info("Dumped all character icons"); return; } public void DumpItems() { if (_dataPak == null || _provider == null) throw new InvalidOperationException("Attempted to call dump function without dumper initialization"); _log.Info("Dumping character icons"); List itemDBPaths = _dataPak.Files.Keys.Where(x => x.Contains("/ItemDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); foreach (string path in itemDBPaths) { string cleanPath = path.Contains('.') ? path[..path.LastIndexOf('.')] : path; if (_provider.TryLoadPackageObject(cleanPath, out UDataTable? dataTable)) { foreach (KeyValuePair row in dataTable.RowMap) { List props = row.Value.Properties; string itemId = row.Key.Text; if (!TryGetProp(props, "Type", out EInventoryItemType itemType) || !TryGetProp(props, "UIData", out FStructFallback uiDataFb)) throw new KeyNotFoundException("Type or UIData was not found."); UIDataStruct uiData = uiDataFb.MapToStruct(); if (itemType != EInventoryItemType.Item) { _log.Verbose("Ignoring invalid item type ({0})", itemType.ToString()); continue; } if (uiData.IconAssetList.Length == 0) throw new InvalidDataException("Item's UIData had no icons"); _itemMap[itemId] = new ItemInfo { id = itemId, name = uiData.DisplayName.ToString(), iconFilePath = uiData.IconAssetList[0].ToString() }; } } } WriteJson("items", _itemMap.Values); _log.Info("Dumped all items"); } /* * internal helper functions */ private bool TryGetProp(IEnumerable properties, string propName, out T value) { FPropertyTag? prop = properties.FirstOrDefault(p => p.Name.Text == propName); if (prop != null && prop.Tag != null) { T? val = prop.Tag.GetValue(); if (val != null) { value = val; return true; } } _log.Error("Character missing or invalid property: {0}", propName); value = default!; return false; } private bool TryGetStringProp(IEnumerable properties, string propName, out string value) { if (TryGetProp(properties, propName, out T? rawValue) && rawValue != null) { string? strVal = rawValue.ToString(); if (!string.IsNullOrEmpty(strVal)) { value = strVal; return true; } } _log.Error($"Property '{propName}' returned an empty or null string."); value = string.Empty; return false; } private void WriteJson(string name, object data) { string json = JsonConvert.SerializeObject(data, Formatting.Indented); File.WriteAllText(_outDir + name + ".json", json); } private void ExportIcon(string assetPath, string outPath) { if (_provider == null) throw new InvalidOperationException("Attempted to export icon when dumper was not initialized/invalid"); string cleanPath = assetPath.Contains('.') ? assetPath[..assetPath.LastIndexOf('.')] : assetPath; string fileName = Path.GetFileName(cleanPath) + ".png"; string relativeOutPath = outPath.TrimStart('/', '\\'); string fullPath = Path.Combine(_outDir, relativeOutPath, fileName); if (cleanPath.StartsWith("UI/Icons/")) cleanPath = "/Game/UI/UMGAssets/" + cleanPath["UI/".Length..]; if (File.Exists(fullPath)) return; // special thanks to whichever dev kept fucking up the casing if (!_provider.TryLoadPackage(cleanPath, out IPackage? package)) { _log.Verbose("Failed exact path match for {0}. Attempting case-insensitive search", cleanPath); string searchSuffix = cleanPath; if (searchSuffix.StartsWith("/Game/")) searchSuffix = searchSuffix["/Game/".Length..]; if (!searchSuffix.EndsWith(".uasset")) searchSuffix += ".uasset"; var actualFile = _provider.Files.FirstOrDefault(kvp => kvp.Key.EndsWith(searchSuffix, StringComparison.OrdinalIgnoreCase)); if (actualFile.Value != null) _provider.TryLoadPackage(actualFile.Value, out package); } if (package != null) { UTexture2D? texture = package.GetExports().OfType().FirstOrDefault(); if (texture != null) { CTexture? bitmap = texture.Decode(ETexturePlatform.DesktopMobile); if (bitmap != null) { byte[] bytes = bitmap.Encode(ETextureFormat.Png, false, out _); string? parentFolder = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(parentFolder)) Directory.CreateDirectory(parentFolder); File.WriteAllBytes(fullPath, bytes); _log.Verbose("Exported icon: {0}", fileName); } else throw new InvalidDataException("Bitmap was invalid"); } else throw new FileNotFoundException("Failed to find texture"); } else throw new FileNotFoundException("Failed to find texture package"); } }