Improve support for Unity 6.7+

- Added support for SerializedFile v26
- "Build Pipeline: Archives built with LZ4 compression now store each compressed chunk at a 16 byte aligned offset and cut chunks on file and serialized object boundaries..."
This commit is contained in:
VaDiM
2026-10-06 20:25:29 +03:00
parent cc9bd41c27
commit 1f2bc05d48
10 changed files with 209 additions and 29 deletions
+9
View File
@@ -394,6 +394,10 @@ namespace AssetStudio
compressedSize = blocksInfoReader.ReadUInt32(),
flags = (StorageBlockFlags)blocksInfoReader.ReadUInt16()
};
if (m_Header.version >= 9)
{
var offset = blocksInfoReader.ReadUInt64();
}
}
var nodesCount = blocksInfoReader.ReadInt32();
@@ -483,6 +487,11 @@ namespace AssetStudio
var read = reader.Read(sharedCompressedBuff, 0, compressedSize);
debugMsg += $"(read: {read.ToString().ColorIf(read != compressedSize, ColorConsole.BrightRed)})";
// https://unity.com/releases/editor/beta/6000.7.0b2#notes
if (m_Header.version >= 9)
{
reader.AlignStream(16);
}
var compressedSpan = new ReadOnlySpan<byte>(sharedCompressedBuff, 0, compressedSize);
var uncompressedSpan = new Span<byte>(sharedUncompressedBuff, 0, uncompressedSize);
+1 -1
View File
@@ -39,7 +39,7 @@ namespace AssetStudio
platform = assetsFile.m_TargetPlatform;
m_Version = assetsFile.header.m_Version;
if (assetsFile.ExtractedTypeTreeHashes.Count > 0)
if (assetsFile.IsTypeTreeExtracted)
{
assetsFile.ReadExtractedTypeTree();
}
+157 -12
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -7,6 +8,8 @@ namespace AssetStudio
{
public class SerializedFile
{
private static readonly ConcurrentDictionary<string, SubTreeInfo> SharedSubTreeInfoDict = new ConcurrentDictionary<string, SubTreeInfo>();
public AssetsManager assetsManager;
public FileReader reader;
public string fullName;
@@ -15,7 +18,8 @@ namespace AssetStudio
public UnityVersion version = new UnityVersion();
public readonly List<Object> Objects;
public readonly Dictionary<long, Object> ObjectsDic;
public readonly Dictionary<string, List<SerializedType>> ExtractedTypeTreeHashes = new Dictionary<string, List<SerializedType>>();
private readonly Dictionary<string, List<SerializedType>> extractedTypeTreeDict = new Dictionary<string, List<SerializedType>>();
public bool IsTypeTreeExtracted => extractedTypeTreeDict.Count > 0;
public SerializedFileHeader header;
private byte m_FileEndianess;
@@ -69,6 +73,10 @@ namespace AssetStudio
{
reader.Endian = EndianType.LittleEndian;
}
if (header.m_Version >= SerializedFileFormatVersion.NotTested)
{
Logger.Warning($"Unknown SerializedFile version detected [{(int)header.m_Version}]");
}
switch (header.m_Version) //default unity versions
{
case SerializedFileFormatVersion.Unsupported:
@@ -241,6 +249,31 @@ namespace AssetStudio
}
}
if (header.m_Version >= SerializedFileFormatVersion.SharedSubTreeSupport)
{
var sharedSubtreeCount = reader.ReadUInt32();
for (var i = 0; i < sharedSubtreeCount; i++)
{
var subTreeHash = reader.ReadBytesAsHexString(16);
var subTreeBlobSize = reader.ReadInt32();
var subTreeInfo = subTreeBlobSize > 0
? ReadSubTreeInfo(reader)
: null; //extracted subTree
SharedSubTreeInfoDict.TryAdd(subTreeHash, subTreeInfo);
}
foreach (var serializedType in m_Types)
{
if (serializedType.subTreeHashList == null || serializedType.subTreeHashList.Count <= 0)
continue;
if (!ResolveSubTrees(serializedType.m_Type.m_Nodes, serializedType.subTreeHashList))
{
serializedType.m_Type = null;
}
}
}
if (header.m_Version >= SerializedFileFormatVersion.Unknown_5)
{
userInformation = reader.ReadStringToNull();
@@ -274,8 +307,48 @@ namespace AssetStudio
}
}
}
}
//reader.AlignStream(16);
private bool ResolveSubTrees(List<TypeTreeNode> m_Nodes, List<string> subTreeHashes)
{
try
{
for (var i = 0; i < m_Nodes.Count; i++)
{
var m_Node = m_Nodes[i];
var isSharedSubTreeNode = (m_Node.m_TypeFlags & 0x20) != 0;
if (!isSharedSubTreeNode)
continue;
var hashIndex = (int)m_Node.m_RefTypeHash;
if (SharedSubTreeInfoDict.TryGetValue(subTreeHashes[hashIndex], out var subTreeInfo))
{
if (subTreeInfo.SubTreeDependencies.Count > 0)
{
ResolveSubTrees(subTreeInfo.SubTreeNodes, subTreeInfo.SubTreeDependencies);
}
m_Node.m_TypeFlags = subTreeInfo.SubTreeNodes[0].m_TypeFlags;
for (var k = 1; k < subTreeInfo.SubTreeNodes.Count; k++) //skip Base node
{
var subTreeNode = new TypeTreeNode(subTreeInfo.SubTreeNodes[k]); //deep copy
subTreeNode.m_Level += m_Node.m_Level;
subTreeNode.m_Index = m_Node.m_Index + k;
m_Nodes.Insert(i + k, subTreeNode);
}
i += subTreeInfo.SubTreeNodes.Count - 1;
}
else
{
throw new IOException($"Can't find a shared subtree for the \"{m_Node.m_Type}\" type");
}
}
return true;
}
catch (Exception e)
{
Logger.Warning($"[SubTree] {e.Message}\n{e.StackTrace}");
return false;
}
}
private SerializedType ReadSerializedType(bool isRefType)
@@ -322,13 +395,13 @@ namespace AssetStudio
else
{
type.m_Type = null;
if (ExtractedTypeTreeHashes.TryGetValue(typeTreeHash, out var types))
if (extractedTypeTreeDict.TryGetValue(typeTreeHash, out var types))
{
types.Add(type);
}
else
{
ExtractedTypeTreeHashes[typeTreeHash] = new List<SerializedType> { type };
extractedTypeTreeDict[typeTreeHash] = new List<SerializedType> { type };
}
}
}
@@ -340,6 +413,15 @@ namespace AssetStudio
{
ReadTypeTree(type.m_Type);
}
if (header.m_Version >= SerializedFileFormatVersion.SharedSubTreeSupport && !IsTypeTreeExtracted)
{
type.subTreeHashList = new List<string>();
var sharedSubTreeCount = reader.ReadInt32();
for (var i = 0; i < sharedSubTreeCount; i++)
{
type.subTreeHashList.Add(reader.ReadBytesAsHexString(16));
}
}
if (header.m_Version >= SerializedFileFormatVersion.StoresTypeDependencies)
{
if (isRefType)
@@ -354,7 +436,6 @@ namespace AssetStudio
}
}
}
return type;
}
@@ -469,26 +550,36 @@ namespace AssetStudio
public void ReadExtractedTypeTree()
{
var hashes = ExtractedTypeTreeHashes.Keys.ToArray();
var hashes = extractedTypeTreeDict.Keys.ToArray();
foreach (var hashStr in hashes)
{
if (!assetsManager.ResourceFileReaders.TryGetValue(hashStr, out var binaryReader))
if (!assetsManager.ResourceFileReaders.TryGetValue(hashStr, out var binaryReader))
continue;
var fileReader = (FileReader)binaryReader;
fileReader.Endian = EndianType.LittleEndian;
var serializedTypeList = ExtractedTypeTreeHashes[hashStr];
var serializedTypeList = extractedTypeTreeDict[hashStr];
foreach (var serializedType in serializedTypeList)
{
fileReader.Position = 0;
serializedType.m_Type = new TypeTree();
serializedType.m_Type.m_Nodes = new List<TypeTreeNode>();
TypeTreeBlobRead(serializedType.m_Type, fileReader);
if (header.m_Version >= SerializedFileFormatVersion.SharedSubTreeSupport)
{
serializedType.subTreeHashList = new List<string>();
var sharedSubTreeCount = fileReader.ReadInt32();
for (var i = 0; i < sharedSubTreeCount; i++)
{
serializedType.subTreeHashList.Add(fileReader.ReadBytesAsHexString(16));
}
}
}
ExtractedTypeTreeHashes[hashStr] = null;
extractedTypeTreeDict[hashStr] = null;
}
var notFound = ExtractedTypeTreeHashes.Where(x=> x.Value != null).ToArray();
if (notFound.Length > 0 && notFound.Length < ExtractedTypeTreeHashes.Count)
var notFound = extractedTypeTreeDict.Where(x => x.Value != null).ToArray();
if (notFound.Length > 0 && notFound.Length < extractedTypeTreeDict.Count)
{
foreach (var kv in notFound)
{
@@ -497,7 +588,56 @@ namespace AssetStudio
Logger.Warning("Not all typetree hashes were found in the loaded typetree file\n" +
"The typetree file is probably outdated or not designed for the loaded asset bundles");
}
ExtractedTypeTreeHashes.Clear();
if (header.m_Version >= SerializedFileFormatVersion.SharedSubTreeSupport && notFound.Length < extractedTypeTreeDict.Count)
{
ReadExtractedSubTree();
}
extractedTypeTreeDict.Clear();
}
private void ReadExtractedSubTree()
{
var hashes = SharedSubTreeInfoDict.Keys.ToArray();
foreach (var subTreeHash in hashes)
{
if (!assetsManager.ResourceFileReaders.TryGetValue(subTreeHash, out var binaryReader))
continue;
var fileReader = (FileReader)binaryReader;
fileReader.Endian = EndianType.LittleEndian;
fileReader.Position = 0;
SharedSubTreeInfoDict[subTreeHash] = ReadSubTreeInfo(fileReader);
}
foreach (var serializedType in m_Types)
{
if (serializedType.subTreeHashList == null || serializedType.subTreeHashList.Count <= 0)
continue;
if (!ResolveSubTrees(serializedType.m_Type.m_Nodes, serializedType.subTreeHashList))
{
serializedType.m_Type = null;
}
}
}
private SubTreeInfo ReadSubTreeInfo(FileReader subTreeReader)
{
var m_Type = new TypeTree();
m_Type.m_Nodes = new List<TypeTreeNode>();
TypeTreeBlobRead(m_Type, subTreeReader);
var subTreeDependencies = new List<string>();
var subTreeDependenciesCount = subTreeReader.ReadUInt32();
for (var i = 0; i < subTreeDependenciesCount; i++)
{
subTreeDependencies.Add(subTreeReader.ReadBytesAsHexString(16));
}
return new SubTreeInfo
{
SubTreeNodes = m_Type.m_Nodes,
SubTreeDependencies = subTreeDependencies,
};
}
public void AddObject(Object obj)
@@ -505,5 +645,10 @@ namespace AssetStudio
Objects.Add(obj);
ObjectsDic.Add(obj.m_PathID, obj);
}
public static void Clear()
{
SharedSubTreeInfoDict.Clear();
}
}
}
+10 -3
View File
@@ -78,16 +78,23 @@
/// </summary>
LargeFilesSupport = 22,
/// <summary>
/// 6000.5.0a5 to x
/// 6000.5.0a5 to 6000.6
/// </summary>
ExtractedTypeTreeSupport = 23,
/// <summary>
/// 6000.7.0a6 to x
/// 6000.7.0a6
/// </summary>
IndependentTypeTreeVersion = 24,
/// <summary>
/// 6000.7.0a6 to x
/// 6000.7.0a6
/// </summary>
SerializeReferenceRegistryFirst = 25,
/// <summary>
/// 6000.7.0b2 to x
/// </summary>
SharedSubTreeSupport = 26,
// used by AssetStudio
NotTested
}
}
+3 -1
View File
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
namespace AssetStudio
{
@@ -14,6 +15,7 @@ namespace AssetStudio
public string m_KlassName;
public string m_NameSpace;
public string m_AsmName;
public List<string> subTreeHashList;
public bool Match(params ulong[] hashes)
{
+10
View File
@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace AssetStudio
{
public class SubTreeInfo
{
public List<TypeTreeNode> SubTreeNodes;
public List<string> SubTreeDependencies;
}
}
+1 -5
View File
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace AssetStudio
{
+1 -1
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -11,6 +10,7 @@ namespace AssetStudio
public static class TypeTreeHelper
{
private static readonly JsonSerializerOptions JsonOptions;
static TypeTreeHelper()
{
JsonOptions = new JsonSerializerOptions
+16 -6
View File
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AssetStudio
namespace AssetStudio
{
public class TypeTreeNode
{
@@ -21,6 +16,21 @@ namespace AssetStudio
public TypeTreeNode() { }
public TypeTreeNode(TypeTreeNode node)
{
m_Type = node.m_Type;
m_Name = node.m_Name;
m_ByteSize = node.m_ByteSize;
m_Index = node.m_Index;
m_TypeFlags = node.m_TypeFlags;
m_Version = node.m_Version;
m_MetaFlag = node.m_MetaFlag;
m_Level = node.m_Level;
m_TypeStrOffset = node.m_TypeStrOffset;
m_NameStrOffset = node.m_NameStrOffset;
m_RefTypeHash = node.m_RefTypeHash;
}
public TypeTreeNode(string type, string name, int level, bool align)
{
m_Type = type;
+1
View File
@@ -1600,6 +1600,7 @@ namespace AssetStudioGUI
Text = guiTitle;
AssetItem.Reset();
SerializedFile.Clear();
Studio.assetsManager.Clear();
Studio.assemblyLoader.Clear();
Studio.exportableAssets.Clear();