using CUE4Parse.Compression; using CUE4Parse.Encryption.Aes; using CUE4Parse.FileProvider; using CUE4Parse.FileProvider.Objects; 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; } struct OfferingInfo { public string id; public string name; public string iconFilePath; public EPlayerRole role; } struct DLCInfo { public string id; public string? name; public Dictionary dlcIds; } class Dumper { private DefaultFileProvider? _provider; private IAesVfsReader? _dataPak; private Logger _log; private string _outDir; private readonly Dictionary _characterMap = new(); private readonly Dictionary _itemMap = new(); private readonly Dictionary _offeringMap = 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() { ProcessDataTables("/CharacterDescriptionDB.uasset", "characters", (rowKey, props) => { if (!TryGetProp(props, "CharacterIndex", out int charIndex)) throw new KeyNotFoundException("CharacterIndex was not found."); if (charIndex == -1) return; // there is a -1 character (template, placeholder, spectator or smth) 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); } public void DumpItems() { ProcessDataTables("/ItemDB.uasset", "items", (rowKey, props) => { if (!TryGetProp(props, "Type", out EInventoryItemType itemType) || !TryGetProp(props, "UIData", out FStructFallback uiDataFb) || !TryGetProp(props, "Role", out EPlayerRole role) || !TryGetProp(props, "Inventory", out bool isInventory) || !TryGetProp(props, "IsFakeItem", out bool isFakeItem)) throw new KeyNotFoundException($"Required properties missing for Item: {rowKey}"); UIDataStruct uiData = uiDataFb.MapToStruct(); if (isFakeItem || !isInventory || itemType != EInventoryItemType.Item || role != EPlayerRole.VE_Camper) return; if (uiData.IconAssetList.Length == 0) throw new InvalidDataException("Item's UIData had no icons"); _itemMap[rowKey] = new ItemInfo { id = rowKey, name = uiData.DisplayName.ToString(), iconFilePath = uiData.IconAssetList[0].ToString() }; }); _log.Info("Dumped {0} items", _itemMap.Count); WriteJson("items", _itemMap.Values); } public void DumpOfferings() { ProcessDataTables("/OfferingDB.uasset", "offerings", (rowKey, props) => { if (!TryGetProp(props, "Role", out EPlayerRole role) || !TryGetProp(props, "UIData", out FStructFallback uiDataFb) || !TryGetProp(props, "Inventory", out bool isInventory) || !TryGetProp(props, "IsFakeItem", out bool isFakeItem)) throw new KeyNotFoundException($"Required properties missing for offering: {rowKey}"); UIDataStruct uiData = uiDataFb.MapToStruct(); if (!isInventory || isFakeItem) return; if (uiData.IconAssetList.Length == 0) throw new InvalidDataException("Offerings's UIData had no icons"); _offeringMap[rowKey] = new OfferingInfo { id = rowKey, name = uiData.DisplayName.ToString(), iconFilePath = uiData.IconAssetList[0].ToString(), role = role }; }); _log.Info("Dumped {0} offerings", _offeringMap.Count); WriteJson("offerings", _offeringMap.Values); } public void DumpDLCs() { List dlcList = new List(); ProcessDataTables("/DlcDB.uasset", "dlcs", (rowKey, props) => { if (!TryGetProp(props, "DlcIdSteam", out string steamId) || !TryGetProp(props, "DlcIdEpic", out string epicId) || !TryGetProp(props, "DlcIdGRDK", out string grdkId) ) throw new KeyNotFoundException($"Required properties missing for DLC: {rowKey}"); string? displayName = null; if (TryGetStringProp(props, "DisplayName", out string foundName) && !string.IsNullOrWhiteSpace(foundName)) displayName = foundName; DLCInfo info = new DLCInfo { id = rowKey, name = displayName, dlcIds = new Dictionary { { "steam", steamId }, { "epic", epicId }, { "grdk", grdkId } } }; dlcList.Add(info); }); _log.Info("Dumped {0} dlcs", dlcList.Count); WriteJson("dlcs", dlcList.ToArray()); } public void DumpCharacterIcons() => ExportIcons(_characterMap.Values.Select(x => x.iconFilePath), "character", "/character-icons/"); public void DumpItemIcons() => ExportIcons(_itemMap.Values.Select(x => x.iconFilePath), "item", "/item-icons/"); public void DumpOfferingIcons() => ExportIcons(_offeringMap.Values.Select(x => x.iconFilePath), "offering", "/offering-icons/"); /* * bulk functions for dumping */ private void ProcessDataTables(string pathFilter, string logName, Action> rowProcessor) { if (_dataPak == null || _provider == null) throw new InvalidOperationException("Attempted to call dump function without dumper initialization/state"); _log.Info($"Dumping {logName}"); var dbPaths = _dataPak.Files.Keys.Where(x => x.Contains(pathFilter, StringComparison.OrdinalIgnoreCase)); foreach (string path in dbPaths) { string cleanPath = path.Contains('.') ? path[..path.LastIndexOf('.')] : path; if (_provider.TryLoadPackageObject(cleanPath, out UDataTable? dataTable)) { foreach (KeyValuePair row in dataTable.RowMap) rowProcessor(row.Key.Text, row.Value.Properties); } } } private void ExportIcons(IEnumerable iconPaths, string logName, string outFolder) { if (_provider == null) throw new InvalidOperationException("Attempted to call dump function without dumper initialization/state"); _log.Info($"Dumping {logName} icons"); foreach (string path in iconPaths) ExportIcon(path, outFolder); _log.Info($"Dumped all {logName} icons"); } /* * 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 call dump function without dumper initialization/state"); 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; /* * shoutout to whichever dev kept fucking up the casing * its rly surprising how many times this happened */ if (!_provider.TryLoadPackage(cleanPath, out IPackage? package)) { string searchSuffix = cleanPath; if (searchSuffix.StartsWith("/Game/")) searchSuffix = searchSuffix["/Game/".Length..]; if (!searchSuffix.EndsWith(".uasset")) searchSuffix += ".uasset"; KeyValuePair 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); } else throw new InvalidDataException("Bitmap was invalid"); } else throw new FileNotFoundException("Failed to find texture"); } else throw new FileNotFoundException("Failed to find texture package"); } }