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";
}
}
diff --git a/TACTLib/Config/BuildConfig.cs b/TACTLib/Config/BuildConfig.cs
index e11a857..0f49267 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,108 @@ 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 record) where T : IBuildConfigRecord {
+ if (!TryGetRecord(key, out T? res)) {
+ throw new KeyNotFoundException($"Failed to find \"{key}\" in the build config.");
}
- @out = GetFileRecord(list);
+
+ record = res;
}
-
- 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? record) where T : IBuildConfigRecord {
+ if (!Values.TryGetValue(key, out var vals)) {
+ record = default;
+ return false;
}
- @out = new SizeRecord {
- ContentSize = int.Parse(list[0]),
- EncodedSize = int.Parse(list[1])
- };
+
+ record = T.Decode(vals);
+ return true;
+ }
+
+ 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 static FileRecord GetFileRecord(IReadOnlyList vals) {
- FileRecord record = new FileRecord();
+ private bool TryGetRecords(string key, int baseIter, [MaybeNullWhen(false)] out IReadOnlyList record) 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)) {
+ record = null;
+ return false;
}
- if (vals.Count > 1) {
- record.EncodingKey = FullEKey.FromString(vals[1]);
+ var values = new List { T.Decode(baseVals) };
+ record = 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
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
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)
{