From 5989bd3bcec429023096d133058264a72d138d8a Mon Sep 17 00:00:00 2001 From: neru Date: Thu, 18 Jun 2026 18:59:32 -0300 Subject: [PATCH] feat: add customizations dump (data and icons) --- src/dumper/dumper.cs | 122 ++++++++++++++++++++++++++++++++++++++++++- src/dumper/main.cs | 1 + 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/dumper/dumper.cs b/src/dumper/dumper.cs index 501da66..fc1e1db 100644 --- a/src/dumper/dumper.cs +++ b/src/dumper/dumper.cs @@ -44,6 +44,15 @@ struct DLCInfo public Dictionary dlcIds; } +struct CustomizationItemInfo +{ + public string id; + public string name; + public string iconFilePath; + public int associatedCharacter; + public ECustomizationCategory category; +} + class Dumper { private DefaultFileProvider? _provider; @@ -55,6 +64,7 @@ class Dumper private readonly Dictionary _characterMap = new(); private readonly Dictionary _itemMap = new(); private readonly Dictionary _offeringMap = new(); + private readonly Dictionary _customizationItemMap = new(); public Dumper(string outDir) { @@ -228,9 +238,9 @@ class Dumper string? displayName = null; - if (TryGetStringProp(props, "DisplayName", out string foundName) && !string.IsNullOrWhiteSpace(foundName)) + if (TryGetStringProp(props, "DisplayName", out string foundName) && !string.IsNullOrWhiteSpace(foundName)) displayName = foundName; - + DLCInfo info = new DLCInfo { id = rowKey, @@ -245,9 +255,117 @@ class Dumper WriteJson("dlcs", dlcList.ToArray()); } + public void DumpCustomizations() + { + ProcessDataTables("/CustomizationItemDB.uasset", "customization items", (rowKey, props) => + { + if (!TryGetProp(props, "Category", out ECustomizationCategory category) + || !TryGetProp(props, "Availability", out FStructFallback availabilityFb) + || !TryGetProp(props, "UIData", out FStructFallback uiDataFb) + || !TryGetProp(props, "AssociatedCharacter", out int associatedCharacter)) + throw new KeyNotFoundException($"Required properties missing for CustomizationItem: {rowKey}"); + + AvailabilityStruct availability = availabilityFb.MapToStruct(); + UIDataStruct uiData = uiDataFb.MapToStruct(); + + if (availability.DLCId == "development") return; // skip development dlc items just in case + + if (uiData.IconAssetList.Length == 0) + throw new InvalidDataException("CustomizationItem's UIData had no icons"); + + string iconPathStr = uiData.IconAssetList[0].ToString(); + + _customizationItemMap[rowKey] = new CustomizationItemInfo + { + id = rowKey, + name = uiData.DisplayName.ToString(), + associatedCharacter = associatedCharacter, + category = category, + iconFilePath = iconPathStr, + }; + }); + + _log.Info("Dumped {0} customization items", _customizationItemMap.Count); + WriteJson("customization_items", _customizationItemMap.Values); + } + 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/"); + public void DumpCustomizationIcons() + { + _log.Info("Dumping customization item icons"); + + foreach (var item in _customizationItemMap.Values) + { + string outPath = "customization/"; + + if (item.category == ECustomizationCategory.Charm || + item.category == ECustomizationCategory.Badge || + item.category == ECustomizationCategory.Banner || + item.category == ECustomizationCategory.PortraitBackground) + { + switch (item.category) + { + case ECustomizationCategory.Charm: + outPath += "charms/"; + break; + case ECustomizationCategory.Badge: + outPath += "badges/"; + break; + case ECustomizationCategory.Banner: + outPath += "banners/"; + break; + case ECustomizationCategory.PortraitBackground: + outPath += "portrait-backgrounds/"; + break; + } + } + else if (item.associatedCharacter > -1) + { + string charFolderName = item.associatedCharacter.ToString(); + + if (_characterMap.TryGetValue(item.associatedCharacter, out CharacterInfo charInfo) && !string.IsNullOrWhiteSpace(charInfo.name)) + charFolderName = string.Join("_", charInfo.name.Split(Path.GetInvalidFileNameChars())); + + outPath += $"characters/{charFolderName}/"; + + switch (item.category) + { + case ECustomizationCategory.SurvivorHead: + case ECustomizationCategory.KillerHead: + outPath += "heads/"; + break; + case ECustomizationCategory.SurvivorTorso: + outPath += "torsos/"; + break; + case ECustomizationCategory.SurvivorLegs: + outPath += "legs/"; + break; + case ECustomizationCategory.KillerBody: + outPath += "bodys/"; + break; + case ECustomizationCategory.KillerWeapon: + outPath += "weapons/"; + break; + case ECustomizationCategory.Outfits: + outPath += "outfits/"; + break; + } + } + + try + { + ExportIcon(item.iconFilePath, outPath); + } + catch (Exception ex) + { + _log.Error($"Failed to export icon for {item.id}: {ex.Message}"); + } + } + + _log.Info("Dumped all customization item icons"); + } /* * bulk functions for dumping diff --git a/src/dumper/main.cs b/src/dumper/main.cs index 5936f07..b941348 100644 --- a/src/dumper/main.cs +++ b/src/dumper/main.cs @@ -15,6 +15,7 @@ var dumpTasks = new (string Name, Action Execute)[] ("character", () => { dumper.DumpCharacters(); dumper.DumpCharacterIcons(); }), ("item", () => { dumper.DumpItems(); dumper.DumpItemIcons(); }), ("offering", () => { dumper.DumpOfferings(); dumper.DumpOfferingIcons(); }), + ("customizations", () => { dumper.DumpCustomizations(); dumper.DumpCustomizationIcons(); }), ("dlc", () => { dumper.DumpDLCs(); }) };