diff --git a/AssetStudio/BundleFile.cs b/AssetStudio/BundleFile.cs index 1263e4c..9901bfd 100644 --- a/AssetStudio/BundleFile.cs +++ b/AssetStudio/BundleFile.cs @@ -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(sharedCompressedBuff, 0, compressedSize); var uncompressedSpan = new Span(sharedUncompressedBuff, 0, uncompressedSize); diff --git a/AssetStudio/ObjectReader.cs b/AssetStudio/ObjectReader.cs index 3fb38d6..7101ffd 100644 --- a/AssetStudio/ObjectReader.cs +++ b/AssetStudio/ObjectReader.cs @@ -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(); } diff --git a/AssetStudio/SerializedFile.cs b/AssetStudio/SerializedFile.cs index f00765c..cd001c5 100644 --- a/AssetStudio/SerializedFile.cs +++ b/AssetStudio/SerializedFile.cs @@ -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 SharedSubTreeInfoDict = new ConcurrentDictionary(); + public AssetsManager assetsManager; public FileReader reader; public string fullName; @@ -15,7 +18,8 @@ namespace AssetStudio public UnityVersion version = new UnityVersion(); public readonly List Objects; public readonly Dictionary ObjectsDic; - public readonly Dictionary> ExtractedTypeTreeHashes = new Dictionary>(); + private readonly Dictionary> extractedTypeTreeDict = new Dictionary>(); + 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 m_Nodes, List 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 { type }; + extractedTypeTreeDict[typeTreeHash] = new List { type }; } } } @@ -340,6 +413,15 @@ namespace AssetStudio { ReadTypeTree(type.m_Type); } + if (header.m_Version >= SerializedFileFormatVersion.SharedSubTreeSupport && !IsTypeTreeExtracted) + { + type.subTreeHashList = new List(); + 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(); TypeTreeBlobRead(serializedType.m_Type, fileReader); + if (header.m_Version >= SerializedFileFormatVersion.SharedSubTreeSupport) + { + serializedType.subTreeHashList = new List(); + 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(); + TypeTreeBlobRead(m_Type, subTreeReader); + var subTreeDependencies = new List(); + 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(); + } } } diff --git a/AssetStudio/SerializedFileFormatVersion.cs b/AssetStudio/SerializedFileFormatVersion.cs index 6322a09..d321672 100644 --- a/AssetStudio/SerializedFileFormatVersion.cs +++ b/AssetStudio/SerializedFileFormatVersion.cs @@ -78,16 +78,23 @@ /// LargeFilesSupport = 22, /// - /// 6000.5.0a5 to x + /// 6000.5.0a5 to 6000.6 /// ExtractedTypeTreeSupport = 23, /// - /// 6000.7.0a6 to x + /// 6000.7.0a6 /// IndependentTypeTreeVersion = 24, /// - /// 6000.7.0a6 to x + /// 6000.7.0a6 /// SerializeReferenceRegistryFirst = 25, + /// + /// 6000.7.0b2 to x + /// + SharedSubTreeSupport = 26, + + // used by AssetStudio + NotTested } } diff --git a/AssetStudio/SerializedType.cs b/AssetStudio/SerializedType.cs index 4be3d6b..821f0b8 100644 --- a/AssetStudio/SerializedType.cs +++ b/AssetStudio/SerializedType.cs @@ -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 subTreeHashList; public bool Match(params ulong[] hashes) { diff --git a/AssetStudio/SubTreeInfo.cs b/AssetStudio/SubTreeInfo.cs new file mode 100644 index 0000000..3d55885 --- /dev/null +++ b/AssetStudio/SubTreeInfo.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace AssetStudio +{ + public class SubTreeInfo + { + public List SubTreeNodes; + public List SubTreeDependencies; + } +} diff --git a/AssetStudio/TypeTree.cs b/AssetStudio/TypeTree.cs index a10a2d0..43ba908 100644 --- a/AssetStudio/TypeTree.cs +++ b/AssetStudio/TypeTree.cs @@ -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 { diff --git a/AssetStudio/TypeTreeHelper.cs b/AssetStudio/TypeTreeHelper.cs index 14fa5bd..8e7b622 100644 --- a/AssetStudio/TypeTreeHelper.cs +++ b/AssetStudio/TypeTreeHelper.cs @@ -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 diff --git a/AssetStudio/TypeTreeNode.cs b/AssetStudio/TypeTreeNode.cs index f5de3c0..63da7d9 100644 --- a/AssetStudio/TypeTreeNode.cs +++ b/AssetStudio/TypeTreeNode.cs @@ -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; diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index c113a69..1fa7437 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -1600,6 +1600,7 @@ namespace AssetStudioGUI Text = guiTitle; AssetItem.Reset(); + SerializedFile.Clear(); Studio.assetsManager.Clear(); Studio.assemblyLoader.Clear(); Studio.exportableAssets.Clear();