diff --git a/src/BlitzKit.CLI/Functions/Mangler.cs b/src/BlitzKit.CLI/Functions/Mangler.cs index 0c386bdb9..0a47b67ae 100644 --- a/src/BlitzKit.CLI/Functions/Mangler.cs +++ b/src/BlitzKit.CLI/Functions/Mangler.cs @@ -1,6 +1,10 @@ using Blitzkit; using BlitzKit.CLI.Models; using BlitzKit.CLI.Utils; +using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using CUE4Parse.UE4.Objects.UObject; +using CUE4Parse.Utils; using Google.Protobuf.Collections; using Newtonsoft.Json.Linq; @@ -28,8 +32,8 @@ namespace BlitzKit.CLI.Functions { foreach (var tankDir in nation.Directories) { - // if (tankDir.Value.Name != "R90_IS_4") - // continue; + if (tankDir.Value.Name != "R90_IS_4") + continue; var tank = MangleTank(tankDir.Value); @@ -57,6 +61,15 @@ namespace BlitzKit.CLI.Functions PrettyLog.Success($"Mangling tank: {tankId}"); } + var hulls = pda.GetObjectDataTable("DT_Hulls"); + var hull = hulls.GetRow("hull"); + var hullVisual = hull.GetObject("VisualData"); + var hullMeshSettings = hullVisual.GetStruct("MeshSettings"); + var hullMesh = hullMeshSettings.GetSoftObject("Mesh"); + + // var hullMesh = hullMeshSettings.GetSoftObject("Mesh"); + // GLB hullGlb = new(hullMesh); + return new() { Id = tankId }; } diff --git a/src/BlitzKit.CLI/Functions/Unpacker.cs b/src/BlitzKit.CLI/Functions/Unpacker.cs index ade5631aa..b36ab8ee0 100644 --- a/src/BlitzKit.CLI/Functions/Unpacker.cs +++ b/src/BlitzKit.CLI/Functions/Unpacker.cs @@ -1,5 +1,6 @@ using System.Text.Json; using BlitzKit.CLI.Models; +using BlitzKit.CLI.Quicktype; using BlitzKit.CLI.Utils; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Pak; diff --git a/src/BlitzKit.CLI/Models/DLCManifest.cs b/src/BlitzKit.CLI/Models/DLCManifest.cs deleted file mode 100644 index 22bd0fe25..000000000 --- a/src/BlitzKit.CLI/Models/DLCManifest.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Text.Json.Serialization; - -namespace BlitzKit.CLI.Models -{ - public partial class DlcManifest - { - [JsonPropertyName("contentBuildId")] - public string ContentBuildId { get; set; } - - [JsonPropertyName("pakFiles")] - public List PakFiles { get; set; } - - [JsonPropertyName("chunkTags")] - public Dictionary ChunkTags { get; set; } - - [JsonPropertyName("chunkDependencies")] - public List ChunkDependencies { get; set; } - } - - public partial class ChunkDependency - { - [JsonPropertyName("chunkId")] - public long ChunkId { get; set; } - - [JsonPropertyName("parentChunkId")] - public long ParentChunkId { get; set; } - } - - public partial class ChunkTag - { - [JsonPropertyName("gameplayTags")] - public List GameplayTags { get; set; } - } - - public partial class GameplayTag - { - [JsonPropertyName("tagName")] - public string TagName { get; set; } - } - - public partial class PakFile - { - [JsonPropertyName("fileName")] - public string FileName { get; set; } - - [JsonPropertyName("fileSize")] - public long FileSize { get; set; } - - [JsonPropertyName("fileVersion")] - public string FileVersion { get; set; } - - [JsonPropertyName("chunkId")] - public long ChunkId { get; set; } - - [JsonPropertyName("relativeUrl")] - public string RelativeUrl { get; set; } - - [JsonPropertyName("availabilityMask")] - public long AvailabilityMask { get; set; } - } -} diff --git a/src/BlitzKit.CLI/Models/GLB.cs b/src/BlitzKit.CLI/Models/GLB.cs new file mode 100644 index 000000000..1c9f15b29 --- /dev/null +++ b/src/BlitzKit.CLI/Models/GLB.cs @@ -0,0 +1,15 @@ +namespace BlitzKit.CLI.Models +{ + public class GLB + { + public GLB(MappedUObject obj) + { + var staticMaterials = obj.GetArray("StaticMaterials"); + + foreach (var staticMaterial in staticMaterials) + { + Console.WriteLine(staticMaterial.GetAny("MaterialInterface")); + } + } + } +} diff --git a/src/BlitzKit.CLI/Models/MappedUDataTable.cs b/src/BlitzKit.CLI/Models/MappedUDataTable.cs new file mode 100644 index 000000000..d4bc04b87 --- /dev/null +++ b/src/BlitzKit.CLI/Models/MappedUDataTable.cs @@ -0,0 +1,21 @@ +using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Assets.Objects; + +namespace BlitzKit.CLI.Models +{ + public class MappedUDataTable : MappedUObject + { + readonly Dictionary StructFallbacks = []; + + public MappedUDataTable(UDataTable table) + : base(table.Properties) + { + foreach (var row in table.RowMap) + { + StructFallbacks.Add(row.Key.Text, row.Value); + } + } + + public MappedUObject GetRow(string name) => new(StructFallbacks[name].Properties); + } +} diff --git a/src/BlitzKit.CLI/Models/MappedUObject.cs b/src/BlitzKit.CLI/Models/MappedUObject.cs index 678294135..bcf8c88e3 100644 --- a/src/BlitzKit.CLI/Models/MappedUObject.cs +++ b/src/BlitzKit.CLI/Models/MappedUObject.cs @@ -1,6 +1,10 @@ +using System.Collections; +using System.Collections.Frozen; +using System.Dynamic; using CUE4Parse.FileProvider; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Assets.Objects.Properties; using CUE4Parse.UE4.Objects.UObject; @@ -18,6 +22,7 @@ namespace BlitzKit.CLI.Models readonly Dictionary Arrays = []; readonly Dictionary Texts = []; readonly Dictionary Ints = []; + readonly Dictionary Maps = []; public MappedUObject(List properties) : base(properties) @@ -80,6 +85,12 @@ namespace BlitzKit.CLI.Models break; } + case MapProperty map: + { + Maps.Add(property.Name.Text, map); + break; + } + default: { throw new Exception($"Unhandled property type: {property}"); @@ -88,19 +99,47 @@ namespace BlitzKit.CLI.Models } } - public string GetName(string name) - { - return Names[name].Value.Text; - } + public FPropertyTag GetAny(string name) => + Properties.First(property => property.Name.Text == name); - public bool GetBool(string name) - { - return Booleans[name].Value; - } + public string GetName(string name) => Names[name].Value.Text; - public bool? TryGetBool(string name) + public string? TryGetName(string name) => + Names.TryGetValue(name, out var entry) ? entry.Value.Text : null; + + public bool GetBool(string name) => Booleans[name].Value; + + public bool? TryGetBool(string name) => + Booleans.TryGetValue(name, out var entry) ? entry.Value : null; + + public T GetSoftObject(string name) + where T : UObject => SoftObjects[name].Value.Load(); + + public MappedUObject GetMappedSoftObject(string name) => + new(GetMappedSoftObject(name).Properties); + + public MappedUObject? TryGetSoftObject(string name) => + SoftObjects.TryGetValue(name, out var entry) + ? new MappedUObject(entry.Value.Load().Properties) + : null; + + public MappedUObject GetObject(string name) => new(Objects[name].Value!.Load()!.Properties); + + public MappedUDataTable GetObjectDataTable(string name) => + new(Objects[name].Value!.Load()!); + + public MappedUObject GetStruct(string name) => + new(Structs[name].GetValue()!.Properties); + + public MappedUObject[] GetArray(string name) { - return Booleans.TryGetValue(name, out var entry) ? entry.Value : null; + return + [ + .. Arrays[name] + .Value!.Properties.Select(property => new MappedUObject( + property.GetValue()!.Properties + )), + ]; } } } diff --git a/src/BlitzKit.CLI/Quicktype/DLCManifest.cs b/src/BlitzKit.CLI/Quicktype/DLCManifest.cs new file mode 100644 index 000000000..d048e0742 --- /dev/null +++ b/src/BlitzKit.CLI/Quicktype/DLCManifest.cs @@ -0,0 +1,211 @@ +// +// +// To parse this JSON data, add NuGet 'System.Text.Json' then do: +// +// using BlitzKit.CLI.Quicktype; +// +// var dlcManifest = DlcManifest.FromJson(jsonString); +#nullable enable +#pragma warning disable CS8618 +#pragma warning disable CS8601 +#pragma warning disable CS8603 + +namespace BlitzKit.CLI.Quicktype +{ + using System; + using System.Collections.Generic; + + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Globalization; + + public partial class DlcManifest + { + [JsonPropertyName("contentBuildId")] + public string ContentBuildId { get; set; } + + [JsonPropertyName("pakFiles")] + public List PakFiles { get; set; } + + [JsonPropertyName("chunkTags")] + public Dictionary ChunkTags { get; set; } + + [JsonPropertyName("chunkDependencies")] + public List ChunkDependencies { get; set; } + } + + public partial class ChunkDependency + { + [JsonPropertyName("chunkId")] + public long ChunkId { get; set; } + + [JsonPropertyName("parentChunkId")] + public long ParentChunkId { get; set; } + } + + public partial class ChunkTag + { + [JsonPropertyName("gameplayTags")] + public List GameplayTags { get; set; } + } + + public partial class GameplayTag + { + [JsonPropertyName("tagName")] + public string TagName { get; set; } + } + + public partial class PakFile + { + [JsonPropertyName("fileName")] + public string FileName { get; set; } + + [JsonPropertyName("fileSize")] + public long FileSize { get; set; } + + [JsonPropertyName("fileVersion")] + public string FileVersion { get; set; } + + [JsonPropertyName("chunkId")] + public long ChunkId { get; set; } + + [JsonPropertyName("relativeUrl")] + public string RelativeUrl { get; set; } + } + + public partial class DlcManifest + { + public static DlcManifest FromJson(string json) => JsonSerializer.Deserialize(json, BlitzKit.CLI.Quicktype.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this DlcManifest self) => JsonSerializer.Serialize(self, BlitzKit.CLI.Quicktype.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerOptions Settings = new(JsonSerializerDefaults.General) + { + Converters = + { + new DateOnlyConverter(), + new TimeOnlyConverter(), + IsoDateTimeOffsetConverter.Singleton + }, + }; + } + + public class DateOnlyConverter : JsonConverter + { + private readonly string serializationFormat; + public DateOnlyConverter() : this(null) { } + + public DateOnlyConverter(string? serializationFormat) + { + this.serializationFormat = serializationFormat ?? "yyyy-MM-dd"; + } + + public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return DateOnly.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(serializationFormat)); + } + + public class TimeOnlyConverter : JsonConverter + { + private readonly string serializationFormat; + + public TimeOnlyConverter() : this(null) { } + + public TimeOnlyConverter(string? serializationFormat) + { + this.serializationFormat = serializationFormat ?? "HH:mm:ss.fff"; + } + + public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return TimeOnly.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(serializationFormat)); + } + + internal class IsoDateTimeOffsetConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(DateTimeOffset); + + private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK"; + + private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; + private string? _dateTimeFormat; + private CultureInfo? _culture; + + public DateTimeStyles DateTimeStyles + { + get => _dateTimeStyles; + set => _dateTimeStyles = value; + } + + public string? DateTimeFormat + { + get => _dateTimeFormat ?? string.Empty; + set => _dateTimeFormat = (string.IsNullOrEmpty(value)) ? null : value; + } + + public CultureInfo Culture + { + get => _culture ?? CultureInfo.CurrentCulture; + set => _culture = value; + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + string text; + + + if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal + || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal) + { + value = value.ToUniversalTime(); + } + + text = value.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture); + + writer.WriteStringValue(text); + } + + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? dateText = reader.GetString(); + + if (string.IsNullOrEmpty(dateText) == false) + { + if (!string.IsNullOrEmpty(_dateTimeFormat)) + { + return DateTimeOffset.ParseExact(dateText, _dateTimeFormat, Culture, _dateTimeStyles); + } + else + { + return DateTimeOffset.Parse(dateText, Culture, _dateTimeStyles); + } + } + else + { + return default(DateTimeOffset); + } + } + + + public static readonly IsoDateTimeOffsetConverter Singleton = new IsoDateTimeOffsetConverter(); + } +} +#pragma warning restore CS8618 +#pragma warning restore CS8601 +#pragma warning restore CS8603