From bda393690b5ad4e9eb7599a29b43109efc480fcb Mon Sep 17 00:00:00 2001 From: Hazno21688 <62346467+Hazno-dev@users.noreply.github.com> Date: Fri, 25 Sep 2026 09:37:15 +0100 Subject: [PATCH 1/5] [VFS] Added encoded VFS sizes --- TACTLib/Core/VFS/VFSFile.cs | 1 + TACTLib/Core/VFS/VFSManifestReader.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/TACTLib/Core/VFS/VFSFile.cs b/TACTLib/Core/VFS/VFSFile.cs index 1b6d6bd..7590981 100644 --- a/TACTLib/Core/VFS/VFSFile.cs +++ b/TACTLib/Core/VFS/VFSFile.cs @@ -1,6 +1,7 @@ namespace TACTLib.Core.VFS { public class VFSFile { public string? Name; + public int EncodedSize; public int ContentSize; public CKey EKey; public int Offset; diff --git a/TACTLib/Core/VFS/VFSManifestReader.cs b/TACTLib/Core/VFS/VFSManifestReader.cs index 8028027..7589070 100644 --- a/TACTLib/Core/VFS/VFSManifestReader.cs +++ b/TACTLib/Core/VFS/VFSManifestReader.cs @@ -363,8 +363,10 @@ private static VFSFile ReadVfsSpanEntries(Manifest manifest, BinaryReader reader reader.BaseStream.Position = cftFileEntry; var eKey = reader.Read(); + var encSize = reader.ReadInt32BE(); VFSFile file = new VFSFile { Offset = fileOffset, + EncodedSize = encSize, ContentSize = spanSize, Name = null, EKey = eKey From ec82cdbb1a4d76c41a575a926b5863db59d03d58 Mon Sep 17 00:00:00 2001 From: Hazno21688 <62346467+Hazno-dev@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:10:48 +0100 Subject: [PATCH 2/5] Added missing client misc args/info Just added the missing platform/region strings for clarity --- .../Client/HandlerArgs/ClientCreateArgs_Tank.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/TACTLib/Client/HandlerArgs/ClientCreateArgs_Tank.cs b/TACTLib/Client/HandlerArgs/ClientCreateArgs_Tank.cs index 7d62c24..971e2e2 100644 --- a/TACTLib/Client/HandlerArgs/ClientCreateArgs_Tank.cs +++ b/TACTLib/Client/HandlerArgs/ClientCreateArgs_Tank.cs @@ -20,17 +20,27 @@ public class ClientCreateArgs_Tank : IHandlerArgs { /// Load manifest files. Flag here to allow disabling for development purposes /// public bool LoadManifest { get; set; } = true; - + /// /// Load bundles for lookup. Flag here to allow disabling for development purposes /// public bool LoadBundlesForLookup { get; set; } = true; - + public const string REGION_DEV = "DEV"; public const string REGION_CN = "CN"; public const string REGION_KR = "KR"; + public const string REGION_TW = "TW"; + public const string REGION_SG = "SG"; + public const string REGION_EU = "EU"; + public const string REGION_US = "US"; public const string PLATFORM_WIN = "Win"; public const string PLATFORM_WINPRISM = "WinPrism"; + public const string PLATFORM_XBOXONE = "XB1"; + public const string PLATFORM_XBOXSERIES = "XSX"; + public const string PLATFORM_PLAYSTATION4 = "PS4"; + public const string PLATFORM_PLAYSTATION5 = "PS5"; + public const string PLATFORM_NINTENDOSWITCH = "NX"; + public const string PLATFORM_NINTENDOSWITCH2 = "NX2"; } } From 91387b5b2a5fbcaa6e92ff684e021301facd0f9c Mon Sep 17 00:00:00 2001 From: Hazno21688 <62346467+Hazno-dev@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:17:06 +0100 Subject: [PATCH 3/5] Reworked build config parser with generics + array lookup --- TACTLib/Config/BuildConfig.cs | 118 +++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 39 deletions(-) diff --git a/TACTLib/Config/BuildConfig.cs b/TACTLib/Config/BuildConfig.cs index e11a857..548f8c3 100644 --- a/TACTLib/Config/BuildConfig.cs +++ b/TACTLib/Config/BuildConfig.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Text; namespace TACTLib.Config { public class BuildConfig : Config { @@ -10,67 +13,104 @@ public class BuildConfig : Config { public FileRecord? Download; public FileRecord Encoding; public SizeRecord? EncodingSize; + public FileRecord? VFSRoot; - + public SizeRecord? VFSRootSize; + public IReadOnlyList? VFSManifests; + public IReadOnlyList? VFSManifestsSize; + public string GetBuildName() => Values["build-name"][0]; - + public BuildConfig(Stream? stream) : base(stream) { - GetFileRecord("root", out var root); - GetFileRecord("install", out Install); - GetFileRecord("patch", out Patch); - GetFileRecord("download", out Download); - GetFileRecord("encoding", out var encoding); - GetSizeRecord("encoding-size", out EncodingSize); - GetFileRecord("vfs-root", out VFSRoot); - - if (root == null) throw new NullReferenceException(nameof(root)); - Root = root; - - if (encoding == null) throw new NullReferenceException(nameof(encoding)); - Encoding = encoding; + GetRecord("root", out Root); + TryGetRecord("install", out Install); + TryGetRecord("patch", out Patch); + TryGetRecord("download", out Download); + GetRecord("encoding", out Encoding); + TryGetRecord("encoding-size", out EncodingSize); + + TryGetRecord("vfs-root", out VFSRoot); + TryGetRecord("vfs-root-size", out VFSRootSize); + TryGetRecords("vfs-{0}", 1, out VFSManifests); + TryGetRecords("vfs-{0}-size", 1, out VFSManifestsSize); } - private void GetFileRecord(string key, out FileRecord? @out) { - if (!Values.TryGetValue(key, out var list)) { - @out = null; - return; + private void GetRecord(string key, out T @out) where T : IBuildConfigRecord { + if (!TryGetRecord(key, out @out!)) { + throw new NullReferenceException($"Failed to find \"{key}\" in the build config."); } - @out = GetFileRecord(list); } - - private void GetSizeRecord(string key, out SizeRecord? @out) { - if (!Values.TryGetValue(key, out var list)) { - @out = null; - return; + + private bool TryGetRecord(string key, [NotNullWhen(true)] out T? @out) where T : IBuildConfigRecord { + if (!Values.TryGetValue(key, out var vals)) { + @out = default; + return false; + } + + @out = T.Decode(vals); + return true; + } + + private void GetRecords(string key, int baseIter, out IReadOnlyList @out) where T : IBuildConfigRecord { + if (!TryGetRecords(key, baseIter, out @out!)) { + throw new NullReferenceException($"Failed to find any \"{key}\" build config records."); } - @out = new SizeRecord { - ContentSize = int.Parse(list[0]), - EncodedSize = int.Parse(list[1]) - }; } - private static FileRecord GetFileRecord(IReadOnlyList vals) { - FileRecord record = new FileRecord(); + private bool TryGetRecords(string key, int baseIter, [NotNullWhen(true)] out IReadOnlyList? @out) where T : IBuildConfigRecord { + Debug.Assert(key.Contains("{0}")); - if (vals.Count > 0) { - record.ContentKey = CKey.FromString(vals[0]); + if (!Values.TryGetValue(string.Format(key, baseIter++), out var baseVals)) { + @out = null; + return false; } - if (vals.Count > 1) { - record.EncodingKey = FullEKey.FromString(vals[1]); + var values = new List { T.Decode(baseVals) }; + @out = values; + while (true) { + if (!Values.TryGetValue(string.Format(key, baseIter++), out var vals)) { + break; + } + + values.Add(T.Decode(vals)); } - - return record; + + return true; + } + + private interface IBuildConfigRecord where T : IBuildConfigRecord { + abstract static T Decode(List vals); } - public class FileRecord { + public class FileRecord : IBuildConfigRecord { public CKey ContentKey; public FullEKey EncodingKey; + + public static FileRecord Decode(List vals) { + FileRecord record = new FileRecord(); + + if (vals.Count > 0) { + record.ContentKey = CKey.FromString(vals[0]); + } + + if (vals.Count > 1) { + record.EncodingKey = FullEKey.FromString(vals[1]); + } + + return record; + } } - public class SizeRecord { + public class SizeRecord : IBuildConfigRecord { public int ContentSize; public int EncodedSize; + + public static SizeRecord Decode(List vals) { + return new SizeRecord { + ContentSize = int.Parse(vals[0]), + EncodedSize = int.Parse(vals[1]) + }; + } } } } \ No newline at end of file From 7efd915a8d3bdc2fbeecdddd894dc64ace239444 Mon Sep 17 00:00:00 2001 From: Hazno21688 <62346467+Hazno-dev@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:47:45 +0100 Subject: [PATCH 4/5] adjusted build config nullability --- TACTLib/Config/BuildConfig.cs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/TACTLib/Config/BuildConfig.cs b/TACTLib/Config/BuildConfig.cs index 548f8c3..0f49267 100644 --- a/TACTLib/Config/BuildConfig.cs +++ b/TACTLib/Config/BuildConfig.cs @@ -35,38 +35,42 @@ public BuildConfig(Stream? stream) : base(stream) { TryGetRecords("vfs-{0}-size", 1, out VFSManifestsSize); } - private void GetRecord(string key, out T @out) where T : IBuildConfigRecord { - if (!TryGetRecord(key, out @out!)) { - throw new NullReferenceException($"Failed to find \"{key}\" in the build config."); + private void GetRecord(string key, out T record) where T : IBuildConfigRecord { + if (!TryGetRecord(key, out T? res)) { + throw new KeyNotFoundException($"Failed to find \"{key}\" in the build config."); } + + record = res; } - private bool TryGetRecord(string key, [NotNullWhen(true)] out T? @out) where T : IBuildConfigRecord { + private bool TryGetRecord(string key, [NotNullWhen(true)] out T? record) where T : IBuildConfigRecord { if (!Values.TryGetValue(key, out var vals)) { - @out = default; + record = default; return false; } - @out = T.Decode(vals); + record = T.Decode(vals); return true; } - private void GetRecords(string key, int baseIter, out IReadOnlyList @out) where T : IBuildConfigRecord { - if (!TryGetRecords(key, baseIter, out @out!)) { - throw new NullReferenceException($"Failed to find any \"{key}\" build config records."); + private void GetRecords(string key, int baseIter, out IReadOnlyList record) where T : IBuildConfigRecord { + if (!TryGetRecords(key, baseIter, out IReadOnlyList? res)) { + throw new KeyNotFoundException($"Failed to find any \"{key}\" build config records."); } + + record = res; } - private bool TryGetRecords(string key, int baseIter, [NotNullWhen(true)] out IReadOnlyList? @out) where T : IBuildConfigRecord { + private bool TryGetRecords(string key, int baseIter, [MaybeNullWhen(false)] out IReadOnlyList record) where T : IBuildConfigRecord { Debug.Assert(key.Contains("{0}")); if (!Values.TryGetValue(string.Format(key, baseIter++), out var baseVals)) { - @out = null; + record = null; return false; } var values = new List { T.Decode(baseVals) }; - @out = values; + record = values; while (true) { if (!Values.TryGetValue(string.Format(key, baseIter++), out var vals)) { break; From 8556d4486fe94934f81054bcb3a94e841838af95 Mon Sep 17 00:00:00 2001 From: Hazno21688 <62346467+Hazno-dev@users.noreply.github.com> Date: Sat, 26 Sep 2026 11:14:11 +0100 Subject: [PATCH 5/5] Adde Peek to stream extensions --- TACTLib/Helpers/Extensions.cs | 38 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/TACTLib/Helpers/Extensions.cs b/TACTLib/Helpers/Extensions.cs index 75fb3c3..0b17593 100644 --- a/TACTLib/Helpers/Extensions.cs +++ b/TACTLib/Helpers/Extensions.cs @@ -6,7 +6,7 @@ namespace TACTLib.Helpers { public static class Extensions { #region BinaryReader - + public static void DefinitelyRead(this Stream stream, Span buffer) { stream.ReadExactly(buffer); @@ -16,7 +16,7 @@ public static void DefinitelyRead(this BinaryReader reader, Span buffer) { DefinitelyRead(reader.BaseStream, buffer); } - + /// /// Read struct from a BinaryReader /// @@ -27,14 +27,14 @@ public static T Read(this BinaryReader reader) where T : unmanaged { return reader.BaseStream.Read(); } - + public static unsafe T Read(this Stream stream) where T : unmanaged { var result = default(T); stream.DefinitelyRead(new Span(&result, sizeof(T))); return result; } - + /// /// Read array of structs from a reader /// @@ -46,16 +46,34 @@ public static T[] ReadArray(this BinaryReader reader, int count) where T : un { return reader.BaseStream.ReadArray(count); } - + public static T[] ReadArray(this Stream stream, int count) where T : unmanaged { if (count == 0) return Array.Empty(); - + var result = new T[count]; stream.DefinitelyRead(result.AsSpan().AsBytes()); return result; } + /// + /// Peek a value from the stream without incrementing the position + /// + /// Target reader + /// Struct to read + /// Read struct + public static T Peek(this BinaryReader reader) where T : unmanaged + { + return reader.BaseStream.Peek(); + } + + public static unsafe T Peek(this Stream stream) where T : unmanaged + { + var value = stream.Read(); + stream.Position -= sizeof(T); + return value; + } + /// /// Write a struct to a BinaryWriter /// @@ -67,7 +85,7 @@ public static void Write(this BinaryWriter writer, T @struct) where T : unman var bytes = MemoryMarshal.CreateReadOnlySpan(ref @struct, 1).AsBytes(); writer.Write(bytes); } - + /// /// Write an array of structs to a BinaryWriter /// @@ -79,7 +97,7 @@ public static void WriteStructArray(this BinaryWriter writer, T[] @struct) wh var bytes = @struct.AsSpan().AsBytes(); writer.Write(bytes); } - + /// Read a big endian 32-bit int // ReSharper disable once InconsistentNaming public static int ReadInt32BE(this BinaryReader reader) @@ -93,7 +111,7 @@ public static short ReadInt16BE(this BinaryReader reader) { return (short)ReadUInt16BE(reader); } - + /// Read a big endian 16-bit uint // ReSharper disable once InconsistentNaming public static ushort ReadUInt16BE(this BinaryReader reader) @@ -110,7 +128,7 @@ public static int ReadInt24BE(this BinaryReader reader) return s.ToInt(); } #endregion - + /// Convert to a hexadecimal string public static string ToHexString(this ReadOnlySpan data) {