4 Commits
Author SHA1 Message Date
ds5678 f94757d204 Bump version to 2.0.0 2026-07-03 11:17:38 -07:00
ds5678 474fe30489 Revert "Do not archive CI artifacts"
This reverts commit 70c3b6ca93.
2026-07-01 23:56:45 -07:00
ds5678 70c3b6ca93 Do not archive CI artifacts 2026-07-01 23:19:46 -07:00
ds5678 6b72bf855d Update binary format to support Unity 6.6 common string 2026-07-01 22:47:10 -07:00
5 changed files with 106 additions and 77 deletions

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@
<Authors>ds5678</Authors>
<Company>AssetRipper</Company>
<VersionPrefix>1.2.1</VersionPrefix>
<VersionPrefix>2.0.0</VersionPrefix>
<RepositoryUrl>https://github.com/AssetRipper/Tpk</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
+27 -23
View File
@@ -1,4 +1,5 @@
using AssetRipper.Primitives;
using AssetRipper.Tpk.Shared;
using AssetRipper.Tpk.TypeTrees;
using AssetRipper.Tpk.TypeTrees.Json;
using System.Collections.Generic;
@@ -40,37 +41,25 @@ namespace AssetRipper.Tpk.ConsoleApp
private static TpkTypeTreeBlob Create(IEnumerable<(UnityVersion Version, UnityInfo Info)> infosOrderedByUnityVersion)
{
TpkTypeTreeBlob blob = new TpkTypeTreeBlob();
blob.CommonString.Add(UnityVersion.MinVersion, 0);
byte latestCommonStringCount = 0;
List<string> commonStrings = new List<string>();
Dictionary<int, string> latestUnityClassesDumped = new Dictionary<int, string>();
Dictionary<int, TpkClassInformation> classDictionary = new Dictionary<int, TpkClassInformation>();
List<TpkCommonString.Entry> commonStrings = [];
Dictionary<int, string> latestUnityClassesDumped = [];
Dictionary<int, TpkClassInformation> classDictionary = [];
foreach ((UnityVersion version, UnityInfo? info) in infosOrderedByUnityVersion)
{
Console.WriteLine(version.ToString());
blob.Versions.Add(version);
if (info.Strings.Count != latestCommonStringCount)
if (!Same(info.Strings, commonStrings, blob.StringBuffer))
{
latestCommonStringCount = (byte)info.Strings.Count;
blob.CommonString.Add(version, latestCommonStringCount);
}
for (int i = 0; i < info.Strings.Count; i++)
{
if (i < commonStrings.Count)
commonStrings.Clear();
commonStrings.EnsureCapacity(commonStrings.Count);
foreach (UnityString unityString in info.Strings)
{
if (info.Strings[i].String != commonStrings[i])
{
throw new Exception($"String inequality at index {i} for version {version}");
}
}
else
{
commonStrings.Add(info.Strings[i].String);
commonStrings.Add(new TpkCommonString.Entry((ushort)unityString.Index, unityString.String, blob.StringBuffer));
}
blob.CommonString.Add(version, commonStrings.ToArray());
}
foreach (UnityClass unityClass in info.Classes)
@@ -120,8 +109,7 @@ namespace AssetRipper.Tpk.ConsoleApp
blob.ClassInformation.AddRange(classDictionary.Values);
blob.CommonString.SetIndices(blob.StringBuffer, commonStrings);
//About 21k / 65k
//About 26k / 65k
Console.WriteLine($"Node buffer has {blob.NodeBuffer.Count} entries, which is {GetUShortPercent(blob.NodeBuffer.Count)}% of its maximum {ushort.MaxValue} entries");
//About 7k / 65k
Console.WriteLine($"String buffer has {blob.StringBuffer.Count} entries, which is {GetUShortPercent(blob.StringBuffer.Count)}% of its maximum {ushort.MaxValue} entries");
@@ -131,6 +119,22 @@ namespace AssetRipper.Tpk.ConsoleApp
return blob;
}
private static bool Same(List<UnityString> jsonStrings, List<TpkCommonString.Entry> tpkEntries, TpkStringBuffer stringBuffer)
{
if (jsonStrings.Count != tpkEntries.Count)
{
return false;
}
for (int i = 0; i < jsonStrings.Count; i++)
{
if (jsonStrings[i].String != tpkEntries[i].ToString(stringBuffer))
{
return false;
}
}
return true;
}
private static int GetUShortPercent(int value) => value * 100 / ushort.MaxValue;
}
}
+28 -8
View File
@@ -1,6 +1,7 @@
using AssetRipper.Primitives;
using AssetRipper.Tpk.TypeTrees;
using NUnit.Framework;
using System.IO;
namespace AssetRipper.Tpk.Tests.TypeTrees
{
@@ -16,7 +17,7 @@ namespace AssetRipper.Tpk.Tests.TypeTrees
public static void CountIsCorrectForEmptyCommonString()
{
TpkCommonString commonString = new();
Assert.That(commonString.GetCount(Unity5), Is.Zero);
Assert.That(commonString.GetEntries(Unity5), Is.Empty);
}
[Test]
@@ -25,19 +26,38 @@ namespace AssetRipper.Tpk.Tests.TypeTrees
TpkCommonString commonString = MakeCommonString();
using (Assert.EnterMultipleScope())
{
Assert.That(commonString.GetCount(Unity3), Is.EqualTo(5));
Assert.That(commonString.GetCount(Unity4), Is.EqualTo(5));
Assert.That(commonString.GetCount(Unity5), Is.EqualTo(5));
Assert.That(commonString.GetCount(Unity6), Is.EqualTo(10));
Assert.That(commonString.GetCount(Unity7), Is.EqualTo(10));
Assert.That(commonString.GetEntries(Unity3), Has.Length.EqualTo(5));
Assert.That(commonString.GetEntries(Unity4), Has.Length.EqualTo(5));
Assert.That(commonString.GetEntries(Unity5), Has.Length.EqualTo(5));
Assert.That(commonString.GetEntries(Unity6), Has.Length.EqualTo(10));
Assert.That(commonString.GetEntries(Unity7), Has.Length.EqualTo(10));
}
}
[Test]
public static void ReadingAndWritingUseSameBytes()
{
TpkCommonString commonString = MakeCommonString();
MemoryStream stream = new();
BinaryWriter writer = new(stream);
commonString.Write(writer);
writer.Flush();
int bytesWritten = (int)stream.Length;
stream.Position = 0;
BinaryReader reader = new(stream);
TpkCommonString readCommonString = new();
readCommonString.Read(reader);
int bytesRead = (int)stream.Position;
Assert.That(bytesRead, Is.EqualTo(bytesWritten));
}
private static TpkCommonString MakeCommonString()
{
TpkCommonString commonString = new();
commonString.Add(Unity4, 5);
commonString.Add(Unity6, 10);
commonString.Add(Unity4, new TpkCommonString.Entry[5]);
commonString.Add(Unity6, new TpkCommonString.Entry[10]);
return commonString;
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ namespace AssetRipper.Tpk
public sealed class TpkFile
{
public const uint TpkMagicBytes = 0x2A4B5054; // TPK* in binary
public const byte TpkVersionNumber = 1;
public const byte TpkVersionNumber = 2;
public TpkCompressionType CompressionType { get; private set; }
public TpkDataType DataType { get; private set; }
+49 -44
View File
@@ -9,33 +9,51 @@ namespace AssetRipper.Tpk.TypeTrees
public sealed class TpkCommonString
{
/// <summary>
/// Unity version : string count<br/>
/// Sequential by ascending Unity version
/// An entry in <see cref="TpkCommonString"/>
/// </summary>
public List<KeyValuePair<UnityVersion, byte>> VersionInformation { get; } = new();
public List<ushort> StringBufferIndices { get; } = new();
public void Add(UnityVersion version, byte count)
/// <param name="Offset">The byte offset of the string</param>
/// <param name="String">The index of the string in <see cref="TpkStringBuffer"/></param>
public readonly record struct Entry(ushort Offset, ushort String)
{
VersionInformation.Add(new KeyValuePair<UnityVersion, byte>(version, count));
}
public void SetIndices(TpkStringBuffer buffer, List<string> strings)
{
int count = strings.Count;
StringBufferIndices.Clear();
StringBufferIndices.Capacity = count;
for (int i = 0; i < count; i++)
public Entry(ushort offset, string @string, TpkStringBuffer buffer) : this(offset, buffer.AddString(@string))
{
StringBufferIndices.Add(buffer.AddString(strings[i]));
}
public string ToString(TpkStringBuffer buffer)
{
return buffer[String];
}
public static Entry Read(BinaryReader reader)
{
ushort offset = reader.ReadUInt16();
ushort stringIndex = reader.ReadUInt16();
return new Entry(offset, stringIndex);
}
public void Write(BinaryWriter writer)
{
writer.Write(Offset);
writer.Write(String);
}
}
public byte GetCount(UnityVersion exactVersion)
/// <summary>
/// Unity version : string count<br/>
/// Sequential by ascending Unity version
/// </summary>
public List<KeyValuePair<UnityVersion, Entry[]>> VersionInformation { get; } = new();
public void Add(UnityVersion version, Entry[] entries)
{
VersionInformation.Add(new KeyValuePair<UnityVersion, Entry[]>(version, entries));
}
public Entry[] GetEntries(UnityVersion exactVersion)
{
if (VersionInformation.Count == 0)
{
return 0;
return [];
}
for (int i = 1; i < VersionInformation.Count; i++)
{
@@ -47,17 +65,6 @@ namespace AssetRipper.Tpk.TypeTrees
return VersionInformation[^1].Value;
}
public string[] GetStrings(TpkStringBuffer buffer)
{
int length = StringBufferIndices.Count;
string[] strings = new string[length];
for (int i = 0; i < length; i++)
{
strings[i] = buffer[StringBufferIndices[i]];
}
return strings;
}
public void Read(BinaryReader reader)
{
int versionCount = reader.ReadInt32();
@@ -66,15 +73,13 @@ namespace AssetRipper.Tpk.TypeTrees
for (int i = 0; i < versionCount; i++)
{
UnityVersion version = reader.ReadUnityVersion();
byte stringCount = reader.ReadByte();
VersionInformation.Add(new KeyValuePair<UnityVersion, byte>(version, stringCount));
}
int indicesCount = reader.ReadInt32();
StringBufferIndices.Clear();
StringBufferIndices.Capacity = indicesCount;
for (int j = 0; j < indicesCount; j++)
{
StringBufferIndices.Add(reader.ReadUInt16());
int entryCount = reader.ReadInt32();
Entry[] entries = new Entry[entryCount];
for (int j = 0; j < entryCount; j++)
{
entries[j] = Entry.Read(reader);
}
VersionInformation.Add(new KeyValuePair<UnityVersion, Entry[]>(version, entries));
}
}
@@ -84,12 +89,12 @@ namespace AssetRipper.Tpk.TypeTrees
for (int i = 0; i < VersionInformation.Count; i++)
{
writer.Write(VersionInformation[i].Key);
writer.Write(VersionInformation[i].Value);
}
writer.Write(StringBufferIndices.Count);
for (int i = 0; i < StringBufferIndices.Count; i++)
{
writer.Write(StringBufferIndices[i]);
Entry[] entries = VersionInformation[i].Value;
writer.Write(entries.Length);
for (int j = 0; j < entries.Length; j++)
{
entries[j].Write(writer);
}
}
}
}