mirror of
https://github.com/Dongyifengs/AssetStudio-Genshin-MoYi.git
synced 2025-04-21 20:19:19 +08:00
587 lines
24 KiB
C#
587 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using static AssetStudio.ImportHelper;
|
|
|
|
namespace AssetStudio
|
|
{
|
|
public class AssetsManager
|
|
{
|
|
public Game Game;
|
|
public bool ResolveDependancies;
|
|
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)
|
|
{
|
|
if (ResolveDependancies)
|
|
files = CABManager.ProcessDependencies(files);
|
|
Load(files);
|
|
}
|
|
|
|
public void LoadFolder(string path)
|
|
{
|
|
var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories).ToArray();
|
|
Load(files);
|
|
}
|
|
|
|
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();
|
|
CABManager.offsets.Clear();
|
|
|
|
ReadAssets();
|
|
ProcessAssets();
|
|
}
|
|
|
|
private void LoadFile(string fullName)
|
|
{
|
|
var reader = new FileReader(fullName, Game);
|
|
LoadFile(reader);
|
|
}
|
|
|
|
private void LoadFile(FileReader reader)
|
|
{
|
|
switch (reader.FileType)
|
|
{
|
|
case FileType.AssetsFile:
|
|
LoadAssetsFile(reader);
|
|
break;
|
|
case FileType.BundleFile:
|
|
LoadBundleFile(reader);
|
|
break;
|
|
case FileType.GameFile:
|
|
LoadGameFile(reader);
|
|
break;
|
|
case FileType.WebFile:
|
|
LoadWebFile(reader);
|
|
break;
|
|
case FileType.GZipFile:
|
|
LoadFile(DecompressGZip(reader));
|
|
break;
|
|
case FileType.BrotliFile:
|
|
LoadFile(DecompressBrotli(reader));
|
|
break;
|
|
case FileType.ZipFile:
|
|
LoadZipFile(reader);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void LoadAssetsFile(FileReader reader)
|
|
{
|
|
if (!assetsFileListHash.Contains(reader.FileName))
|
|
{
|
|
Logger.Info($"Loading {reader.FileName}");
|
|
try
|
|
{
|
|
var assetsFile = new SerializedFile(reader, this, reader.FullPath);
|
|
CheckStrippedVersion(assetsFile);
|
|
assetsFileList.Add(assetsFile);
|
|
assetsFileListHash.Add(assetsFile.fileName);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while reading assets file {reader.FullPath}", e);
|
|
reader.Dispose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger.Info($"Skipping {reader.FullPath}");
|
|
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.Unknown_7)
|
|
{
|
|
assetsFile.SetVersion(unityVersion);
|
|
}
|
|
CheckStrippedVersion(assetsFile);
|
|
assetsFileList.Add(assetsFile);
|
|
assetsFileListHash.Add(assetsFile.fileName);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while reading assets file {reader.FullPath} from {Path.GetFileName(originalPath)}", e);
|
|
resourceFileReaders.Add(reader.FileName, reader);
|
|
}
|
|
}
|
|
else
|
|
Logger.Info($"Skipping {originalPath} ({reader.FileName})");
|
|
}
|
|
|
|
private void LoadBundleFile(FileReader reader, string originalPath = null)
|
|
{
|
|
Logger.Info("Loading " + reader.FullPath);
|
|
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
|
|
{
|
|
resourceFileReaders[file.fileName] = subReader; //TODO
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var str = $"Error while reading bundle file {reader.FullPath}";
|
|
if (originalPath != null)
|
|
{
|
|
str += $" from {Path.GetFileName(originalPath)}";
|
|
}
|
|
Logger.Error(str, e);
|
|
}
|
|
finally
|
|
{
|
|
reader.Dispose();
|
|
}
|
|
}
|
|
|
|
private void LoadWebFile(FileReader reader)
|
|
{
|
|
Logger.Info("Loading " + reader.FullPath);
|
|
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.FullPath}", e);
|
|
}
|
|
finally
|
|
{
|
|
reader.Dispose();
|
|
}
|
|
}
|
|
|
|
private void LoadZipFile(FileReader reader)
|
|
{
|
|
Logger.Info("Loading " + reader.FileName);
|
|
try
|
|
{
|
|
using (ZipArchive archive = new ZipArchive(reader.BaseStream, ZipArchiveMode.Read))
|
|
{
|
|
List<string> splitFiles = new List<string>();
|
|
// register all files before parsing the assets so that the external references can be found
|
|
// and find split files
|
|
foreach (ZipArchiveEntry entry in archive.Entries)
|
|
{
|
|
if (entry.Name.Contains(".split"))
|
|
{
|
|
string baseName = Path.GetFileNameWithoutExtension(entry.Name);
|
|
string basePath = Path.Combine(Path.GetDirectoryName(entry.FullName), baseName);
|
|
if (!splitFiles.Contains(basePath))
|
|
{
|
|
splitFiles.Add(basePath);
|
|
importFilesHash.Add(baseName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
importFilesHash.Add(entry.Name);
|
|
}
|
|
}
|
|
|
|
// merge split files and load the result
|
|
foreach (string basePath in splitFiles)
|
|
{
|
|
try
|
|
{
|
|
Stream splitStream = new MemoryStream();
|
|
int i = 0;
|
|
while (true)
|
|
{
|
|
string path = $"{basePath}.split{i++}";
|
|
ZipArchiveEntry entry = archive.GetEntry(path);
|
|
if (entry == null)
|
|
break;
|
|
using (Stream entryStream = entry.Open())
|
|
{
|
|
entryStream.CopyTo(splitStream);
|
|
}
|
|
}
|
|
splitStream.Seek(0, SeekOrigin.Begin);
|
|
FileReader entryReader = new FileReader(basePath, splitStream);
|
|
LoadFile(entryReader);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while reading zip split file {basePath}", e);
|
|
}
|
|
}
|
|
|
|
// load all entries
|
|
foreach (ZipArchiveEntry entry in archive.Entries)
|
|
{
|
|
try
|
|
{
|
|
string dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), reader.FileName, entry.FullName);
|
|
// create a new stream
|
|
// - to store the deflated stream in
|
|
// - to keep the data for later extraction
|
|
Stream streamReader = new MemoryStream();
|
|
using (Stream entryStream = entry.Open())
|
|
{
|
|
entryStream.CopyTo(streamReader);
|
|
}
|
|
streamReader.Position = 0;
|
|
|
|
FileReader entryReader = new FileReader(dummyPath, streamReader);
|
|
LoadFile(entryReader);
|
|
if (entryReader.FileType == FileType.ResourceFile)
|
|
{
|
|
entryReader.Position = 0;
|
|
if (!resourceFileReaders.ContainsKey(entry.Name))
|
|
{
|
|
resourceFileReaders.Add(entry.Name, entryReader);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while reading zip entry {entry.FullName}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while reading zip file {reader.FileName}", e);
|
|
}
|
|
finally
|
|
{
|
|
reader.Dispose();
|
|
}
|
|
}
|
|
|
|
private void LoadGameFile(FileReader reader)
|
|
{
|
|
Logger.Info("Loading " + reader.FileName);
|
|
try
|
|
{
|
|
reader.BundlePos = CABManager.offsets.TryGetValue(reader.FullPath, out var list) ? list.ToArray() : Array.Empty<long>();
|
|
var gameFile = new GameFile(reader);
|
|
foreach (var bundle in gameFile.Bundles)
|
|
{
|
|
foreach (var file in bundle.Value)
|
|
{
|
|
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
|
|
var cabReader = new FileReader(dummyPath, file.stream, Game);
|
|
if (cabReader.FileType == FileType.AssetsFile)
|
|
{
|
|
var assetsFile = new SerializedFile(cabReader, this, reader.FullPath);
|
|
CheckStrippedVersion(assetsFile);
|
|
assetsFileList.Add(assetsFile);
|
|
assetsFileListHash.Add(assetsFile.fileName);
|
|
}
|
|
else
|
|
{
|
|
resourceFileReaders[file.fileName] = cabReader; //TODO
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while reading 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.IndexObject:
|
|
obj = new IndexObject(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:
|
|
if (!Renderer.Parsable) continue;
|
|
obj = new MeshRenderer(objectReader);
|
|
break;
|
|
case ClassIDType.MiHoYoBinData:
|
|
obj = new MiHoYoBinData(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:
|
|
if (!Shader.Parsable) continue;
|
|
obj = new Shader(objectReader);
|
|
break;
|
|
case ClassIDType.SkinnedMeshRenderer:
|
|
if (!Renderer.Parsable) continue;
|
|
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($"Path {assetsFile.originalPath}")
|
|
.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)
|
|
{
|
|
if (m_SpriteAtlas.m_RenderDataMap.Count > 0)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlaOld);
|
|
if (m_SpriteAtlaOld.m_IsVariant)
|
|
{
|
|
m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|