mirror of
https://github.com/StarBeat/UnityDependencyAnalyzer.git
synced 2026-03-08 05:35:27 +08:00
432 lines
15 KiB
C#
432 lines
15 KiB
C#
|
|
using System;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Text;
|
|||
|
|
namespace UnityFileApi
|
|||
|
|
{
|
|||
|
|
public class TextDumperTool
|
|||
|
|
{
|
|||
|
|
StringBuilder m_StringBuilder = new StringBuilder(1024);
|
|||
|
|
bool m_SkipLargeArrays;
|
|||
|
|
UnityFileReader m_Reader;
|
|||
|
|
SerializedFile m_SerializedFile;
|
|||
|
|
StreamWriter m_Writer;
|
|||
|
|
|
|||
|
|
public int Dump(string path, string outputPath, bool skipLargeArrays)
|
|||
|
|
{
|
|||
|
|
m_SkipLargeArrays = skipLargeArrays;
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using var archive = UnityFileSystem.MountArchive(path, "/");
|
|||
|
|
foreach (var node in archive.Nodes)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"Processing {node.Path} {node.Size} {node.Flags}");
|
|||
|
|
|
|||
|
|
if (node.Flags.HasFlag(ArchiveNodeFlags.SerializedFile))
|
|||
|
|
{
|
|||
|
|
using (m_Writer = new StreamWriter(Path.Combine(outputPath, Path.GetFileName(node.Path) + ".txt"), false))
|
|||
|
|
{
|
|||
|
|
OutputSerializedFile("/" + node.Path);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (NotSupportedException)
|
|||
|
|
{
|
|||
|
|
// Try as SerializedFile
|
|||
|
|
using (m_Writer = new StreamWriter(Path.Combine(outputPath, Path.GetFileName(path) + ".txt"), false))
|
|||
|
|
{
|
|||
|
|
OutputSerializedFile(path);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine("Error!");
|
|||
|
|
Console.Write($"{e.GetType()}: ");
|
|||
|
|
Console.WriteLine(e.Message);
|
|||
|
|
Console.WriteLine(e.StackTrace);
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void RecursiveDump(TypeTreeNode node, ref long offset, int level, int arrayIndex = -1)
|
|||
|
|
{
|
|||
|
|
bool skipChildren = false;
|
|||
|
|
|
|||
|
|
if (!node.IsArray)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(' ', level * 2);
|
|||
|
|
|
|||
|
|
if (level != 0)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(node.Name);
|
|||
|
|
if (arrayIndex >= 0)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append('[');
|
|||
|
|
m_StringBuilder.Append(arrayIndex);
|
|||
|
|
m_StringBuilder.Append(']');
|
|||
|
|
}
|
|||
|
|
m_StringBuilder.Append(' ');
|
|||
|
|
m_StringBuilder.Append('(');
|
|||
|
|
m_StringBuilder.Append(node.Type);
|
|||
|
|
m_StringBuilder.Append(')');
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(node.Type);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Basic data type.
|
|||
|
|
if (node.IsBasicType)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(' ');
|
|||
|
|
m_StringBuilder.Append(ReadValue(node, offset));
|
|||
|
|
|
|||
|
|
offset += node.Size;
|
|||
|
|
}
|
|||
|
|
else if (node.Type == "string")
|
|||
|
|
{
|
|||
|
|
var stringSize = m_Reader.ReadInt32(offset);
|
|||
|
|
|
|||
|
|
m_StringBuilder.Append(' ');
|
|||
|
|
m_StringBuilder.Append(m_Reader.ReadString(offset + 4, stringSize));
|
|||
|
|
|
|||
|
|
offset += stringSize + 4;
|
|||
|
|
|
|||
|
|
// Skip child nodes as they were already processed here.
|
|||
|
|
skipChildren = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine(m_StringBuilder);
|
|||
|
|
m_StringBuilder.Clear();
|
|||
|
|
|
|||
|
|
if (node.IsManagedReferenceRegistry)
|
|||
|
|
{
|
|||
|
|
DumpManagedReferenceRegistry(node, ref offset, level + 1);
|
|||
|
|
|
|||
|
|
// Skip child nodes as they were already processed here.
|
|||
|
|
skipChildren = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
DumpArray(node, ref offset, level);
|
|||
|
|
|
|||
|
|
// Skip child nodes as they were already processed here.
|
|||
|
|
skipChildren = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!skipChildren)
|
|||
|
|
{
|
|||
|
|
foreach (var child in node.Children)
|
|||
|
|
{
|
|||
|
|
RecursiveDump(child, ref offset, level + 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
((int)node.MetaFlags & (int)TypeTreeMetaFlags.AlignBytes) != 0 ||
|
|||
|
|
((int)node.MetaFlags & (int)TypeTreeMetaFlags.AnyChildUsesAlignBytes) != 0
|
|||
|
|
)
|
|||
|
|
{
|
|||
|
|
offset = (offset + 3) & ~(3);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void DumpArray(TypeTreeNode node, ref long offset, int level)
|
|||
|
|
{
|
|||
|
|
// First child contains array size.
|
|||
|
|
var sizeNode = node.Children[0];
|
|||
|
|
// Second child contains array type information.
|
|||
|
|
var dataNode = node.Children[1];
|
|||
|
|
|
|||
|
|
if (sizeNode.Size != 4 || !sizeNode.IsLeaf)
|
|||
|
|
throw new Exception("Invalid array size");
|
|||
|
|
|
|||
|
|
var arraySize = m_Reader.ReadInt32(offset);
|
|||
|
|
offset += 4;
|
|||
|
|
|
|||
|
|
m_StringBuilder.Append(' ', level * 2);
|
|||
|
|
m_StringBuilder.Append("Array");
|
|||
|
|
m_StringBuilder.Append('<');
|
|||
|
|
m_StringBuilder.Append(dataNode.Type);
|
|||
|
|
m_StringBuilder.Append(">[");
|
|||
|
|
m_StringBuilder.Append(arraySize);
|
|||
|
|
m_StringBuilder.Append(']');
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine(m_StringBuilder);
|
|||
|
|
m_StringBuilder.Clear();
|
|||
|
|
|
|||
|
|
if (arraySize > 0)
|
|||
|
|
{
|
|||
|
|
if (dataNode.IsBasicType)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(' ', (level + 1) * 2);
|
|||
|
|
|
|||
|
|
if (arraySize > 256 && m_SkipLargeArrays)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append("<Skipped>");
|
|||
|
|
offset += dataNode.Size * arraySize;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
var array = ReadBasicTypeArray(dataNode, offset, arraySize);
|
|||
|
|
offset += dataNode.Size * arraySize;
|
|||
|
|
|
|||
|
|
m_StringBuilder.Append(array.GetValue(0));
|
|||
|
|
for (int i = 1; i < arraySize; ++i)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(", ");
|
|||
|
|
m_StringBuilder.Append(array.GetValue(i));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine(m_StringBuilder);
|
|||
|
|
m_StringBuilder.Clear();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
++level;
|
|||
|
|
|
|||
|
|
for (int i = 0; i < arraySize; ++i)
|
|||
|
|
{
|
|||
|
|
RecursiveDump(dataNode, ref offset, level, i);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void DumpManagedReferenceRegistry(TypeTreeNode node, ref long offset, int level)
|
|||
|
|
{
|
|||
|
|
if (node.Children.Count < 2)
|
|||
|
|
throw new Exception("Invalid ManagedReferenceRegistry");
|
|||
|
|
|
|||
|
|
// First child is version number.
|
|||
|
|
var version = m_Reader.ReadInt32(offset);
|
|||
|
|
RecursiveDump(node.Children[0], ref offset, level);
|
|||
|
|
|
|||
|
|
TypeTreeNode refTypeNode;
|
|||
|
|
TypeTreeNode refObjData;
|
|||
|
|
|
|||
|
|
if (version == 1)
|
|||
|
|
{
|
|||
|
|
// Second child is the ReferencedObject.
|
|||
|
|
var refObjNode = node.Children[1];
|
|||
|
|
// And its children are the referenced type and data nodes.
|
|||
|
|
refTypeNode = refObjNode.Children[0];
|
|||
|
|
refObjData = refObjNode.Children[1];
|
|||
|
|
|
|||
|
|
int i = 0;
|
|||
|
|
|
|||
|
|
while (DumpManagedReferenceData(refTypeNode, refObjData, ref offset, level, i++))
|
|||
|
|
{ }
|
|||
|
|
}
|
|||
|
|
else if (version == 2)
|
|||
|
|
{
|
|||
|
|
// Second child is the RefIds vector.
|
|||
|
|
var refIdsVectorNode = node.Children[1];
|
|||
|
|
|
|||
|
|
if (refIdsVectorNode.Children.Count < 1 || refIdsVectorNode.Name != "RefIds")
|
|||
|
|
throw new Exception("Invalid ManagedReferenceRegistry RefIds vector");
|
|||
|
|
|
|||
|
|
var refIdsArrayNode = refIdsVectorNode.Children[0];
|
|||
|
|
|
|||
|
|
if (refIdsArrayNode.Children.Count != 2 || !refIdsArrayNode.IsArray)
|
|||
|
|
throw new Exception("Invalid ManagedReferenceRegistry RefIds array");
|
|||
|
|
|
|||
|
|
// First child is the array size.
|
|||
|
|
int arraySize = m_Reader.ReadInt32(offset);
|
|||
|
|
offset += 4;
|
|||
|
|
|
|||
|
|
// Second child is the ReferencedObject.
|
|||
|
|
var refObjNode = refIdsArrayNode.Children[1];
|
|||
|
|
|
|||
|
|
for (int i = 0; i < arraySize; ++i)
|
|||
|
|
{
|
|||
|
|
// First child is the rid.
|
|||
|
|
long rid = m_Reader.ReadInt64(offset);
|
|||
|
|
offset += 8;
|
|||
|
|
|
|||
|
|
// And the next children are the referenced type and data nodes.
|
|||
|
|
refTypeNode = refObjNode.Children[1];
|
|||
|
|
refObjData = refObjNode.Children[2];
|
|||
|
|
DumpManagedReferenceData(refTypeNode, refObjData, ref offset, level, rid);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
throw new Exception($"Unsupported ManagedReferenceRegistry version {version}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool DumpManagedReferenceData(TypeTreeNode refTypeNode, TypeTreeNode referencedTypeDataNode, ref long offset, int level, long id)
|
|||
|
|
{
|
|||
|
|
if (refTypeNode.Children.Count < 3)
|
|||
|
|
throw new Exception("Invalid ReferencedManagedType");
|
|||
|
|
|
|||
|
|
m_StringBuilder.Append(' ', level * 2);
|
|||
|
|
m_StringBuilder.Append($"rid(");
|
|||
|
|
m_StringBuilder.Append(id);
|
|||
|
|
m_StringBuilder.Append(") ReferencedObject");
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine(m_StringBuilder);
|
|||
|
|
m_StringBuilder.Clear();
|
|||
|
|
|
|||
|
|
++level;
|
|||
|
|
|
|||
|
|
var refTypeOffset = offset;
|
|||
|
|
var stringSize = m_Reader.ReadInt32(offset);
|
|||
|
|
var className = m_Reader.ReadString(offset + 4, stringSize);
|
|||
|
|
offset += stringSize + 4;
|
|||
|
|
offset = (offset + 3) & ~(3);
|
|||
|
|
|
|||
|
|
stringSize = m_Reader.ReadInt32(offset);
|
|||
|
|
var namespaceName = m_Reader.ReadString(offset + 4, stringSize);
|
|||
|
|
offset += stringSize + 4;
|
|||
|
|
offset = (offset + 3) & ~(3);
|
|||
|
|
|
|||
|
|
stringSize = m_Reader.ReadInt32(offset);
|
|||
|
|
var assemblyName = m_Reader.ReadString(offset + 4, stringSize);
|
|||
|
|
offset += stringSize + 4;
|
|||
|
|
offset = (offset + 3) & ~(3);
|
|||
|
|
|
|||
|
|
if (className == "Terminus" && namespaceName == "UnityEngine.DMAT" && assemblyName == "FAKE_ASM")
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
// Not the most efficient way, but it simplifies the code.
|
|||
|
|
RecursiveDump(refTypeNode, ref refTypeOffset, level);
|
|||
|
|
|
|||
|
|
m_StringBuilder.Append(' ', level * 2);
|
|||
|
|
m_StringBuilder.Append(referencedTypeDataNode.Name);
|
|||
|
|
m_StringBuilder.Append(' ');
|
|||
|
|
m_StringBuilder.Append(referencedTypeDataNode.Type);
|
|||
|
|
m_StringBuilder.Append(' ');
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine(m_StringBuilder);
|
|||
|
|
m_StringBuilder.Clear();
|
|||
|
|
|
|||
|
|
if (id == -1 || id == -2)
|
|||
|
|
{
|
|||
|
|
m_StringBuilder.Append(' ', level * 2);
|
|||
|
|
m_StringBuilder.Append(id == -1 ? " unknown" : " null");
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine(m_StringBuilder);
|
|||
|
|
m_StringBuilder.Clear();
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var refTypeRoot = m_SerializedFile.GetRefTypeTypeTreeRoot(className, namespaceName, assemblyName);
|
|||
|
|
|
|||
|
|
// Dump the ReferencedObject using its own TypeTree, but skip the root.
|
|||
|
|
foreach (var child in refTypeRoot.Children)
|
|||
|
|
{
|
|||
|
|
RecursiveDump(child, ref offset, level + 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void OutputSerializedFile(string path)
|
|||
|
|
{
|
|||
|
|
using (m_Reader = new UnityFileReader(path, 64 * 1024 * 1024))
|
|||
|
|
using (m_SerializedFile = UnityFileSystem.OpenSerializedFile(path))
|
|||
|
|
{
|
|||
|
|
var i = 1;
|
|||
|
|
|
|||
|
|
m_Writer.WriteLine("External References");
|
|||
|
|
foreach (var extRef in m_SerializedFile.ExternalReferences)
|
|||
|
|
{
|
|||
|
|
m_Writer.WriteLine($"path({i}): \"{extRef.Path}\" GUID: {extRef.Guid} Type: {(int)extRef.Type}");
|
|||
|
|
++i;
|
|||
|
|
}
|
|||
|
|
m_Writer.WriteLine();
|
|||
|
|
|
|||
|
|
foreach (var obj in m_SerializedFile.Objects)
|
|||
|
|
{
|
|||
|
|
var root = m_SerializedFile.GetTypeTreeRoot(obj.Id);
|
|||
|
|
var offset = obj.Offset;
|
|||
|
|
|
|||
|
|
m_Writer.Write($"ID: {obj.Id} (ClassID: {obj.TypeId}) ");
|
|||
|
|
RecursiveDump(root, ref offset, 0);
|
|||
|
|
m_Writer.WriteLine();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string ReadValue(TypeTreeNode node, long offset)
|
|||
|
|
{
|
|||
|
|
switch (Type.GetTypeCode(node.CSharpType))
|
|||
|
|
{
|
|||
|
|
case TypeCode.Int32:
|
|||
|
|
return m_Reader.ReadInt32(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.UInt32:
|
|||
|
|
return m_Reader.ReadUInt32(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.Single:
|
|||
|
|
return m_Reader.ReadFloat(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.Double:
|
|||
|
|
return m_Reader.ReadDouble(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.Int16:
|
|||
|
|
return m_Reader.ReadInt16(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.UInt16:
|
|||
|
|
return m_Reader.ReadUInt16(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.Int64:
|
|||
|
|
return m_Reader.ReadInt64(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.UInt64:
|
|||
|
|
return m_Reader.ReadUInt64(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.SByte:
|
|||
|
|
return m_Reader.ReadUInt8(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.Byte:
|
|||
|
|
case TypeCode.Char:
|
|||
|
|
return m_Reader.ReadUInt8(offset).ToString();
|
|||
|
|
|
|||
|
|
case TypeCode.Boolean:
|
|||
|
|
return (m_Reader.ReadUInt8(offset) != 0).ToString();
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
throw new Exception($"Can't get value of {node.Type} type");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Array ReadBasicTypeArray(TypeTreeNode node, long offset, int arraySize)
|
|||
|
|
{
|
|||
|
|
// Special case for boolean arrays.
|
|||
|
|
if (node.CSharpType == typeof(bool))
|
|||
|
|
{
|
|||
|
|
var tmpArray = new byte[arraySize];
|
|||
|
|
var boolArray = new bool[arraySize];
|
|||
|
|
|
|||
|
|
m_Reader.ReadArray(offset, arraySize * node.Size, tmpArray);
|
|||
|
|
|
|||
|
|
for (int i = 0; i < arraySize; ++i)
|
|||
|
|
{
|
|||
|
|
boolArray[i] = tmpArray[i] != 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return boolArray;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
var array = Array.CreateInstance(node.CSharpType, arraySize);
|
|||
|
|
|
|||
|
|
m_Reader.ReadArray(offset, arraySize * node.Size, array);
|
|||
|
|
|
|||
|
|
return array;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|