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; } class Dumper { private DefaultFileProvider? _provider; private IAesVfsReader? _dataPak; private readonly Dictionary _characterMap = new(); private Logger _log; private string _outDir; 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; } /* * internal helper functions */ private bool TryGetProp(IEnumerable properties, string propName, out T value) { FPropertyTag? prop = properties.FirstOrDefault(p => p.Name.Text == propName); if (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) { 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); // consider umg path for icons (should i also do this with perks and offerings n stuff?) if (cleanPath.StartsWith("UI/Icons/")) cleanPath = "/Game/UI/UMGAssets/" + cleanPath["UI/".Length..]; if (File.Exists(fullPath)) return; if (_provider == null) return; if (_provider.TryLoadPackage(cleanPath, out IPackage? package)) { 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 FileNotFoundException("Failed to find texture"); } } }