AssetStudio/AssetStudio/AssetsManager.cs
VaDiM 19c6c5fe73 Minor UI improvments & bugfixes
- improved "Copy text" option in right click menu, to display what exactly to copy
- added "Dump selected assets" option to right click menu
- added 'selected assets count' info to status strip when you select assets
- added 'exported count / total export count` info to status strip during export
- "Show error message" option on the "Debug" tab renamed to "Show all error messages" and is now disabled by default
- "fixed" an issue with getting stuck during the "Building tree structure" step
- fixed a bug with listSearch that could make it not work in some conditions
- fixed a rare bug for resource files with the same name, that caused their data to be overwritten and become incorrect
2021-11-24 14:55:38 +02:00

442 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using static AssetStudio.ImportHelper;
namespace AssetStudio
{
public class AssetsManager
{
public string SpecifyUnityVersion;
public List<SerializedFile> assetsFileList = new List<SerializedFile>();
internal Dictionary<string, int> assetsFileIndexCache = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
internal Dictionary<string, BinaryReader> resourceFileReaders = new Dictionary<string, BinaryReader>(StringComparer.OrdinalIgnoreCase);
private List<string> importFiles = new List<string>();
private HashSet<string> importFilesHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> assetsFileListHash = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public void LoadFiles(params string[] files)
{
var path = Path.GetDirectoryName(files[0]);
MergeSplitAssets(path);
var toReadFile = ProcessingSplitFiles(files.ToList());
Load(toReadFile);
}
public void LoadFolder(string path)
{
MergeSplitAssets(path, true);
var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories).ToList();
var toReadFile = ProcessingSplitFiles(files);
Load(toReadFile);
}
private void Load(string[] files)
{
foreach (var file in files)
{
importFiles.Add(file);
importFilesHash.Add(Path.GetFileName(file));
}
Progress.Reset();
//use a for loop because list size can change
for (var i = 0; i < importFiles.Count; i++)
{
LoadFile(importFiles[i]);
Progress.Report(i + 1, importFiles.Count);
}
importFiles.Clear();
importFilesHash.Clear();
assetsFileListHash.Clear();
ReadAssets();
ProcessAssets();
}
private void LoadFile(string fullName)
{
var reader = new FileReader(fullName);
LoadFile(reader);
}
private void LoadFile(FileReader reader)
{
switch (reader.FileType)
{
case FileType.AssetsFile:
LoadAssetsFile(reader);
break;
case FileType.BundleFile:
LoadBundleFile(reader);
break;
case FileType.WebFile:
LoadWebFile(reader);
break;
case FileType.GZipFile:
LoadFile(DecompressGZip(reader));
break;
case FileType.BrotliFile:
LoadFile(DecompressBrotli(reader));
break;
}
}
private void LoadAssetsFile(FileReader reader)
{
if (!assetsFileListHash.Contains(reader.FileName))
{
Logger.Info($"Loading {reader.FileName}");
try
{
var assetsFile = new SerializedFile(reader, this);
CheckStrippedVersion(assetsFile);
assetsFileList.Add(assetsFile);
assetsFileListHash.Add(assetsFile.fileName);
foreach (var sharedFile in assetsFile.m_Externals)
{
var sharedFileName = sharedFile.fileName;
if (!importFilesHash.Contains(sharedFileName))
{
var sharedFilePath = Path.Combine(Path.GetDirectoryName(reader.FullPath), sharedFileName);
if (!File.Exists(sharedFilePath))
{
var findFiles = Directory.GetFiles(Path.GetDirectoryName(reader.FullPath), sharedFileName, SearchOption.AllDirectories);
if (findFiles.Length > 0)
{
sharedFilePath = findFiles[0];
}
}
if (File.Exists(sharedFilePath))
{
importFiles.Add(sharedFilePath);
importFilesHash.Add(sharedFileName);
}
}
}
}
catch (Exception e)
{
Logger.Error($"Error while reading assets file {reader.FileName}", e);
reader.Dispose();
}
}
else
{
reader.Dispose();
}
}
private void LoadAssetsFromMemory(FileReader reader, string originalPath, string unityVersion = null)
{
if (!assetsFileListHash.Contains(reader.FileName))
{
try
{
var assetsFile = new SerializedFile(reader, this);
assetsFile.originalPath = originalPath;
if (!string.IsNullOrEmpty(unityVersion) && assetsFile.header.m_Version < SerializedFileFormatVersion.kUnknown_7)
{
assetsFile.SetVersion(unityVersion);
}
CheckStrippedVersion(assetsFile);
assetsFileList.Add(assetsFile);
assetsFileListHash.Add(assetsFile.fileName);
}
catch (Exception e)
{
Logger.Error($"Error while reading assets file {reader.FileName} from {Path.GetFileName(originalPath)}", e);
resourceFileReaders.Add(reader.FileName, reader);
}
}
}
private void LoadBundleFile(FileReader reader, string originalPath = null)
{
Logger.Info("Loading " + reader.FileName);
try
{
var bundleFile = new BundleFile(reader);
foreach (var file in bundleFile.fileList)
{
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
var subReader = new FileReader(dummyPath, file.stream);
if (subReader.FileType == FileType.AssetsFile)
{
LoadAssetsFromMemory(subReader, originalPath ?? reader.FullPath, bundleFile.m_Header.unityRevision);
}
else if (!resourceFileReaders.ContainsKey(file.fileName))
{
resourceFileReaders.Add(file.fileName, subReader);
}
}
}
catch (Exception e)
{
var str = $"Error while reading bundle file {reader.FileName}";
if (originalPath != null)
{
str += $" from {Path.GetFileName(originalPath)}";
}
Logger.Error(str, e);
}
finally
{
reader.Dispose();
}
}
private void LoadWebFile(FileReader reader)
{
Logger.Info("Loading " + reader.FileName);
try
{
var webFile = new WebFile(reader);
foreach (var file in webFile.fileList)
{
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
var subReader = new FileReader(dummyPath, file.stream);
switch (subReader.FileType)
{
case FileType.AssetsFile:
LoadAssetsFromMemory(subReader, reader.FullPath);
break;
case FileType.BundleFile:
LoadBundleFile(subReader, reader.FullPath);
break;
case FileType.WebFile:
LoadWebFile(subReader);
break;
case FileType.ResourceFile:
resourceFileReaders[file.fileName] = subReader; //TODO
break;
}
}
}
catch (Exception e)
{
Logger.Error($"Error while reading web file {reader.FileName}", e);
}
finally
{
reader.Dispose();
}
}
public void CheckStrippedVersion(SerializedFile assetsFile)
{
if (assetsFile.IsVersionStripped && string.IsNullOrEmpty(SpecifyUnityVersion))
{
throw new Exception("The Unity version has been stripped, please set the version in the options");
}
if (!string.IsNullOrEmpty(SpecifyUnityVersion))
{
assetsFile.SetVersion(SpecifyUnityVersion);
}
}
public void Clear()
{
foreach (var assetsFile in assetsFileList)
{
assetsFile.Objects.Clear();
assetsFile.reader.Close();
}
assetsFileList.Clear();
foreach (var resourceFileReader in resourceFileReaders)
{
resourceFileReader.Value.Close();
}
resourceFileReaders.Clear();
assetsFileIndexCache.Clear();
}
private void ReadAssets()
{
Logger.Info("Read assets...");
var progressCount = assetsFileList.Sum(x => x.m_Objects.Count);
int i = 0;
Progress.Reset();
foreach (var assetsFile in assetsFileList)
{
foreach (var objectInfo in assetsFile.m_Objects)
{
var objectReader = new ObjectReader(assetsFile.reader, assetsFile, objectInfo);
try
{
Object obj;
switch (objectReader.type)
{
case ClassIDType.Animation:
obj = new Animation(objectReader);
break;
case ClassIDType.AnimationClip:
obj = new AnimationClip(objectReader);
break;
case ClassIDType.Animator:
obj = new Animator(objectReader);
break;
case ClassIDType.AnimatorController:
obj = new AnimatorController(objectReader);
break;
case ClassIDType.AnimatorOverrideController:
obj = new AnimatorOverrideController(objectReader);
break;
case ClassIDType.AssetBundle:
obj = new AssetBundle(objectReader);
break;
case ClassIDType.AudioClip:
obj = new AudioClip(objectReader);
break;
case ClassIDType.Avatar:
obj = new Avatar(objectReader);
break;
case ClassIDType.Font:
obj = new Font(objectReader);
break;
case ClassIDType.GameObject:
obj = new GameObject(objectReader);
break;
case ClassIDType.Material:
obj = new Material(objectReader);
break;
case ClassIDType.Mesh:
obj = new Mesh(objectReader);
break;
case ClassIDType.MeshFilter:
obj = new MeshFilter(objectReader);
break;
case ClassIDType.MeshRenderer:
obj = new MeshRenderer(objectReader);
break;
case ClassIDType.MonoBehaviour:
obj = new MonoBehaviour(objectReader);
break;
case ClassIDType.MonoScript:
obj = new MonoScript(objectReader);
break;
case ClassIDType.MovieTexture:
obj = new MovieTexture(objectReader);
break;
case ClassIDType.PlayerSettings:
obj = new PlayerSettings(objectReader);
break;
case ClassIDType.RectTransform:
obj = new RectTransform(objectReader);
break;
case ClassIDType.Shader:
obj = new Shader(objectReader);
break;
case ClassIDType.SkinnedMeshRenderer:
obj = new SkinnedMeshRenderer(objectReader);
break;
case ClassIDType.Sprite:
obj = new Sprite(objectReader);
break;
case ClassIDType.SpriteAtlas:
obj = new SpriteAtlas(objectReader);
break;
case ClassIDType.TextAsset:
obj = new TextAsset(objectReader);
break;
case ClassIDType.Texture2D:
obj = new Texture2D(objectReader);
break;
case ClassIDType.Transform:
obj = new Transform(objectReader);
break;
case ClassIDType.VideoClip:
obj = new VideoClip(objectReader);
break;
case ClassIDType.ResourceManager:
obj = new ResourceManager(objectReader);
break;
default:
obj = new Object(objectReader);
break;
}
assetsFile.AddObject(obj);
}
catch (Exception e)
{
var sb = new StringBuilder();
sb.AppendLine("Unable to load object")
.AppendLine($"Assets {assetsFile.fileName}")
.AppendLine($"Type {objectReader.type}")
.AppendLine($"PathID {objectInfo.m_PathID}")
.Append(e);
Logger.Error(sb.ToString());
}
Progress.Report(++i, progressCount);
}
}
}
private void ProcessAssets()
{
Logger.Info("Process Assets...");
foreach (var assetsFile in assetsFileList)
{
foreach (var obj in assetsFile.Objects)
{
if (obj is GameObject m_GameObject)
{
foreach (var pptr in m_GameObject.m_Components)
{
if (pptr.TryGet(out var m_Component))
{
switch (m_Component)
{
case Transform m_Transform:
m_GameObject.m_Transform = m_Transform;
break;
case MeshRenderer m_MeshRenderer:
m_GameObject.m_MeshRenderer = m_MeshRenderer;
break;
case MeshFilter m_MeshFilter:
m_GameObject.m_MeshFilter = m_MeshFilter;
break;
case SkinnedMeshRenderer m_SkinnedMeshRenderer:
m_GameObject.m_SkinnedMeshRenderer = m_SkinnedMeshRenderer;
break;
case Animator m_Animator:
m_GameObject.m_Animator = m_Animator;
break;
case Animation m_Animation:
m_GameObject.m_Animation = m_Animation;
break;
}
}
}
}
else if (obj is SpriteAtlas m_SpriteAtlas)
{
foreach (var m_PackedSprite in m_SpriteAtlas.m_PackedSprites)
{
if (m_PackedSprite.TryGet(out var m_Sprite))
{
if (m_Sprite.m_SpriteAtlas.IsNull)
{
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
}
}
}
}
}
}
}
}