feat: rewrite dumper from scratch
This commit is contained in:
@@ -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"];
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<bool> 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<string>();
|
||||||
|
var outfits = new List<string>();
|
||||||
|
|
||||||
|
foreach (var path in searchPaths)
|
||||||
|
{
|
||||||
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
|
if (_provider.TryLoadPackageObject<UDataTable>(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<FStructFallback>();
|
||||||
|
if (availabilityFallback == null) continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* real data
|
||||||
|
*/
|
||||||
|
string itemName = row.Key.Text;
|
||||||
|
AvailabilityStruct availability = availabilityFallback.MapToStruct<AvailabilityStruct>();
|
||||||
|
ECustomizationCategory category = categoryProp.Tag.GetValue<ECustomizationCategory>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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<UDataTable>(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<FStructFallback>();
|
||||||
|
if (availabilityFallback == null) continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* real data
|
||||||
|
*/
|
||||||
|
string outfitName = row.Key.Text;
|
||||||
|
AvailabilityStruct availability = availabilityFallback.MapToStruct<AvailabilityStruct>();
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
//var slasherPowers = new List<string>();
|
||||||
|
|
||||||
|
foreach (var path in searchPaths)
|
||||||
|
{
|
||||||
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
|
if (_provider.TryLoadPackageObject<UDataTable>(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<EPlayerRole>();
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
var slasherAddons = new List<string>();
|
||||||
|
|
||||||
|
foreach (var path in searchPaths)
|
||||||
|
{
|
||||||
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
|
if (_provider.TryLoadPackageObject<UDataTable>(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<EPlayerRole>();
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
var camperOfferings = new List<string>();
|
||||||
|
var slasherOfferings = new List<string>();
|
||||||
|
|
||||||
|
foreach (var path in searchPaths)
|
||||||
|
{
|
||||||
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
|
if (_provider.TryLoadPackageObject<UDataTable>(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<EPlayerRole>();
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
var slasherPerks = new List<string>();
|
||||||
|
|
||||||
|
foreach (var path in searchPaths)
|
||||||
|
{
|
||||||
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
|
if (_provider.TryLoadPackageObject<UDataTable>(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<EPlayerRole>();
|
||||||
|
|
||||||
|
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<bool> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
-225
@@ -1,228 +1,72 @@
|
|||||||
using CUE4Parse.Compression;
|
using CUE4Parse.UE4.Assets.Exports.CustomizableObject;
|
||||||
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;
|
|
||||||
|
|
||||||
class DumpByDaylight
|
int displayError(string err)
|
||||||
{
|
{
|
||||||
private const string _pakDir = "D:\\XboxGames\\Dead By Daylight\\Content\\DeadByDaylight\\Content\\Paks";
|
Console.WriteLine($"Dumper error: {err}");
|
||||||
private const string _aesKey = "0x22B1639B548124925CF7B9CBAA09F9AC295FCF0324586D6B37EE1D42670B39B3";
|
Console.WriteLine("Press any key to exit");
|
||||||
private const string _mappingURL = "https://git.neru.rip/neru/UnlockedByDaylight/raw/branch/main/res/mappings/latest-xbox.usmap";
|
Console.ReadKey();
|
||||||
|
return 1;
|
||||||
public static async Task<string?> 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<string>();
|
|
||||||
|
|
||||||
var slasherPowers = new List<string>();
|
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
|
||||||
{
|
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(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<string>();
|
|
||||||
var slasherAddons = new List<string>();
|
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
|
||||||
{
|
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(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<string>();
|
|
||||||
var slasherOfferings = new List<string>();
|
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
|
||||||
{
|
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(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<string>();
|
|
||||||
var camperPerks = new List<string>();
|
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
|
||||||
{
|
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(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 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;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
public static class StructMapper
|
||||||
|
{
|
||||||
|
public static T MapToStruct<T>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user