diff --git a/src/dumper/constants.cs b/src/dumper/constants.cs new file mode 100644 index 0000000..81ff740 --- /dev/null +++ b/src/dumper/constants.cs @@ -0,0 +1,10 @@ +public static class Constants +{ + public static readonly string MappingURL = "https://git.neru.rip/neru/UnlockedByDaylight/raw/branch/main/res/mappings/latest-xbox.usmap"; + + public static readonly string PackageName = "BehaviourInteractive.DeadbyDaylightWindows"; + + public static readonly string AESKey = "0x22B1639B548124925CF7B9CBAA09F9AC295FCF0324586D6B37EE1D42670B39B3"; + + public static readonly string[] BlacklistedCustomizationItems = ["NK_Torso01_Crew01Kraken"]; +} \ No newline at end of file diff --git a/src/dumper/dbd.cs b/src/dumper/dbd.cs new file mode 100644 index 0000000..cbcd521 --- /dev/null +++ b/src/dumper/dbd.cs @@ -0,0 +1,40 @@ +public enum EItemAvailability : byte +{ + Available, + Disabled, + Retired +} + +public enum ECustomizationCategory : byte +{ + None, + SurvivorHead, + SurvivorTorso, + SurvivorLegs, + KillerHead, + KillerBody, + KillerWeapon, + Outfits, + Charm, + Badge, + Banner, + PortraitBackground +} + +public enum EPlayerRole : byte +{ + VE_None, + VE_Slasher, + VE_Camper, + VE_Observer +} + +public struct AvailabilityStruct +{ + public EItemAvailability itemAvailability; + public string DLCId; + public string[] AdditionalDlcIds; + public Int32 CloudInventoryId; + public string CommunityId; + public bool _isLicensed; +} \ No newline at end of file diff --git a/src/dumper/dumper.cs b/src/dumper/dumper.cs new file mode 100644 index 0000000..c889f45 --- /dev/null +++ b/src/dumper/dumper.cs @@ -0,0 +1,404 @@ +using CUE4Parse.Compression; +using CUE4Parse.Encryption.Aes; +using CUE4Parse.FileProvider; +using CUE4Parse.MappingsProvider; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Actor; +using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Assets.Objects.Properties; +using CUE4Parse.UE4.Objects.Core.Misc; +using CUE4Parse.UE4.Versions; +using CUE4Parse.UE4.VirtualFileSystem; +using CUE4Parse.UE4.Wwise.Plugins.MasteringSuite; +using Microsoft.Win32; +using Newtonsoft.Json; +using System.Reflection; + +class Dumper +{ + private DefaultFileProvider? _provider = null; + private IAesVfsReader? _dataPak = null; + + public async Task InitAsync() + { + /* + * ensure mapping + */ + string mappingPath = Path.Combine(Path.GetTempPath(), "DeadByDaylight.usmap"); + bool hasDownloadedMapping = await DownloadMappingFileAsync(Constants.MappingURL, mappingPath); + if (!hasDownloadedMapping) + return false; + + /* + * compression setup + */ + ZlibHelper.Initialize(); + + var oodlePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OodleHelper.OodleFileName); + OodleHelper.Initialize(oodlePath); + + /* + * game localization + */ + var gamePath = getGamePath(); + if (gamePath == null) return false; + + var pakDir = gamePath + "\\DeadByDaylight\\Content\\Paks"; + if (!Directory.Exists(pakDir)) + { + Console.WriteLine("PAK dir does not exist. (Invalid install?)"); + return false; + } + + /* + * provider setup + */ + var version = new VersionContainer(EGame.GAME_DeadByDaylight, ETexturePlatform.DesktopMobile); + _provider = new DefaultFileProvider(pakDir, SearchOption.TopDirectoryOnly, version); + _provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); + + _provider.Initialize(); + _provider.SubmitKey(new FGuid(), new FAesKey(Constants.AESKey)); + _provider.Mount(); + + /* + * load db pak + */ + bool hasDataPak = _provider.TryGetArchive("pakchunk4-WinGDK.utoc", out _dataPak); + if (!hasDataPak) + { + Console.WriteLine("Failed to load pakchunk4-WinGDK.utoc"); + return false; + } + + return true; + } + + public string? DumpCustomizationItems() + { + if (_dataPak == null || _provider == null) return null; + + var searchPaths = _dataPak.Files.Keys.Where(x => x.Contains($"/CustomizationItemDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); + + var characterItems = new List(); + var outfits = new List(); + + foreach (var path in searchPaths) + { + var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; + if (_provider.TryLoadPackageObject(cleanPath, out var dataTable)) + { + foreach (var row in dataTable.RowMap) + { + /* + * props + */ + var props = row.Value.Properties; + + FPropertyTag? categoryProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Category"); + if (categoryProp == null || categoryProp.Tag == null) continue; + + FPropertyTag? availabilityProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Availability"); + if (availabilityProp == null || availabilityProp.Tag == null) continue; + + FStructFallback? availabilityFallback = availabilityProp.Tag.GetValue(); + if (availabilityFallback == null) continue; + + /* + * real data + */ + string itemName = row.Key.Text; + AvailabilityStruct availability = availabilityFallback.MapToStruct(); + ECustomizationCategory category = categoryProp.Tag.GetValue(); + + /* + * sketchy item filtering + */ + if (availability.DLCId == "development") continue; + if (category < ECustomizationCategory.SurvivorHead || category > ECustomizationCategory.KillerWeapon) continue; + if (Constants.BlacklistedCustomizationItems.Contains(itemName)) continue; + + /* + * storing + */ + characterItems.Add(itemName); + } + } + } + + searchPaths = _dataPak.Files.Keys.Where(x => x.Contains($"/OutfitDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); + + foreach (var path in searchPaths) + { + var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; + if (_provider.TryLoadPackageObject(cleanPath, out var dataTable)) + { + foreach (var row in dataTable.RowMap) + { + /* + * props + */ + var props = row.Value.Properties; + + FPropertyTag? availabilityProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Availability"); + if (availabilityProp == null || availabilityProp.Tag == null) continue; + + FStructFallback? availabilityFallback = availabilityProp.Tag.GetValue(); + if (availabilityFallback == null) continue; + + /* + * real data + */ + string outfitName = row.Key.Text; + AvailabilityStruct availability = availabilityFallback.MapToStruct(); + + if (availability.DLCId == "development") + continue; + + outfits.Add(outfitName); + } + } + } + + var customizationsSerialized = new + { + Items = characterItems.OrderBy(x => x).ToList(), + Outfits = outfits.OrderBy(x => x).ToList() + }; + + return JsonConvert.SerializeObject(customizationsSerialized, Formatting.Indented); + } + + public string? DumpItems() + { + if (_dataPak == null || _provider == null) return null; + + var searchPaths = _dataPak.Files.Keys.Where(x => x.Contains($"/ItemDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); + + var camperItems = new List(); + //var slasherPowers = new List(); + + foreach (var path in searchPaths) + { + var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; + if (_provider.TryLoadPackageObject(cleanPath, out var dataTable)) + { + foreach (var row in dataTable.RowMap) + { + /* + * props + */ + var props = row.Value.Properties; + + FPropertyTag? roleProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); + if (roleProp == null || roleProp.Tag == null) continue; + + //FPropertyTag? typeProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Type"); + //if (typeProp == null || typeProp.Tag == null) continue; + + /* + * real data + */ + string itemName = row.Key.Text; + EPlayerRole role = roleProp.Tag.GetValue(); + + if (role == EPlayerRole.VE_Camper) + camperItems.Add(itemName); + //else + // slasherPowers.Add(itemName); + } + } + } + + var itemsSerialized = new + { + Campers = camperItems.OrderBy(x => x).ToList() + }; + + return JsonConvert.SerializeObject(itemsSerialized, Formatting.Indented); + } + + public string? DumpAddons() + { + if (_dataPak == null || _provider == null) return null; + + var searchPaths = _dataPak.Files.Keys.Where(x => x.Contains($"/ItemAddonDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); + + var camperAddons = new List(); + var slasherAddons = new List(); + + foreach (var path in searchPaths) + { + var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; + if (_provider.TryLoadPackageObject(cleanPath, out var dataTable)) + { + foreach (var row in dataTable.RowMap) + { + /* + * props + */ + var props = row.Value.Properties; + + FPropertyTag? roleProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); + if (roleProp == null || roleProp.Tag == null) continue; + + /* + * real data + */ + string itemName = row.Key.Text; + EPlayerRole role = roleProp.Tag.GetValue(); + + if (role == EPlayerRole.VE_Camper) + camperAddons.Add(itemName); + else + slasherAddons.Add(itemName); + + } + } + } + + var addonsSerialized = new + { + Slashers = slasherAddons.OrderBy(x => x).ToList(), + Campers = camperAddons.OrderBy(x => x).ToList() + }; + + return JsonConvert.SerializeObject(addonsSerialized, Formatting.Indented); + } + + public string? DumpOfferings() + { + if (_dataPak == null || _provider == null) return null; + + var searchPaths = _dataPak.Files.Keys.Where(x => x.Contains($"/OfferingDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); + + var globalOfferings = new List(); + var camperOfferings = new List(); + var slasherOfferings = new List(); + + foreach (var path in searchPaths) + { + var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; + if (_provider.TryLoadPackageObject(cleanPath, out var dataTable)) + { + foreach (var row in dataTable.RowMap) + { + /* + * props + */ + var props = row.Value.Properties; + + FPropertyTag? roleProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); + if (roleProp == null || roleProp.Tag == null) continue; + + /* + * real data + */ + string itemName = row.Key.Text; + EPlayerRole role = roleProp.Tag.GetValue(); + + if (role == EPlayerRole.VE_Camper) + camperOfferings.Add(itemName); + else if (role == EPlayerRole.VE_Slasher) + slasherOfferings.Add(itemName); + else + globalOfferings.Add(itemName); + } + } + } + var offeringsSerialized = new + { + Slashers = slasherOfferings.OrderBy(x => x).ToList(), + Campers = camperOfferings.OrderBy(x => x).ToList(), + All = globalOfferings.OrderBy(x => x).ToList() + }; + + return JsonConvert.SerializeObject(offeringsSerialized, Formatting.Indented); + } + + public string? DumpPerks() + { + if (_dataPak == null || _provider == null) return null; + + var searchPaths = _dataPak.Files.Keys.Where(x => x.Contains($"/PerkDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); + + var camperPerks = new List(); + var slasherPerks = new List(); + + foreach (var path in searchPaths) + { + var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; + if (_provider.TryLoadPackageObject(cleanPath, out var dataTable)) + { + foreach (var row in dataTable.RowMap) + { + /* + * props + */ + var props = row.Value.Properties; + + FPropertyTag? roleProp = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); + if (roleProp == null || roleProp.Tag == null) continue; + + /* + * real data + */ + string itemName = row.Key.Text; + EPlayerRole role = roleProp.Tag.GetValue(); + + if (role == EPlayerRole.VE_Camper) + camperPerks.Add(itemName); + else + slasherPerks.Add(itemName); + } + } + } + var perksSerialized = new + { + Slashers = slasherPerks.OrderBy(x => x).ToList(), + Campers = camperPerks.OrderBy(x => x).ToList() + }; + + return JsonConvert.SerializeObject(perksSerialized, Formatting.Indented); + } + private string? getGamePath() + { + const string packagesPath = @"Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages"; + + using var key = Registry.CurrentUser.OpenSubKey(packagesPath); + + if (key == null) return null; + string? matchingKeyName = key.GetSubKeyNames() + .FirstOrDefault(name => name.StartsWith(Constants.PackageName, StringComparison.OrdinalIgnoreCase)); + + if (matchingKeyName != null) + { + using (RegistryKey? subKey = key.OpenSubKey(matchingKeyName)) + return subKey?.GetValue("PackageRootFolder")?.ToString(); + } + + Console.WriteLine("Failed to find game install dir"); + return null; + } + private async Task DownloadMappingFileAsync(string url, string outPath) + { + try + { + using (var httpClient = new HttpClient()) + { + var bytes = await httpClient.GetByteArrayAsync(url); + Console.WriteLine("Downloading mapping file..."); + await File.WriteAllBytesAsync(outPath, bytes); + Console.WriteLine($"Mapping file downloaded to: {outPath}"); + return true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error occured while downloading mapping: {ex.Message}"); + } + return false; + } +} diff --git a/src/dumper/main.cs b/src/dumper/main.cs index 702b78f..3f09723 100644 --- a/src/dumper/main.cs +++ b/src/dumper/main.cs @@ -1,228 +1,72 @@ -using CUE4Parse.Compression; -using CUE4Parse.Encryption.Aes; -using CUE4Parse.FileProvider; -using CUE4Parse.MappingsProvider; -using CUE4Parse.UE4.Assets.Exports.Actor; -using CUE4Parse.UE4.Assets.Exports.Engine; -using CUE4Parse.UE4.Assets.Exports.Texture; -using CUE4Parse.UE4.Assets.Objects.Properties; -using CUE4Parse.UE4.Objects.Core.Misc; -using CUE4Parse.UE4.Objects.UObject; -using CUE4Parse.UE4.Versions; -using Newtonsoft.Json; +using CUE4Parse.UE4.Assets.Exports.CustomizableObject; -class DumpByDaylight +int displayError(string err) { - private const string _pakDir = "D:\\XboxGames\\Dead By Daylight\\Content\\DeadByDaylight\\Content\\Paks"; - private const string _aesKey = "0x22B1639B548124925CF7B9CBAA09F9AC295FCF0324586D6B37EE1D42670B39B3"; - private const string _mappingURL = "https://git.neru.rip/neru/UnlockedByDaylight/raw/branch/main/res/mappings/latest-xbox.usmap"; - - public static async Task DownloadMappingFileAsync(string url, string savePath) - { - try - { - using (var httpClient = new HttpClient()) - { - Console.WriteLine("downloading mapping file..."); - var bytes = await httpClient.GetByteArrayAsync(url); - await File.WriteAllBytesAsync(savePath, bytes); - Console.WriteLine($"mapping file downloaded to: {savePath}"); - return savePath; - } - } - catch (Exception ex) - { - Console.WriteLine($"failed to download mapping: {ex.Message}"); - return null; - } - } - - static async Task Main(string[] args) - { - // mapping - string mappingPath = Path.Combine(Path.GetTempPath(), "DeadByDaylight.usmap"); - string? downloadedMapping = await DownloadMappingFileAsync(_mappingURL, mappingPath); - - if (string.IsNullOrEmpty(downloadedMapping)) - { - Console.WriteLine("failed to download mapping file, press any key to exit"); - Console.ReadKey(); - return; - } - - ZlibHelper.Initialize(); - - var oodlePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OodleHelper.OodleFileName); - OodleHelper.Initialize(oodlePath); - - - // parsing setup - var version = new VersionContainer(EGame.GAME_DeadByDaylight, ETexturePlatform.DesktopMobile); - var provider = new DefaultFileProvider(_pakDir, SearchOption.TopDirectoryOnly, version); - provider.MappingsContainer = new FileUsmapTypeMappingsProvider(downloadedMapping); - - provider.Initialize(); - provider.SubmitKey(new FGuid(), new FAesKey(_aesKey)); - provider.Mount(); - - Console.WriteLine("\nProvider Initialized. Extracting Databases..."); - - var dataPak = provider.GetArchive("pakchunk4-WinGDK.utoc"); - - /* - * itemdb dump - */ - var searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/ItemDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); - var camperItems = new List(); - - var slasherPowers = new List(); - - foreach (var path in searchPaths) - { - var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; - if (provider.TryLoadPackageObject(cleanPath, out var dataTable)) - { - Console.WriteLine($"getting items / powers from {Path.GetFullPath(path)}"); - - foreach (var row in dataTable.RowMap) - { - var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); - var typeProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Type"); - if (typeProperty?.Tag is EnumProperty typeProp && roleProperty?.Tag is EnumProperty roleProp) - { - string typeName = typeProp.Value.ToString() ?? "null"; - string roleName = roleProp.Value.ToString() ?? "null"; - - if (roleName == "EPlayerRole::VE_Slasher") - slasherPowers.Add(row.Key.Text); - else - camperItems.Add(row.Key.Text); - } - } - } - } - - var itemsSerialized = new - { - Campers = camperItems.OrderBy(x => x).ToList(), - Slashers = slasherPowers.OrderBy(x => x).ToList() - }; - File.WriteAllText("items.json", JsonConvert.SerializeObject(itemsSerialized, Formatting.Indented)); - - /* - * addon dump - */ - searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/ItemAddonDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); - var camperAddons = new List(); - var slasherAddons = new List(); - - foreach (var path in searchPaths) - { - var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; - if (provider.TryLoadPackageObject(cleanPath, out var dataTable)) - { - Console.WriteLine($"getting item / power addons from {Path.GetFullPath(path)}"); - - foreach (var row in dataTable.RowMap) - { - bool isSlasherAddon = false; - var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); - if (roleProperty?.Tag is EnumProperty roleEnumProp) - { - string roleName = roleEnumProp.Value.ToString() ?? "null"; - if (roleName == "EPlayerRole::VE_Slasher") - isSlasherAddon = true; - } - - if (isSlasherAddon) - slasherAddons.Add(row.Key.Text); - else - camperAddons.Add(row.Key.Text); - } - } - } - - var addonsSerialized = new - { - Slashers = slasherAddons.OrderBy(x => x).ToList(), - Campers = camperAddons.OrderBy(x => x).ToList() - }; - File.WriteAllText("addons.json", JsonConvert.SerializeObject(addonsSerialized, Formatting.Indented)); - - /* - * offerings - */ - searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/OfferingDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); - - var camperOfferings = new List(); - var slasherOfferings = new List(); - - foreach (var path in searchPaths) - { - var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; - if (provider.TryLoadPackageObject(cleanPath, out var dataTable)) - { - Console.WriteLine($"getting offerings from {Path.GetFullPath(path)}"); - - foreach (var row in dataTable.RowMap) - { - var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); - if (roleProperty?.Tag is EnumProperty roleEnumProp) - { - string roleName = roleEnumProp.Value.ToString() ?? "null"; - if (roleName == "EPlayerRole::VE_Slasher") - slasherOfferings.Add(row.Key.Text); - else - camperOfferings.Add(row.Key.Text); - } - } - } - } - var offeringsSerialized = new - { - Slashers = slasherOfferings.OrderBy(x => x).ToList(), - Campers = camperOfferings.OrderBy(x => x).ToList() - }; - File.WriteAllText("offerings.json", JsonConvert.SerializeObject(offeringsSerialized, Formatting.Indented)); - - /* - * perks - */ - searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/PerkDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList(); - - var slasherPerks = new List(); - var camperPerks = new List(); - - foreach (var path in searchPaths) - { - var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path; - if (provider.TryLoadPackageObject(cleanPath, out var dataTable)) - { - Console.WriteLine($"getting perks from {Path.GetFullPath(path)}"); - - foreach (var row in dataTable.RowMap) - { - var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role"); - if (roleProperty?.Tag is EnumProperty roleEnumProp) - { - string roleName = roleEnumProp.Value.ToString() ?? "null"; - if (roleName == "EPlayerRole::VE_Slasher") - slasherPerks.Add(row.Key.Text); - else - camperPerks.Add(row.Key.Text); - } - } - } - } - - var perksSerialized = new - { - Slashers = slasherPerks.OrderBy(x => x).ToList(), - Campers = camperPerks.OrderBy(x => x).ToList() - }; - File.WriteAllText("perks.json", JsonConvert.SerializeObject(perksSerialized, Formatting.Indented)); - - Console.WriteLine("\nAll dumper operations finished. Press any key to Close."); - Console.ReadKey(); - } + Console.WriteLine($"Dumper error: {err}"); + Console.WriteLine("Press any key to exit"); + Console.ReadKey(); + return 1; } + +Console.WriteLine("Dumper start"); +var dumper = new Dumper(); + +bool hasInitialized = await dumper.InitAsync(); +if (!hasInitialized) return displayError("Dumper.InitAsync failed"); + +Console.WriteLine("Dumper initialized"); + +string? customizationItemsJSON = dumper.DumpCustomizationItems(); +if (customizationItemsJSON != null) +{ + File.WriteAllText("customizations.json", customizationItemsJSON); +} +else +{ + Console.WriteLine("Failed to dump customizations"); +} + +string? itemsJSON = dumper.DumpItems(); +if (itemsJSON != null) +{ + File.WriteAllText("items.json", itemsJSON); +} +else +{ + Console.WriteLine("Failed to dump items"); +} + +string? addonsJSON = dumper.DumpAddons(); +if (addonsJSON != null) +{ + File.WriteAllText("addons.json", addonsJSON); +} +else +{ + Console.WriteLine("Failed to dump addons"); +} + + +string? offeringsJSON = dumper.DumpOfferings(); +if (offeringsJSON != null) +{ + File.WriteAllText("offerings.json", offeringsJSON); +} +else +{ + Console.WriteLine("Failed to dump offerings"); +} + +string? perksJSON = dumper.DumpPerks(); +if (perksJSON != null) +{ + File.WriteAllText("perks.json", perksJSON); +} +else +{ + Console.WriteLine("Failed to dump perks"); +} + +Console.WriteLine("Dumper finished"); + +return 0; \ No newline at end of file diff --git a/src/dumper/structmapper.cs b/src/dumper/structmapper.cs new file mode 100644 index 0000000..58be0a2 --- /dev/null +++ b/src/dumper/structmapper.cs @@ -0,0 +1,39 @@ +using CUE4Parse.UE4.Assets.Objects; +using System.Reflection; + +public static class StructMapper +{ + public static T MapToStruct(this FStructFallback fallback) where T : struct + { + object result = new T(); + var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var field in fields) + { + try + { + var method = typeof(CUE4Parse.UE4.Assets.Exports.AbstractPropertyHolder) + .GetMethods() + .FirstOrDefault(m => m.Name == "GetOrDefault" && m.GetGenericArguments().Length == 1); + + if (method != null) + { + var genericMethod = method.MakeGenericMethod(field.FieldType); + + var value = genericMethod.Invoke(fallback, new object[] { field.Name, null!, StringComparison.Ordinal }); + + if (value != null) + { + field.SetValue(result, value); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Mapping failed for {field.Name}: {ex.Message}"); + } + } + + return (T)result; + } +} \ No newline at end of file