From 0bd3fa6db216697ed8e01243efa2f326c9eebef3 Mon Sep 17 00:00:00 2001
From: Razmoth <32140579+Razmoth@users.noreply.github.com>
Date: Mon, 21 Aug 2023 21:45:57 +0400
Subject: [PATCH] v0.90.10
---
.../AssetStudio.PInvoke.csproj | 6 +-
AssetStudio/AssetStudio.csproj | 8 +-
AssetStudio/AssetsHelper.cs | 94 ++++--
AssetStudio/AssetsManager.cs | 315 ++++++------------
AssetStudio/BundleFile.cs | 75 ++++-
AssetStudio/Classes/AnimationClip.cs | 2 +
AssetStudio/Classes/Object.cs | 3 +
AssetStudio/Classes/Texture2D.cs | 2 +-
AssetStudio/Crypto/BlkUtils.cs | 51 +++
AssetStudio/Crypto/Mr0kUtils.cs | 3 +
AssetStudio/Crypto/NetEaseUtils.cs | 5 +-
AssetStudio/Crypto/OPFPUtils.cs | 10 +
AssetStudio/Crypto/UnityCN.cs | 18 +-
AssetStudio/Extensions/ByteArrayExtensions.cs | 2 +-
AssetStudio/FileIdentifier.cs | 10 +
AssetStudio/FileReader.cs | 33 +-
AssetStudio/IImported.cs | 4 +-
AssetStudio/ILogger.cs | 36 ++
AssetStudio/ImportHelper.cs | 112 +++++--
.../LocalSerializedObjectIdentifier.cs | 8 +
AssetStudio/Logger.cs | 75 ++++-
AssetStudio/Math/Quaternion.cs | 1 +
AssetStudio/Mhy0File.cs | 20 ++
AssetStudio/ObjectInfo.cs | 17 +-
AssetStudio/ObjectReader.cs | 3 +
AssetStudio/OffsetStream.cs | 9 +-
AssetStudio/SerializedFile.cs | 30 +-
AssetStudio/SerializedFileHeader.cs | 11 +
AssetStudio/SerializedType.cs | 11 +
AssetStudio/TypeTreeHelper.cs | 1 +
AssetStudio/TypeTreeNode.cs | 14 +
AssetStudio/WebFile.cs | 13 +
AssetStudioCLI/AssetStudioCLI.csproj | 6 +-
.../AssetStudioFBXWrapper.csproj | 6 +-
.../FbxExporterContext.PInvoke.cs | 5 +-
AssetStudioFBXWrapper/FbxExporterContext.cs | 3 +-
AssetStudioGUI/App.config | 9 +
AssetStudioGUI/AssetStudioGUI.csproj | 8 +-
AssetStudioGUI/AssetStudioGUIForm.Designer.cs | 68 ++--
AssetStudioGUI/AssetStudioGUIForm.cs | 205 ++++++------
AssetStudioGUI/AssetStudioGUIForm.resx | 6 +-
AssetStudioGUI/ExportOptions.Designer.cs | 65 +++-
AssetStudioGUI/ExportOptions.cs | 3 +
AssetStudioGUI/ExportOptions.resx | 15 -
.../Properties/Settings.Designer.cs | 36 ++
AssetStudioGUI/Properties/Settings.settings | 9 +
AssetStudioUtility/AssetStudioUtility.csproj | 8 +-
AssetStudioUtility/ModelConverter.cs | 23 +-
48 files changed, 967 insertions(+), 510 deletions(-)
diff --git a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj
index 907fd05..1dd3779 100644
--- a/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj
+++ b/AssetStudio.PInvoke/AssetStudio.PInvoke.csproj
@@ -2,9 +2,9 @@
net6.0;net7.0
- 0.90.00
- 0.90.00
- 0.90.00
+ 0.90.10
+ 0.90.10
+ 0.90.10
Copyright © Perfare 2020-2022; Copyright © hozuki 2020
embedded
diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj
index c5a850f..7cdc339 100644
--- a/AssetStudio/AssetStudio.csproj
+++ b/AssetStudio/AssetStudio.csproj
@@ -2,15 +2,15 @@
net6.0;net7.0
- 0.90.00
- 0.90.00
- 0.90.00
+ 0.90.10
+ 0.90.10
+ 0.90.10
Copyright © Razmoth 2022; Copyright © Perfare 2018-2022
embedded
-
+
diff --git a/AssetStudio/AssetsHelper.cs b/AssetStudio/AssetsHelper.cs
index d5cfbcd..9e8f33a 100644
--- a/AssetStudio/AssetsHelper.cs
+++ b/AssetStudio/AssetsHelper.cs
@@ -36,7 +36,9 @@ namespace AssetStudio
{
Directory.CreateDirectory(MapName);
var files = Directory.GetFiles(MapName, "*.bin", SearchOption.TopDirectoryOnly);
- return files.Select(Path.GetFileNameWithoutExtension).ToArray();
+ var mapNames = files.Select(Path.GetFileNameWithoutExtension).ToArray();
+ Logger.Verbose($"Found {mapNames.Length} CABMaps under Maps folder");
+ return mapNames;
}
public static void Clear()
@@ -48,34 +50,20 @@ namespace AssetStudio
tokenSource.Dispose();
tokenSource = new CancellationTokenSource();
- GC.WaitForPendingFinalizers();
- GC.Collect();
+ Logger.Verbose("Cleared AssetsHelper successfully !!");
}
- public static void ClearOffsets() => Offsets.Clear();
-
- public static void Remove(string path) => Offsets.Remove(path);
-
- public static bool TryAdd(string name, out string path)
+ public static void ClearOffsets()
{
- if (CABMap.TryGetValue(name, out var entry))
- {
- path = Path.Combine(BaseFolder, entry.Path);
- if (!Offsets.ContainsKey(path))
- {
- Offsets.Add(path, new HashSet());
- }
- Offsets[path].Add(entry.Offset);
- return true;
- }
- path = string.Empty;
- return false;
+ Offsets.Clear();
+ Logger.Verbose("Cleared cached offsets");
}
public static bool TryGet(string path, out long[] offsets)
{
- if (Offsets.TryGetValue(path, out var list))
+ if (Offsets.TryGetValue(path, out var list) && list.Count > 0)
{
+ Logger.Verbose($"Found {list.Count} offsets for path {path}");
offsets = list.ToArray();
return true;
}
@@ -83,6 +71,67 @@ namespace AssetStudio
return false;
}
+ public static void AddCABOffsets(string[] paths, List cabs)
+ {
+ for (int i = 0; i < cabs.Count; i++)
+ {
+ var cab = cabs[i];
+ if (CABMap.TryGetValue(cab, out var entry))
+ {
+ var fullPath = Path.Combine(BaseFolder, entry.Path);
+ Logger.Verbose($"Found {cab} in {fullPath}");
+ if (!paths.Contains(fullPath))
+ {
+ Offsets.TryAdd(fullPath, new HashSet());
+ Offsets[fullPath].Add(entry.Offset);
+ Logger.Verbose($"Added {fullPath} to Offsets, at offset {entry.Offset}");
+ }
+ foreach (var dep in entry.Dependencies)
+ {
+ if (!cabs.Contains(dep))
+ cabs.Add(dep);
+ }
+ }
+ }
+ }
+
+ public static bool FindCAB(string path, out List cabs)
+ {
+ var relativePath = Path.GetRelativePath(BaseFolder, path);
+ cabs = CABMap.AsParallel().Where(x => x.Value.Path.Equals(relativePath, StringComparison.OrdinalIgnoreCase)).Select(x => x.Key).Distinct().ToList();
+ Logger.Verbose($"Found {cabs.Count} that belongs to {relativePath}");
+ return cabs.Count != 0;
+ }
+
+ public static string[] ProcessFiles(string[] files)
+ {
+ foreach (var file in files)
+ {
+ Offsets.TryAdd(file, new HashSet());
+ Logger.Verbose($"Added {file} to Offsets dictionary");
+ if (FindCAB(file, out var cabs))
+ {
+ AddCABOffsets(files, cabs);
+ }
+ }
+ Logger.Verbose($"Finished resolving dependncies, the original {files.Length} files will be loaded entirely, and the {Offsets.Count - files.Length} dependicnes will be loaded from cached offsets only");
+ return Offsets.Keys.ToArray();
+ }
+
+ public static string[] ProcessDependencies(string[] files)
+ {
+ if (CABMap.Count == 0)
+ {
+ Logger.Warning("CABMap is not build, skip resolving dependencies...");
+ }
+ else
+ {
+ Logger.Info("Resolving Dependencies...");
+ files = ProcessFiles(files);
+ }
+ return files;
+ }
+
public static void BuildCABMap(string[] files, string mapName, string baseFolder, Game game)
{
Logger.Info("Building CABMap...");
@@ -220,6 +269,7 @@ namespace AssetStudio
CABMap.Add(cab, entry);
}
}
+ Logger.Verbose($"Initialized CABMap with {CABMap.Count} entries");
Logger.Info($"Loaded {mapName} !!");
}
catch (Exception e)
@@ -342,7 +392,7 @@ namespace AssetStudio
case ClassIDType.Texture2D:
case ClassIDType.VideoClip:
case ClassIDType.AudioClip:
- case ClassIDType.AnimationClip:
+ case ClassIDType.AnimationClip when AnimationClip.Parsable:
asset.Name = objectReader.ReadAlignedString();
break;
default:
diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs
index 1e078df..46c789e 100644
--- a/AssetStudio/AssetsManager.cs
+++ b/AssetStudio/AssetsManager.cs
@@ -56,6 +56,8 @@ namespace AssetStudio
var path = Path.GetDirectoryName(Path.GetFullPath(files[0]));
MergeSplitAssets(path);
var toReadFile = ProcessingSplitFiles(files.ToList());
+ if (ResolveDependencies)
+ toReadFile = AssetsHelper.ProcessDependencies(toReadFile);
Load(toReadFile);
if (Silent)
@@ -89,6 +91,7 @@ namespace AssetStudio
{
foreach (var file in files)
{
+ Logger.Verbose($"caching {file} path and name to filter out duplicates");
importFiles.Add(file);
importFilesHash.Add(Path.GetFileName(file));
}
@@ -171,6 +174,7 @@ namespace AssetStudio
foreach (var sharedFile in assetsFile.m_Externals)
{
+ Logger.Verbose($"{assetsFile.fileName} needs external file {sharedFile.fileName}, attempting to look it up...");
var sharedFileName = sharedFile.fileName;
if (!importFilesHash.Contains(sharedFileName))
@@ -183,6 +187,7 @@ namespace AssetStudio
var findFiles = Directory.GetFiles(Path.GetDirectoryName(reader.FullPath), sharedFileName, SearchOption.AllDirectories);
if (findFiles.Length > 0)
{
+ Logger.Verbose($"Found {findFiles.Length} matching files, picking first file {findFiles[0]} !!");
sharedFilePath = findFiles[0];
}
}
@@ -193,6 +198,7 @@ namespace AssetStudio
}
else
{
+ Logger.Verbose("Nothing was found, caching into non existant files to avoid repeated searching !!");
noexistFiles.Add(sharedFilePath);
}
}
@@ -214,6 +220,7 @@ namespace AssetStudio
private void LoadAssetsFromMemory(FileReader reader, string originalPath, string unityVersion = null, long originalOffset = 0)
{
+ Logger.Verbose($"Loading asset file {reader.FileName} with version {unityVersion} from {originalPath} at offset 0x{originalOffset:X8}");
if (!assetsFileListHash.Contains(reader.FileName))
{
try
@@ -228,38 +235,6 @@ namespace AssetStudio
CheckStrippedVersion(assetsFile);
assetsFileList.Add(assetsFile);
assetsFileListHash.Add(assetsFile.fileName);
-
- if (ResolveDependencies)
- {
- foreach (var sharedFile in assetsFile.m_Externals)
- {
- var sharedFileName = sharedFile.fileName;
-
- if (!importFilesHash.Contains(sharedFileName))
- {
- var sharedFilePath = Path.Combine(Path.GetDirectoryName(originalPath), sharedFileName);
- if (!noexistFiles.Contains(sharedFilePath))
- {
- if (AssetsHelper.TryAdd(sharedFileName, out var path))
- {
- sharedFilePath = path;
- }
- if (File.Exists(sharedFilePath))
- {
- if (!importFiles.Contains(sharedFilePath))
- {
- importFiles.Add(sharedFilePath);
- }
- importFilesHash.Add(sharedFileName);
- }
- else
- {
- noexistFiles.Add(sharedFilePath);
- }
- }
- }
- }
- }
}
catch (Exception e)
{
@@ -290,6 +265,7 @@ namespace AssetStudio
}
else
{
+ Logger.Verbose("Caching resource stream");
resourceFileReaders[file.fileName] = subReader; //TODO
}
}
@@ -335,6 +311,7 @@ namespace AssetStudio
LoadWebFile(subReader);
break;
case FileType.ResourceFile:
+ Logger.Verbose("Caching resource stream");
resourceFileReaders[file.fileName] = subReader; //TODO
break;
}
@@ -358,8 +335,7 @@ namespace AssetStudio
using (ZipArchive archive = new ZipArchive(reader.BaseStream, ZipArchiveMode.Read))
{
List splitFiles = new List();
- // register all files before parsing the assets so that the external references can be found
- // and find split files
+ Logger.Verbose("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"))
@@ -378,7 +354,7 @@ namespace AssetStudio
}
}
- // merge split files and load the result
+ Logger.Verbose("Merge split files and load the result");
foreach (string basePath in splitFiles)
{
try
@@ -406,15 +382,14 @@ namespace AssetStudio
}
}
- // load all entries
+ Logger.Verbose("Load all entries");
+ Logger.Verbose($"Found {archive.Entries.Count} 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
+ Logger.Verbose("Create a new stream to store the deflated stream in and keep the data for later extraction");
Stream streamReader = new MemoryStream();
using (Stream entryStream = entry.Open())
{
@@ -429,6 +404,7 @@ namespace AssetStudio
entryReader.Position = 0;
if (!resourceFileReaders.ContainsKey(entry.Name))
{
+ Logger.Verbose("Caching resource file");
resourceFileReaders.Add(entry.Name, entryReader);
}
}
@@ -459,27 +435,14 @@ namespace AssetStudio
{
foreach (var offset in offsets)
{
- var name = offset.ToString("X8");
- Logger.Info($"Loading Block {name}");
-
- stream.Offset = offset;
- var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
- var subReader = new FileReader(dummyPath, stream, true);
- LoadBundleFile(subReader, reader.FullPath, offset, false);
+ LoadBlockSubFile(reader.FullPath, stream, offset);
}
- AssetsHelper.Remove(reader.FullPath);
}
else
{
do
{
- var name = stream.AbsolutePosition.ToString("X8");
- Logger.Info($"Loading Block {name}");
-
- stream.Offset = stream.AbsolutePosition;
- var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
- var subReader = new FileReader(dummyPath, stream, true);
- LoadBundleFile(subReader, reader.FullPath, stream.AbsolutePosition, false);
+ LoadBlockSubFile(reader.FullPath, stream, stream.AbsolutePosition);
} while (stream.Remaining > 0);
}
}
@@ -492,55 +455,38 @@ namespace AssetStudio
reader.Dispose();
}
}
+ private void LoadBlockSubFile(string path, OffsetStream stream, long offset)
+ {
+ var name = offset.ToString("X8");
+ Logger.Info($"Loading Block {name}");
+
+ stream.Offset = offset;
+ var dummyPath = Path.Combine(Path.GetDirectoryName(path), name);
+ var subReader = new FileReader(dummyPath, stream, true);
+ LoadBundleFile(subReader, path, offset, false);
+ }
private void LoadBlkFile(FileReader reader)
{
Logger.Info("Loading " + reader.FullPath);
try
{
using var stream = BlkUtils.Decrypt(reader, (Blk)Game);
- if (AssetsHelper.TryGet(reader.FullPath, out var offsets))
+ foreach (var offset in stream.GetOffsets(reader.FullPath))
{
- foreach (var offset in offsets)
- {
- var name = offset.ToString("X8");
- Logger.Info($"Loading Block {name}");
+ var name = offset.ToString("X8");
+ Logger.Info($"Loading Block {name}");
- stream.Offset = offset;
- var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
- var subReader = new FileReader(dummyPath, stream, true);
- switch (subReader.FileType)
- {
- case FileType.BundleFile:
- LoadBundleFile(subReader, reader.FullPath, offset, false);
- break;
- case FileType.Mhy0File:
- LoadMhy0File(subReader, reader.FullPath, offset, false);
- break;
- }
+ var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
+ var subReader = new FileReader(dummyPath, stream, true);
+ switch (subReader.FileType)
+ {
+ case FileType.BundleFile:
+ LoadBundleFile(subReader, reader.FullPath, offset, false);
+ break;
+ case FileType.Mhy0File:
+ LoadMhy0File(subReader, reader.FullPath, offset, false);
+ break;
}
- AssetsHelper.Remove(reader.FullPath);
- }
- else
- {
- do
- {
- var name = stream.AbsolutePosition.ToString("X8");
- Logger.Info($"Loading Block {name}");
-
- var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name);
- var subReader = new FileReader(dummyPath, stream, true);
- switch (subReader.FileType)
- {
- case FileType.BundleFile:
- LoadBundleFile(subReader, reader.FullPath, stream.AbsolutePosition, false);
- break;
- case FileType.Mhy0File:
- LoadMhy0File(subReader, reader.FullPath, stream.AbsolutePosition, false);
- break;
- }
-
- stream.Offset = stream.AbsolutePosition;
- } while (stream.Remaining > 0);
}
}
catch (InvalidCastException)
@@ -555,7 +501,7 @@ namespace AssetStudio
{
reader.Dispose();
}
- }
+ }
private void LoadMhy0File(FileReader reader, string originalPath = null, long originalOffset = 0, bool log = true)
{
if (log)
@@ -565,6 +511,7 @@ namespace AssetStudio
try
{
var mhy0File = new Mhy0File(reader, reader.FullPath, (Mhy0)Game);
+ Logger.Verbose($"mhy0 total size: {mhy0File.TotalSize:X8}");
foreach (var file in mhy0File.fileList)
{
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
@@ -575,6 +522,7 @@ namespace AssetStudio
}
else
{
+ Logger.Verbose("Caching resource stream");
resourceFileReaders[file.fileName] = cabReader; //TODO
}
}
@@ -612,6 +560,8 @@ namespace AssetStudio
public void Clear()
{
+ Logger.Verbose("Cleaning up...");
+
foreach (var assetsFile in assetsFileList)
{
assetsFile.Objects.Clear();
@@ -653,103 +603,40 @@ namespace AssetStudio
var objectReader = new ObjectReader(assetsFile.reader, assetsFile, objectInfo, Game);
try
{
- Object obj;
- switch (objectReader.type)
+ Object obj = objectReader.type switch
{
- 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 when Renderer.Parsable:
- 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 when Shader.Parsable:
- obj = new Shader(objectReader);
- break;
- case ClassIDType.SkinnedMeshRenderer when Renderer.Parsable:
- 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;
- }
+ ClassIDType.Animation => new Animation(objectReader),
+ ClassIDType.AnimationClip when AnimationClip.Parsable => new AnimationClip(objectReader),
+ ClassIDType.Animator => new Animator(objectReader),
+ ClassIDType.AnimatorController => new AnimatorController(objectReader),
+ ClassIDType.AnimatorOverrideController => new AnimatorOverrideController(objectReader),
+ ClassIDType.AssetBundle => new AssetBundle(objectReader),
+ ClassIDType.AudioClip => new AudioClip(objectReader),
+ ClassIDType.Avatar => new Avatar(objectReader),
+ ClassIDType.Font => new Font(objectReader),
+ ClassIDType.GameObject => new GameObject(objectReader),
+ ClassIDType.IndexObject => new IndexObject(objectReader),
+ ClassIDType.Material => new Material(objectReader),
+ ClassIDType.Mesh => new Mesh(objectReader),
+ ClassIDType.MeshFilter => new MeshFilter(objectReader),
+ ClassIDType.MeshRenderer when Renderer.Parsable => new MeshRenderer(objectReader),
+ ClassIDType.MiHoYoBinData => new MiHoYoBinData(objectReader),
+ ClassIDType.MonoBehaviour => new MonoBehaviour(objectReader),
+ ClassIDType.MonoScript => new MonoScript(objectReader),
+ ClassIDType.MovieTexture => new MovieTexture(objectReader),
+ ClassIDType.PlayerSettings => new PlayerSettings(objectReader),
+ ClassIDType.RectTransform => new RectTransform(objectReader),
+ ClassIDType.Shader when Shader.Parsable => new Shader(objectReader),
+ ClassIDType.SkinnedMeshRenderer when Renderer.Parsable => new SkinnedMeshRenderer(objectReader),
+ ClassIDType.Sprite => new Sprite(objectReader),
+ ClassIDType.SpriteAtlas => new SpriteAtlas(objectReader),
+ ClassIDType.TextAsset => new TextAsset(objectReader),
+ ClassIDType.Texture2D => new Texture2D(objectReader),
+ ClassIDType.Transform => new Transform(objectReader),
+ ClassIDType.VideoClip => new VideoClip(objectReader),
+ ClassIDType.ResourceManager => new ResourceManager(objectReader),
+ _ => new Object(objectReader),
+ };
assetsFile.AddObject(obj);
}
catch (Exception e)
@@ -784,57 +671,67 @@ namespace AssetStudio
}
if (obj is GameObject m_GameObject)
{
- foreach (var pptr in m_GameObject.m_Components)
- {
+ Logger.Verbose($"GameObject with {m_GameObject.m_PathID} in file {m_GameObject.assetsFile.fileName} has {m_GameObject.m_Components.Length} components, Attempting to fetch them...");
+ foreach (var pptr in m_GameObject.m_Components)
+ {
if (pptr.TryGet(out var m_Component))
- {
+ {
switch (m_Component)
- {
+ {
case Transform m_Transform:
+ Logger.Verbose($"Fetched Transform component with {m_Transform.m_PathID} in file {m_Transform.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_Transform = m_Transform;
break;
case MeshRenderer m_MeshRenderer:
+ Logger.Verbose($"Fetched MeshRenderer component with {m_MeshRenderer.m_PathID} in file {m_MeshRenderer.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_MeshRenderer = m_MeshRenderer;
break;
case MeshFilter m_MeshFilter:
+ Logger.Verbose($"Fetched MeshFilter component with {m_MeshFilter.m_PathID} in file {m_MeshFilter.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_MeshFilter = m_MeshFilter;
break;
case SkinnedMeshRenderer m_SkinnedMeshRenderer:
+ Logger.Verbose($"Fetched SkinnedMeshRenderer component with {m_SkinnedMeshRenderer.m_PathID} in file {m_SkinnedMeshRenderer.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_SkinnedMeshRenderer = m_SkinnedMeshRenderer;
break;
case Animator m_Animator:
+ Logger.Verbose($"Fetched Animator component with {m_Animator.m_PathID} in file {m_Animator.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_Animator = m_Animator;
break;
case Animation m_Animation:
+ Logger.Verbose($"Fetched Animation component with {m_Animation.m_PathID} in file {m_Animation.assetsFile.fileName}, assigning to GameObject components...");
m_GameObject.m_Animation = m_Animation;
break;
- }
}
}
+ }
}
else if (obj is SpriteAtlas m_SpriteAtlas)
{
- if (m_SpriteAtlas.m_RenderDataMap.Count > 0)
+ if (m_SpriteAtlas.m_RenderDataMap.Count > 0)
+ {
+ Logger.Verbose($"SpriteAtlas with {m_SpriteAtlas.m_PathID} in file {m_SpriteAtlas.assetsFile.fileName} has {m_SpriteAtlas.m_PackedSprites.Length} packed sprites, Attempting to fetch them...");
+ foreach (var m_PackedSprite in m_SpriteAtlas.m_PackedSprites)
{
- foreach (var m_PackedSprite in m_SpriteAtlas.m_PackedSprites)
- {
if (m_PackedSprite.TryGet(out var m_Sprite))
+ {
+ if (m_Sprite.m_SpriteAtlas.IsNull)
+ {
+ Logger.Verbose($"Fetched Sprite with {m_Sprite.m_PathID} in file {m_Sprite.assetsFile.fileName}, assigning to parent SpriteAtlas...");
+ m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
+ }
+ else
{
- 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);
- }
+ if (m_SpriteAtlaOld.m_IsVariant)
+ {
+ Logger.Verbose($"Fetched Sprite with {m_Sprite.m_PathID} in file {m_Sprite.assetsFile.fileName} has a variant of the origianl SpriteAtlas, disposing of the variant and assinging to the parent SpriteAtlas...");
+ m_Sprite.m_SpriteAtlas.Set(m_SpriteAtlas);
}
}
}
}
+ }
}
}
}
diff --git a/AssetStudio/BundleFile.cs b/AssetStudio/BundleFile.cs
index 35b5d6d..cca62d0 100644
--- a/AssetStudio/BundleFile.cs
+++ b/AssetStudio/BundleFile.cs
@@ -5,6 +5,7 @@ using System.Data;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using System.Text;
namespace AssetStudio
{
@@ -49,6 +50,20 @@ namespace AssetStudio
public uint compressedBlocksInfoSize;
public uint uncompressedBlocksInfoSize;
public ArchiveFlags flags;
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append($"signature: {signature} | ");
+ sb.Append($"version: {version} | ");
+ sb.Append($"unityVersion: {unityVersion} | ");
+ sb.Append($"unityRevision: {unityRevision} | ");
+ sb.Append($"size: 0x{size:X8} | ");
+ sb.Append($"compressedBlocksInfoSize: 0x{compressedBlocksInfoSize:X8} | ");
+ sb.Append($"uncompressedBlocksInfoSize: 0x{uncompressedBlocksInfoSize:X8} | ");
+ sb.Append($"flags: 0x{(int)flags:X8}");
+ return sb.ToString();
+ }
}
public class StorageBlock
@@ -56,6 +71,15 @@ namespace AssetStudio
public uint compressedSize;
public uint uncompressedSize;
public StorageBlockFlags flags;
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append($"compressedSize: 0x{compressedSize:X8} | ");
+ sb.Append($"uncompressedSize: 0x{uncompressedSize:X8} | ");
+ sb.Append($"flags: 0x{(int)flags:X8}");
+ return sb.ToString();
+ }
}
public class Node
@@ -64,6 +88,16 @@ namespace AssetStudio
public long size;
public uint flags;
public string path;
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append($"offset: 0x{offset:X8} | ");
+ sb.Append($"size: 0x{size:X8} | ");
+ sb.Append($"flags: {flags} | ");
+ sb.Append($"path: {path}");
+ return sb.ToString();
+ }
}
private Game Game;
@@ -121,6 +155,7 @@ namespace AssetStudio
{
Header header = new Header();
header.signature = reader.ReadStringToNull(20);
+ Logger.Verbose($"Parsed signature {header.signature}");
switch (header.signature)
{
case "UnityFS":
@@ -129,6 +164,7 @@ namespace AssetStudio
var version = reader.ReadUInt32();
if (version > 11)
{
+ Logger.Verbose($"Encrypted bundle header with key {version}");
XORShift128.InitSeed(version);
header.version = 6;
header.unityVersion = "5.x.x";
@@ -205,6 +241,7 @@ namespace AssetStudio
{
Stream blocksStream;
var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize);
+ Logger.Verbose($"Total size of decompressed blocks: {uncompressedSizeSum}");
if (uncompressedSizeSum >= int.MaxValue)
{
/*var memoryMappedFile = MemoryMappedFile.CreateNew(null, uncompressedSizeSum);
@@ -220,19 +257,17 @@ namespace AssetStudio
private void ReadBlocksAndDirectory(FileReader reader, Stream blocksStream)
{
+ Logger.Verbose($"Writing block and directory to blocks stream...");
+
var isCompressed = m_Header.signature == "UnityWeb";
foreach (var blockInfo in m_BlocksInfo)
{
var uncompressedBytes = reader.ReadBytes((int)blockInfo.compressedSize);
if (isCompressed)
{
- using (var memoryStream = new MemoryStream(uncompressedBytes))
- {
- using (var decompressStream = SevenZipHelper.StreamDecompress(memoryStream))
- {
- uncompressedBytes = decompressStream.ToArray();
- }
- }
+ using var memoryStream = new MemoryStream(uncompressedBytes);
+ using var decompressStream = SevenZipHelper.StreamDecompress(memoryStream);
+ uncompressedBytes = decompressStream.ToArray();
}
blocksStream.Write(uncompressedBytes, 0, uncompressedBytes.Length);
}
@@ -240,6 +275,7 @@ namespace AssetStudio
var blocksReader = new EndianBinaryReader(blocksStream);
var nodesCount = blocksReader.ReadInt32();
m_DirectoryInfo = new Node[nodesCount];
+ Logger.Verbose($"Directory count: {nodesCount}");
for (int i = 0; i < nodesCount; i++)
{
m_DirectoryInfo[i] = new Node
@@ -253,6 +289,8 @@ namespace AssetStudio
public void ReadFiles(Stream blocksStream, string path)
{
+ Logger.Verbose($"Writing files from blocks stream...");
+
fileList = new StreamFile[m_DirectoryInfo.Length];
for (int i = 0; i < m_DirectoryInfo.Length; i++)
{
@@ -286,6 +324,7 @@ namespace AssetStudio
m_Header.uncompressedBlocksInfoSize ^= XORShift128.NextDecryptUInt();
m_Header.compressedBlocksInfoSize ^= XORShift128.NextDecryptUInt();
XORShift128.Init = false;
+ Logger.Verbose($"Bundle header decrypted");
}
private void ReadHeader(FileReader reader)
@@ -317,10 +356,13 @@ namespace AssetStudio
m_Header.compressedBlocksInfoSize -= 0xCA;
m_Header.uncompressedBlocksInfoSize -= 0xCA;
}
+
+ Logger.Verbose($"Bundle header Info: {m_Header}");
}
- private void ReadUnityCN(EndianBinaryReader reader)
+ private void ReadUnityCN(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with UnityCN encryption");
ArchiveFlags mask;
var version = ParseVersion();
@@ -339,8 +381,11 @@ namespace AssetStudio
HasBlockInfoNeedPaddingAtStart = true;
}
+ Logger.Verbose($"Mask set to 0x{mask:X8}");
+
if ((m_Header.flags & mask) != 0)
{
+ Logger.Verbose($"Encryption flag exist, file is encrypted, attempting to decrypt");
UnityCN = new UnityCN(reader);
}
}
@@ -367,6 +412,7 @@ namespace AssetStudio
var blocksInfoBytesSpan = blocksInfoBytes.AsSpan();
var uncompressedSize = m_Header.uncompressedBlocksInfoSize;
var compressionType = (CompressionType)(m_Header.flags & ArchiveFlags.CompressionTypeMask);
+ Logger.Verbose($"BlockInfo compression type: {compressionType}");
switch (compressionType) //kArchiveCompressionTypeMask
{
case CompressionType.None: //None
@@ -399,6 +445,7 @@ namespace AssetStudio
case CompressionType.Lz4Mr0k: //Lz4Mr0k
if (Mr0kUtils.IsMr0k(blocksInfoBytesSpan))
{
+ Logger.Verbose($"Header encrypted with mr0k, decrypting...");
blocksInfoBytesSpan = Mr0kUtils.Decrypt(blocksInfoBytesSpan, (Mr0k)Game).ToArray();
}
goto case CompressionType.Lz4HC;
@@ -413,6 +460,7 @@ namespace AssetStudio
}
var blocksInfoCount = blocksInfoReader.ReadInt32();
m_BlocksInfo = new StorageBlock[blocksInfoCount];
+ Logger.Verbose($"Blocks count: {blocksInfoCount}");
for (int i = 0; i < blocksInfoCount; i++)
{
m_BlocksInfo[i] = new StorageBlock
@@ -421,10 +469,13 @@ namespace AssetStudio
compressedSize = blocksInfoReader.ReadUInt32(),
flags = (StorageBlockFlags)blocksInfoReader.ReadUInt16()
};
+
+ Logger.Verbose($"Block {i} Info: {m_BlocksInfo[i]}");
}
var nodesCount = blocksInfoReader.ReadInt32();
m_DirectoryInfo = new Node[nodesCount];
+ Logger.Verbose($"Directory count: {nodesCount}");
for (int i = 0; i < nodesCount; i++)
{
m_DirectoryInfo[i] = new Node
@@ -434,6 +485,8 @@ namespace AssetStudio
flags = blocksInfoReader.ReadUInt32(),
path = blocksInfoReader.ReadStringToNull(),
};
+
+ Logger.Verbose($"Directory {i} Info: {m_DirectoryInfo[i]}");
}
}
if (HasBlockInfoNeedPaddingAtStart && (m_Header.flags & ArchiveFlags.BlockInfoNeedPaddingAtStart) != 0)
@@ -444,10 +497,14 @@ namespace AssetStudio
private void ReadBlocks(FileReader reader, Stream blocksStream)
{
+ Logger.Verbose($"Writing block to blocks stream...");
+
for (int i = 0; i < m_BlocksInfo.Length; i++)
{
+ Logger.Verbose($"Reading block {i}...");
var blockInfo = m_BlocksInfo[i];
var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask);
+ Logger.Verbose($"Block compression type {compressionType}");
switch (compressionType) //kStorageBlockCompressionTypeMask
{
case CompressionType.None: //None
@@ -470,10 +527,12 @@ namespace AssetStudio
var compressedBytesSpan = compressedBytes.AsSpan(0, compressedSize);
if (compressionType == CompressionType.Lz4Mr0k && Mr0kUtils.IsMr0k(compressedBytes))
{
+ Logger.Verbose($"Block encrypted with mr0k, decrypting...");
compressedBytesSpan = Mr0kUtils.Decrypt(compressedBytesSpan, (Mr0k)Game);
}
if (Game.Type.IsUnityCN() && ((int)blockInfo.flags & 0x100) != 0)
{
+ Logger.Verbose($"Decrypting block with UnityCN...");
UnityCN.DecryptBlock(compressedBytes, compressedSize, i);
}
if (Game.Type.IsNetEase() && i == 0)
diff --git a/AssetStudio/Classes/AnimationClip.cs b/AssetStudio/Classes/AnimationClip.cs
index bc49c6f..886d535 100644
--- a/AssetStudio/Classes/AnimationClip.cs
+++ b/AssetStudio/Classes/AnimationClip.cs
@@ -965,6 +965,8 @@ namespace AssetStudio
public sealed class AnimationClip : NamedObject
{
+ public static bool Parsable;
+
public AnimationType m_AnimationType;
public bool m_Legacy;
public bool m_Compressed;
diff --git a/AssetStudio/Classes/Object.cs b/AssetStudio/Classes/Object.cs
index 4d7ac17..8926390 100644
--- a/AssetStudio/Classes/Object.cs
+++ b/AssetStudio/Classes/Object.cs
@@ -37,6 +37,8 @@ namespace AssetStudio
serializedType = reader.serializedType;
byteSize = reader.byteSize;
+ Logger.Verbose($"Attempting to read object {type} with {m_PathID} in file {assetsFile.fileName}, starting from offset 0x{reader.byteStart:X8} with expected size of 0x{byteSize:X8} !!");
+
if (platform == BuildTarget.NoTarget)
{
var m_ObjectHideFlags = reader.ReadUInt32();
@@ -81,6 +83,7 @@ namespace AssetStudio
public byte[] GetRawData()
{
+ Logger.Verbose($"Dumping raw bytes of the object with {m_PathID} in file {assetsFile.fileName}...");
reader.Reset();
return reader.ReadBytes((int)byteSize);
}
diff --git a/AssetStudio/Classes/Texture2D.cs b/AssetStudio/Classes/Texture2D.cs
index 0258044..e7c22ff 100644
--- a/AssetStudio/Classes/Texture2D.cs
+++ b/AssetStudio/Classes/Texture2D.cs
@@ -105,7 +105,7 @@ namespace AssetStudio
var m_ReadAllowed = reader.ReadBoolean();
}
}
- if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2) || reader.Game.Type.IsGI()) //2018.2 and up
+ if (version[0] > 2018 || (version[0] == 2018 && version[1] >= 2)) //2018.2 and up
{
var m_StreamingMipmaps = reader.ReadBoolean();
}
diff --git a/AssetStudio/Crypto/BlkUtils.cs b/AssetStudio/Crypto/BlkUtils.cs
index b451f25..3f32169 100644
--- a/AssetStudio/Crypto/BlkUtils.cs
+++ b/AssetStudio/Crypto/BlkUtils.cs
@@ -1,5 +1,7 @@
using System;
using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Text;
namespace AssetStudio
{
@@ -8,16 +10,20 @@ namespace AssetStudio
private const int DataOffset = 0x2A;
private const int KeySize = 0x1000;
private const int SeedBlockSize = 0x800;
+ private const int BufferSize = 0x10000;
public static XORStream Decrypt(FileReader reader, Blk blk)
{
reader.Endian = EndianType.LittleEndian;
var signature = reader.ReadStringToNull();
+ Logger.Verbose($"Signature: {signature}");
var count = reader.ReadInt32();
+ Logger.Verbose($"Key size: {count}");
var key = reader.ReadBytes(count);
reader.Position += count;
var seedSize = Math.Min(reader.ReadInt16(), blk.SBox.IsNullOrEmpty() ? SeedBlockSize : SeedBlockSize * 2);
+ Logger.Verbose($"Seed size: 0x{seedSize:X8}");
if (!blk.SBox.IsNullOrEmpty() && blk.Type.IsGI())
{
@@ -47,6 +53,8 @@ namespace AssetStudio
var keyHigh = BinaryPrimitives.ReadUInt64LittleEndian(key.AsSpan(8, 8));
var seed = keyLow ^ keyHigh ^ keySeed ^ blk.InitSeed;
+ Logger.Verbose($"Seed: 0x{seed:X8}");
+
var mt64 = new MT19937_64(seed);
var xorpad = new byte[KeySize];
for (int i = 0; i < KeySize; i += 8)
@@ -56,5 +64,48 @@ namespace AssetStudio
return new XORStream(reader.BaseStream, DataOffset, xorpad);
}
+
+ public static IEnumerable GetOffsets(this XORStream stream, string path)
+ {
+ if (AssetsHelper.TryGet(path, out var offsets))
+ {
+ foreach(var offset in offsets)
+ {
+ stream.Offset = offset;
+ yield return offset;
+ }
+ }
+ else
+ {
+ using var reader = new FileReader(path, stream, true);
+ var signature = reader.FileType switch
+ {
+ FileType.BundleFile => "UnityFS\x00",
+ FileType.Mhy0File => "mhy0",
+ _ => throw new InvalidOperationException()
+ };
+
+ Logger.Verbose($"Prased signature: {signature}");
+
+ var signatureBytes = Encoding.UTF8.GetBytes(signature);
+ var buffer = BigArrayPool.Shared.Rent(BufferSize);
+ while (stream.Remaining > 0)
+ {
+ var index = 0;
+ var absOffset = stream.AbsolutePosition;
+ var read = stream.Read(buffer);
+ while (index < read)
+ {
+ index = buffer.AsSpan(0, read).Search(signatureBytes, index);
+ if (index == -1) break;
+ var offset = absOffset + index;
+ stream.Offset = offset;
+ yield return offset;
+ index++;
+ }
+ }
+ BigArrayPool.Shared.Return(buffer);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/AssetStudio/Crypto/Mr0kUtils.cs b/AssetStudio/Crypto/Mr0kUtils.cs
index e213085..a4fe195 100644
--- a/AssetStudio/Crypto/Mr0kUtils.cs
+++ b/AssetStudio/Crypto/Mr0kUtils.cs
@@ -21,6 +21,7 @@ namespace AssetStudio
var encryptedBlockSize = Math.Min(0x10 * ((data.Length - 0x94) >> 7), BlockSize);
+ Logger.Verbose($"Encrypted block size: {encryptedBlockSize}");
if (!mr0k.InitVector.IsNullOrEmpty())
{
for (int i = 0; i < mr0k.InitVector.Length; i++)
@@ -47,6 +48,8 @@ namespace AssetStudio
var seed2 = BinaryPrimitives.ReadUInt64LittleEndian(key3);
var seed = seed2 ^ seed1 ^ (seed1 + (uint)data.Length - 20);
+ Logger.Verbose($"Seed: 0x{seed:X8}");
+
var encryptedBlock = data.Slice(0x94, encryptedBlockSize);
var seedSpan = BitConverter.GetBytes(seed);
for (var i = 0; i < encryptedBlockSize; i++)
diff --git a/AssetStudio/Crypto/NetEaseUtils.cs b/AssetStudio/Crypto/NetEaseUtils.cs
index d56ce3c..0b7079c 100644
--- a/AssetStudio/Crypto/NetEaseUtils.cs
+++ b/AssetStudio/Crypto/NetEaseUtils.cs
@@ -11,6 +11,8 @@ namespace AssetStudio
private static readonly byte[] Signature = new byte[] { 0xEE, 0xDD };
public static void Decrypt(Span bytes)
{
+ Logger.Verbose($"Attempting to decrypt block with NetEase encryption...");
+
var (encryptedOffset, encryptedSize) = ReadHeader(bytes);
var encrypted = bytes.Slice(encryptedOffset, encryptedSize);
var encryptedInts = MemoryMarshal.Cast(encrypted);
@@ -77,7 +79,7 @@ namespace AssetStudio
}
private static (int, int) ReadHeader(Span bytes)
{
- var index = bytes.Search(Signature, 0);
+ var index = bytes.Search(Signature);
if (index == -1 || index >= 0x40)
{
throw new Exception("Header not found !!");
@@ -127,6 +129,7 @@ namespace AssetStudio
throw new Exception("Unsupported version");
}
var versionString = version.ToString("X4");
+ Logger.Verbose($"Bundle version: {versionString}");
Encoding.UTF8.GetBytes(versionString, bytes);
}
diff --git a/AssetStudio/Crypto/OPFPUtils.cs b/AssetStudio/Crypto/OPFPUtils.cs
index 39c5ee4..84f6559 100644
--- a/AssetStudio/Crypto/OPFPUtils.cs
+++ b/AssetStudio/Crypto/OPFPUtils.cs
@@ -12,6 +12,7 @@ namespace AssetStudio
public static void Decrypt(Span data, string path)
{
+ Logger.Verbose($"Attempting to decrypt block with OPFP encryption...");
if (IsEncryptionBundle(path, out var key, out var version))
{
switch (version)
@@ -41,30 +42,39 @@ namespace AssetStudio
{
if (V1_Prefixes.Any(prefix => relativePath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{
+ Logger.Verbose("Path matches with V1 prefixes, generatring key...");
key = (byte)Path.GetFileName(relativePath).Length;
version = 1;
+ Logger.Verbose($"version: {version}, key: {key}");
return true;
}
else if (V0_Prefixes.Any(prefix => relativePath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{
+ Logger.Verbose("Path matches with V2 prefixes, generatring key...");
+
key = (byte)relativePath.Length;
version = 0;
+ Logger.Verbose($"version: {version}, key: {key}");
return true;
}
}
+ Logger.Verbose($"Unknown encryption type");
key = 0x00;
version = 0;
return false;
}
private static bool IsFixedPath(string path, out string fixedPath)
{
+ Logger.Verbose($"Fixing path before checking...");
var dirs = path.Split(Path.DirectorySeparatorChar);
if (dirs.Contains(BaseFolder))
{
var idx = Array.IndexOf(dirs, BaseFolder);
+ Logger.Verbose($"Seperator found at index {idx}");
fixedPath = string.Join(Path.DirectorySeparatorChar, dirs[(idx+1)..]).Replace("\\", "/");
return true;
}
+ Logger.Verbose($"Unknown path");
fixedPath = string.Empty;
return false;
}
diff --git a/AssetStudio/Crypto/UnityCN.cs b/AssetStudio/Crypto/UnityCN.cs
index 7f4df11..79c0a22 100644
--- a/AssetStudio/Crypto/UnityCN.cs
+++ b/AssetStudio/Crypto/UnityCN.cs
@@ -28,8 +28,11 @@ namespace AssetStudio
DecryptKey(signatureKey, signatureBytes);
var str = Encoding.UTF8.GetString(signatureBytes);
+ Logger.Verbose($"Decrypted signature is {str}");
if (str != Signature)
- throw new Exception("Invalid Signature !!");
+ {
+ throw new Exception($"Invalid Signature, Expected {Signature} but found {str} instead");
+ }
DecryptKey(infoKey, infoBytes);
@@ -41,19 +44,20 @@ namespace AssetStudio
var idx = (i % 4 * 4) + (i / 4);
Sub[idx] = subBytes[i];
}
+
}
public static bool SetKey(Entry entry)
{
+ Logger.Verbose($"Initializing decryptor with key {entry.Key}");
try
{
- using (var aes = Aes.Create())
- {
- aes.Mode = CipherMode.ECB;
- aes.Key = Convert.FromHexString(entry.Key);
+ using var aes = Aes.Create();
+ aes.Mode = CipherMode.ECB;
+ aes.Key = Convert.FromHexString(entry.Key);
- Encryptor = aes.CreateEncryptor();
- }
+ Encryptor = aes.CreateEncryptor();
+ Logger.Verbose($"Decryptor initialized !!");
}
catch (Exception e)
{
diff --git a/AssetStudio/Extensions/ByteArrayExtensions.cs b/AssetStudio/Extensions/ByteArrayExtensions.cs
index 3159ba6..fa2f54b 100644
--- a/AssetStudio/Extensions/ByteArrayExtensions.cs
+++ b/AssetStudio/Extensions/ByteArrayExtensions.cs
@@ -19,7 +19,7 @@ namespace AssetStudio
return buffer;
}
public static int Search(this byte[] src, string value, int offset = 0) => Search(src.AsSpan(), Encoding.UTF8.GetBytes(value), offset);
- public static int Search(this Span src, byte[] pattern, int offset)
+ public static int Search(this Span src, byte[] pattern, int offset = 0)
{
int maxFirstCharSlot = src.Length - pattern.Length + 1;
for (int i = offset; i < maxFirstCharSlot; i++)
diff --git a/AssetStudio/FileIdentifier.cs b/AssetStudio/FileIdentifier.cs
index 8a76e77..daf5408 100644
--- a/AssetStudio/FileIdentifier.cs
+++ b/AssetStudio/FileIdentifier.cs
@@ -13,5 +13,15 @@ namespace AssetStudio
//custom
public string fileName;
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append($"Guid: {guid} | ");
+ sb.Append($"type: {type} | ");
+ sb.Append($"pathName: {pathName} | ");
+ sb.Append($"fileName: {fileName}");
+ return sb.ToString();
+ }
}
}
diff --git a/AssetStudio/FileReader.cs b/AssetStudio/FileReader.cs
index d79944f..b8b1aca 100644
--- a/AssetStudio/FileReader.cs
+++ b/AssetStudio/FileReader.cs
@@ -27,12 +27,14 @@ namespace AssetStudio
FullPath = Path.GetFullPath(path);
FileName = Path.GetFileName(path);
FileType = CheckFileType();
+ Logger.Verbose($"File {path} type is {FileType}");
}
private FileType CheckFileType()
{
var signature = this.ReadStringToNull(20);
Position = 0;
+ Logger.Verbose($"Parsed signature is {signature}");
switch (signature)
{
case "ENCR":
@@ -47,46 +49,59 @@ namespace AssetStudio
return FileType.BlkFile;
default:
{
+ Logger.Verbose("signature does not match any of the supported string signatures, attempting to check bytes signatures");
byte[] magic = ReadBytes(2);
Position = 0;
+ Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (gzipMagic.SequenceEqual(magic))
{
return FileType.GZipFile;
}
+ Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(gzipMagic)}");
Position = 0x20;
magic = ReadBytes(6);
Position = 0;
+ Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (brotliMagic.SequenceEqual(magic))
{
return FileType.BrotliFile;
}
+ Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(brotliMagic)}");
if (IsSerializedFile())
{
return FileType.AssetsFile;
}
magic = ReadBytes(4);
Position = 0;
+ Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (zipMagic.SequenceEqual(magic) || zipSpannedMagic.SequenceEqual(magic))
{
return FileType.ZipFile;
}
+ Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(zipMagic)} or {Convert.ToHexString(zipSpannedMagic)}");
if (mhy0Magic.SequenceEqual(magic))
{
return FileType.Mhy0File;
}
+ Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(mhy0Magic)}");
magic = ReadBytes(7);
Position = 0;
+ Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (narakaMagic.SequenceEqual(magic))
{
return FileType.BundleFile;
}
+ Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(narakaMagic)}");
magic = ReadBytes(9);
Position = 0;
+ Logger.Verbose($"Parsed signature is {Convert.ToHexString(magic)}");
if (gunfireMagic.SequenceEqual(magic))
{
Position = 0x32;
return FileType.BundleFile;
}
+ Logger.Verbose($"Parsed signature does not match with expected signature {Convert.ToHexString(gunfireMagic)}");
+ Logger.Verbose($"Parsed signature does not match any of the supported signatures, assuming resource file");
return FileType.ResourceFile;
}
}
@@ -94,9 +109,12 @@ namespace AssetStudio
private bool IsSerializedFile()
{
+ Logger.Verbose($"Attempting to check if the file is serialized file...");
+
var fileSize = BaseStream.Length;
if (fileSize < 20)
{
+ Logger.Verbose($"File size 0x{fileSize:X8} is too small, minimal acceptable size is 0x14, aborting...");
return false;
}
var m_MetadataSize = ReadUInt32();
@@ -109,6 +127,7 @@ namespace AssetStudio
{
if (fileSize < 48)
{
+ Logger.Verbose($"File size 0x{fileSize:X8} for version {m_Version} is too small, minimal acceptable size is 0x30, aborting...");
Position = 0;
return false;
}
@@ -119,12 +138,15 @@ namespace AssetStudio
Position = 0;
if (m_FileSize != fileSize)
{
+ Logger.Verbose($"Parsed file size 0x{m_FileSize:X8} does not match stream size {fileSize}, file might be corrupted, aborting...");
return false;
}
if (m_DataOffset > fileSize)
{
+ Logger.Verbose($"Parsed data offset 0x{m_DataOffset:X8} is outside the stream of the size {fileSize}, file might be corrupted, aborting...");
return false;
}
+ Logger.Verbose($"Valid serialized file !!");
return true;
}
}
@@ -133,8 +155,10 @@ namespace AssetStudio
{
public static FileReader PreProcessing(this FileReader reader, Game game)
{
+ Logger.Verbose($"Applying preprocessing to file {reader.FileName}");
if (reader.FileType == FileType.ResourceFile || !game.Type.IsNormal())
{
+ Logger.Verbose("File is encrypted !!");
switch (game.Type)
{
case GameType.GI_Pack:
@@ -147,16 +171,13 @@ namespace AssetStudio
reader = DecryptEnsembleStar(reader);
break;
case GameType.OPFP:
- reader = ParseOPFP(reader);
- break;
case GameType.FakeHeader:
+ case GameType.ShiningNikki:
reader = ParseFakeHeader(reader);
break;
case GameType.FantasyOfWind:
reader = DecryptFantasyOfWind(reader);
break;
- case GameType.ShiningNikki:
- reader = ParseShiningNikki(reader);
break;
case GameType.HelixWaltz2:
reader = ParseHelixWaltz2(reader);
@@ -174,6 +195,7 @@ namespace AssetStudio
}
if (reader.FileType == FileType.BundleFile && game.Type.IsBlockFile())
{
+ Logger.Verbose("File might have multiple bundles !!");
try
{
var signature = reader.ReadStringToNull();
@@ -183,6 +205,8 @@ namespace AssetStudio
var size = reader.ReadInt64();
if (!(signature == "UnityFS" && size == reader.BaseStream.Length))
{
+ Logger.Verbose($"Found signature UnityFS, expected bundle size is 0x{size:X8}, found 0x{reader.BaseStream.Length} instead !!");
+ Logger.Verbose("Loading as block file !!");
reader.FileType = FileType.BlockFile;
}
}
@@ -190,6 +214,7 @@ namespace AssetStudio
reader.Position = 0;
}
+ Logger.Verbose("No preprocessing is needed");
return reader;
}
}
diff --git a/AssetStudio/IImported.cs b/AssetStudio/IImported.cs
index 85b2111..ff1b51e 100644
--- a/AssetStudio/IImported.cs
+++ b/AssetStudio/IImported.cs
@@ -17,7 +17,7 @@ namespace AssetStudio
public class ImportedFrame
{
public string Name { get; set; }
- public Vector3 LocalRotation { get; set; }
+ public Quaternion LocalRotation { get; set; }
public Vector3 LocalPosition { get; set; }
public Vector3 LocalScale { get; set; }
public ImportedFrame Parent { get; set; }
@@ -248,7 +248,7 @@ namespace AssetStudio
{
public string Path { get; set; }
public List> Scalings = new List>();
- public List> Rotations = new List>();
+ public List> Rotations = new List>();
public List> Translations = new List>();
public ImportedBlendShape BlendShape;
}
diff --git a/AssetStudio/ILogger.cs b/AssetStudio/ILogger.cs
index fe25f99..474e479 100644
--- a/AssetStudio/ILogger.cs
+++ b/AssetStudio/ILogger.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
@@ -34,4 +35,39 @@ namespace AssetStudio
Console.WriteLine("[{0}] {1}", loggerEvent, message);
}
}
+
+ public sealed class FileLogger : ILogger
+ {
+ private const string LogFileName = "log.txt";
+ private const string PrevLogFileName = "log_prev.txt";
+ private readonly object LockWriter = new object();
+
+ public StreamWriter Writer;
+
+ public FileLogger()
+ {
+ var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogFileName);
+ var prevLogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PrevLogFileName);
+
+ if (File.Exists(logPath))
+ {
+ File.Move(logPath, prevLogPath, true);
+ }
+ Writer = new StreamWriter(logPath, true) { AutoFlush = true };
+ }
+ ~FileLogger()
+ {
+ Writer?.Dispose();
+ }
+ public void Log(LoggerEvent loggerEvent, string message, bool silent = false)
+ {
+ if (silent)
+ return;
+
+ lock (LockWriter)
+ {
+ Writer.WriteLine($"[{DateTime.Now}][{loggerEvent}] {message}");
+ }
+ }
+ }
}
diff --git a/AssetStudio/ImportHelper.cs b/AssetStudio/ImportHelper.cs
index ebd10df..36e729c 100644
--- a/AssetStudio/ImportHelper.cs
+++ b/AssetStudio/ImportHelper.cs
@@ -18,7 +18,9 @@ namespace AssetStudio
{
public static void MergeSplitAssets(string path, bool allDirectories = false)
{
+ Logger.Verbose($"Processing split assets (.splitX) prior to loading files...");
var splitFiles = Directory.GetFiles(path, "*.split0", allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
+ Logger.Verbose($"Found {splitFiles.Length} split files, attempting to merge...");
foreach (var splitFile in splitFiles)
{
var destFile = Path.GetFileNameWithoutExtension(splitFile);
@@ -27,6 +29,7 @@ namespace AssetStudio
if (!File.Exists(destFull))
{
var splitParts = Directory.GetFiles(destPath, destFile + ".split*");
+ Logger.Verbose($"Creating {destFull} where split files will be combined");
using (var destStream = File.Create(destFull))
{
for (int i = 0; i < splitParts.Length; i++)
@@ -35,6 +38,7 @@ namespace AssetStudio
using (var sourceStream = File.OpenRead(splitPart))
{
sourceStream.CopyTo(destStream);
+ Logger.Verbose($"{splitPart} has been combined into {destFull}");
}
}
}
@@ -44,6 +48,7 @@ namespace AssetStudio
public static string[] ProcessingSplitFiles(List selectFile)
{
+ Logger.Verbose("Filter out paths that has .split and has the same name");
var splitFiles = selectFile.Where(x => x.Contains(".split"))
.Select(x => Path.Combine(Path.GetDirectoryName(x), Path.GetFileNameWithoutExtension(x)))
.Distinct()
@@ -61,6 +66,7 @@ namespace AssetStudio
public static FileReader DecompressGZip(FileReader reader)
{
+ Logger.Verbose($"Decompressing GZip file {reader.FileName} into memory");
using (reader)
{
var stream = new MemoryStream();
@@ -75,6 +81,7 @@ namespace AssetStudio
public static FileReader DecompressBrotli(FileReader reader)
{
+ Logger.Verbose($"Decompressing Brotli file {reader.FileName} into memory");
using (reader)
{
var stream = new MemoryStream();
@@ -89,24 +96,31 @@ namespace AssetStudio
public static FileReader DecryptPack(FileReader reader, Game game)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Pack encryption");
+
const int PackSize = 0x880;
const string PackSignature = "pack";
const string UnityFSSignature = "UnityFS";
var data = reader.ReadBytes((int)reader.Length);
- var idx = data.Search(PackSignature);
- if (idx == -1)
+ var packIdx = data.Search(PackSignature);
+ if (packIdx == -1)
{
+ Logger.Verbose($"Signature {PackSignature} was not found, aborting...");
reader.Position = 0;
return reader;
}
- idx = data.Search("mr0k", idx);
- if (idx == -1)
+ Logger.Verbose($"Found signature {PackSignature} at offset 0x{packIdx:X8}");
+ var mr0kIdx = data.Search("mr0k", packIdx);
+ if (mr0kIdx == -1)
{
+ Logger.Verbose("Signature mr0k was not found, aborting...");
reader.Position = 0;
return reader;
}
+ Logger.Verbose($"Found signature mr0k signature at offset 0x{mr0kIdx:X8}");
+ Logger.Verbose("Attempting to process pack chunks...");
var ms = new MemoryStream();
try
{
@@ -121,9 +135,12 @@ namespace AssetStudio
var signature = reader.ReadStringToNull(4);
if (signature == PackSignature)
{
+ Logger.Verbose($"Found {PackSignature} chunk at position {reader.Position - PackSignature.Length}");
var isMr0k = reader.ReadBoolean();
+ Logger.Verbose("Chunk is mr0k encrypted");
var blockSize = BinaryPrimitives.ReadInt32LittleEndian(reader.ReadBytes(4));
+ Logger.Verbose($"Chunk size is 0x{blockSize:X8}");
Span buffer = new byte[blockSize];
reader.Read(buffer);
if (isMr0k)
@@ -134,6 +151,7 @@ namespace AssetStudio
if (bundleSize == 0)
{
+ Logger.Verbose("This is header chunk !! attempting to read the bundle size");
using var blockReader = new EndianBinaryReader(new MemoryStream(buffer.ToArray()));
var header = new Header()
{
@@ -144,17 +162,21 @@ namespace AssetStudio
size = blockReader.ReadInt64()
};
bundleSize = header.size;
+ Logger.Verbose($"Bundle size is 0x{bundleSize:X8}");
}
readSize += buffer.Length;
if (readSize % (PackSize - 0x80) == 0)
{
- reader.Position += PackSize - 9 - blockSize;
+ var padding = PackSize - 9 - blockSize;
+ reader.Position += padding;
+ Logger.Verbose($"Skip 0x{padding:X8} padding");
}
if (readSize == bundleSize)
{
+ Logger.Verbose($"Bundle has been read entirely !!");
readSize = 0;
bundleSize = 0;
}
@@ -166,6 +188,7 @@ namespace AssetStudio
signature = reader.ReadStringToNull();
if (signature == UnityFSSignature)
{
+ Logger.Verbose($"Found {UnityFSSignature} chunk at position {reader.Position - (UnityFSSignature.Length + 1)}");
var header = new Header()
{
signature = reader.ReadStringToNull(),
@@ -175,6 +198,7 @@ namespace AssetStudio
size = reader.ReadInt64()
};
+ Logger.Verbose($"Bundle size is 0x{header.size:X8}");
reader.Position = pos;
reader.BaseStream.CopyTo(ms, header.size);
continue;
@@ -196,15 +220,19 @@ namespace AssetStudio
reader.Dispose();
}
+ Logger.Verbose("Decrypted pack file successfully !!");
ms.Position = 0;
return new FileReader(reader.FullPath, ms);
}
public static FileReader DecryptMark(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Mark encryption");
+
var signature = reader.ReadStringToNull(4);
if (signature != "mark")
{
+ Logger.Verbose($"Expected signature mark, found {signature} instead, aborting...");
reader.Position = 0;
return reader;
}
@@ -240,6 +268,7 @@ namespace AssetStudio
}
}
+ Logger.Verbose("Decrypted mark file successfully !!");
reader.Dispose();
dataStream.Position = 0;
return new FileReader(reader.FullPath, dataStream);
@@ -247,8 +276,10 @@ namespace AssetStudio
public static FileReader DecryptEnsembleStar(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Ensemble Star encryption");
if (Path.GetExtension(reader.FileName) != ".z")
{
+ Logger.Verbose($"Expected file extension .z, found {Path.GetExtension(reader.FileName)} instead, aborting...");
return reader;
}
using (reader)
@@ -269,46 +300,42 @@ namespace AssetStudio
data[i] = (byte)(EnsembleStarKey1[k1] ^ ((size ^ EnsembleStarKey3[k3] ^ data[i] ^ EnsembleStarKey2[k2]) + remaining));
}
+ Logger.Verbose("Decrypted Ensemble Star file successfully !!");
return new FileReader(reader.FullPath, new MemoryStream(data));
}
}
- public static FileReader ParseOPFP(FileReader reader)
- {
- var stream = reader.BaseStream;
- var data = reader.ReadBytes(0x1000);
- var idx = data.Search("UnityFS");
- if (idx != -1)
- {
- stream = new OffsetStream(stream, idx);
- }
-
- return new FileReader(reader.FullPath, stream);
- }
-
public static FileReader ParseFakeHeader(FileReader reader)
{
+ Logger.Verbose($"Attempting to parse file {reader.FileName} with fake header");
+
var stream = reader.BaseStream;
var data = reader.ReadBytes(0x1000);
var idx = data.Search("UnityFS");
if (idx != -1)
{
+ Logger.Verbose($"Found fake header at offset 0x{idx:X8}");
var idx2 = data[(idx + 1)..].Search("UnityFS");
if (idx2 != -1)
{
+ Logger.Verbose($"Found real header at offset 0x{idx + idx2 + 1:X8}");
stream = new OffsetStream(stream, idx + idx2 + 1);
}
else
{
+ Logger.Verbose("Real header was not found, assuming fake header is the real one");
stream = new OffsetStream(stream, idx);
}
}
+ Logger.Verbose("Parsed fake header file successfully !!");
return new FileReader(reader.FullPath, stream);
}
public static FileReader DecryptFantasyOfWind(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Fantasy of Wind encryption");
+
byte[] encryptKeyName = Encoding.UTF8.GetBytes("28856");
const int MinLength = 0xC8;
const int KeyLength = 8;
@@ -319,6 +346,7 @@ namespace AssetStudio
var signature = reader.ReadStringToNull(HeadLength);
if (string.Compare(signature, "K9999") > 0 || reader.Length <= MinLength)
{
+ Logger.Verbose($"Signature version {signature} is higher than K9999 or stream length {reader.Length} is less than minimum length {MinLength}, aborting...");
reader.Position = 0;
return reader;
}
@@ -360,23 +388,13 @@ namespace AssetStudio
reader.BaseStream.CopyTo(ms);
ms.Position = 0;
+ Logger.Verbose("Decrypted Fantasy of Wind file successfully !!");
return new FileReader(reader.FullPath, ms);
}
-
- public static FileReader ParseShiningNikki(FileReader reader)
- {
- var data = reader.ReadBytes(0x1000);
- var idx = data.Search("UnityFS");
- if (idx == -1)
- {
- reader.Position = 0;
- return reader;
- }
- var stream = new OffsetStream(reader.BaseStream, idx);
- return new FileReader(reader.FullPath, stream);
- }
public static FileReader ParseHelixWaltz2(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Helix Waltz 2 encryption");
+
var originalHeader = new byte[] { 0x55, 0x6E, 0x69, 0x74, 0x79, 0x46, 0x53, 0x00, 0x00, 0x00, 0x00, 0x07, 0x35, 0x2E, 0x78, 0x2E };
var signature = reader.ReadStringToNull();
@@ -384,6 +402,7 @@ namespace AssetStudio
if (signature != "SzxFS")
{
+ Logger.Verbose($"Expected signature SzxFS, found {signature} instead, aborting...");
reader.Position = 0;
return reader;
}
@@ -415,6 +434,7 @@ namespace AssetStudio
data[i] = key[idx];
}
+ Logger.Verbose("Decrypted Helix Waltz 2 file successfully !!");
MemoryStream ms = new();
ms.Write(originalHeader);
ms.Write(data);
@@ -424,6 +444,8 @@ namespace AssetStudio
}
public static FileReader DecryptAnchorPanic(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Anchor Panic encryption");
+
const int BlockSize = 0x800;
var data = reader.ReadBytes(0x1000);
@@ -432,25 +454,31 @@ namespace AssetStudio
var idx = data.Search("UnityFS");
if (idx != -1)
{
+ Logger.Verbose("Found UnityFS signature, file might not be encrypted");
return ParseFakeHeader(reader);
}
var key = GetKey(Path.GetFileNameWithoutExtension(reader.FileName));
+ Logger.Verbose($"Calculated key is {key}");
var chunkIndex = 0;
MemoryStream ms = new();
while (reader.Remaining > 0)
{
var chunkSize = Math.Min((int)reader.Remaining, BlockSize);
+ Logger.Verbose($"Chunk {chunkIndex} size is {chunkSize}");
var chunk = reader.ReadBytes(chunkSize);
if (IsEncrypt((int)reader.Length, chunkIndex++))
+ {
+ Logger.Verbose($"Chunk {chunkIndex} is encrypted, decrypting...");
RC4(chunk, key);
+ }
ms.Write(chunk);
}
+ Logger.Verbose("Decrypted Anchor Panic file successfully !!");
ms.Position = 0;
-
return new FileReader(reader.FullPath, ms);
bool IsEncrypt(int fileSize, int chunkIndex)
@@ -533,11 +561,14 @@ namespace AssetStudio
public static FileReader DecryptDreamscapeAlbireo(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Dreamscape Albireo encryption");
+
var key = new byte[] { 0x1E, 0x1E, 0x01, 0x01, 0xFC };
var signature = reader.ReadStringToNull(4);
if (signature != "MJJ")
{
+ Logger.Verbose($"Expected signature MJJ, found {signature} instead, aborting...");
reader.Position = 0;
return reader;
}
@@ -559,6 +590,8 @@ namespace AssetStudio
var sizeLow = (u5 >> 24 | (u2 << 8)) ^ u1;
var size = (long)(sizeHigh << 32 | sizeLow);
+ Logger.Verbose($"Decrypted File info: Flag 0x{flag:X8} | Compressed blockInfo size 0x{compressedBlocksInfoSize:X8} | Decompressed blockInfo size 0x{uncompressedBlocksInfoSize:X8} | Bundle size 0x{size:X8}");
+
var blocksInfo = reader.ReadBytes((int)compressedBlocksInfoSize);
for(int i = 0; i < blocksInfo.Length; i++)
{
@@ -587,6 +620,7 @@ namespace AssetStudio
reader.BaseStream.CopyTo(ms);
ms.Position = 0;
+ Logger.Verbose("Decrypted Dreamscape Albireo file successfully !!");
return new FileReader(reader.FullPath, ms);
static uint Scrample(uint value) => (value >> 5) & 0xFFE000 | (value >> 29) | (value << 14) & 0xFF000000 | (8 * value) & 0x1FF8;
@@ -594,6 +628,8 @@ namespace AssetStudio
public static FileReader DecryptImaginaryFest(FileReader reader)
{
+ Logger.Verbose($"Attempting to decrypt file {reader.FileName} with Imaginary Fest encryption");
+
const string dataRoot = "data";
var key = new byte[] { 0xBD, 0x65, 0xF2, 0x4F, 0xBE, 0xD1, 0x36, 0xD4, 0x95, 0xFE, 0x64, 0x94, 0xCB, 0xD3, 0x7E, 0x91, 0x57, 0xB7, 0x94, 0xB7, 0x9F, 0x70, 0xB2, 0xA9, 0xA0, 0xD5, 0x4E, 0x36, 0xC6, 0x40, 0xE0, 0x2F, 0x4E, 0x6E, 0x76, 0x6D, 0xCD, 0xAE, 0xEA, 0x05, 0x13, 0x6B, 0xA7, 0x84, 0xFF, 0xED, 0x90, 0x91, 0x15, 0x7E, 0xF1, 0xF8, 0xA5, 0x9C, 0xB6, 0xDE, 0xF9, 0x56, 0x57, 0x18, 0xBF, 0x94, 0x63, 0x6F, 0x1B, 0xE2, 0x92, 0xD2, 0x7E, 0x25, 0x95, 0x23, 0x24, 0xCB, 0x93, 0xD3, 0x36, 0xD9, 0x18, 0x11, 0xF5, 0x50, 0x18, 0xE4, 0x22, 0x28, 0xD8, 0xE2, 0x1A, 0x57, 0x1E, 0x04, 0x88, 0xA5, 0x84, 0xC0, 0x6C, 0x3B, 0x46, 0x62, 0xCE, 0x85, 0x10, 0x2E, 0xA0, 0xDC, 0xD3, 0x09, 0xB2, 0xB6, 0xA4, 0x8D, 0xAF, 0x74, 0x36, 0xF7, 0x9A, 0x3F, 0x98, 0xDA, 0x62, 0x57, 0x71, 0x75, 0x92, 0x05, 0xA3, 0xB2, 0x7C, 0xCA, 0xFB, 0x1E, 0xBE, 0xC9, 0x24, 0xC1, 0xD2, 0xB9, 0xDE, 0xE4, 0x7E, 0xF3, 0x0F, 0xB4, 0xFB, 0xA2, 0xC1, 0xC2, 0x14, 0x5C, 0x78, 0x13, 0x74, 0x41, 0x8D, 0x79, 0xF4, 0x3C, 0x49, 0x92, 0x98, 0xF2, 0xCD, 0x8C, 0x09, 0xA6, 0x40, 0x34, 0x51, 0x1C, 0x11, 0x2B, 0xE0, 0x6B, 0x42, 0x9C, 0x86, 0x41, 0x06, 0xF6, 0xD2, 0x87, 0xF1, 0x10, 0x26, 0x89, 0xC2, 0x7B, 0x2A, 0x5D, 0x1C, 0xDA, 0x92, 0xC8, 0x93, 0x59, 0xF9, 0x60, 0xD0, 0xB5, 0x1E, 0xD5, 0x75, 0x56, 0xA0, 0x05, 0x83, 0x90, 0xAC, 0x72, 0xC8, 0x10, 0x09, 0xED, 0x1A, 0x46, 0xD9, 0x39, 0x6B, 0x9E, 0x19, 0x5E, 0x51, 0x44, 0x09, 0x0D, 0x74, 0xAB, 0xA8, 0xF9, 0x32, 0x43, 0xBC, 0xD2, 0xED, 0x7B, 0x6C, 0x75, 0x32, 0x24, 0x14, 0x43, 0x5D, 0x98, 0xB2, 0xFC, 0xFB, 0xF5, 0x9A, 0x19, 0x03, 0xB0, 0xB7, 0xAC, 0xAE, 0x8B };
@@ -601,12 +637,14 @@ namespace AssetStudio
var signature = Encoding.UTF8.GetString(signatureBytes[..7]);
if (signature == "UnityFS")
{
+ Logger.Verbose("Found UnityFS signature, file might not be encrypted");
reader.Position = 0;
return reader;
}
if (signatureBytes[7] != 0)
{
+ Logger.Verbose($"File might be encrypted with a byte xorkey 0x{signatureBytes[7]:X8}, attemping to decrypting...");
var xorKey = signatureBytes[7];
for (int i = 0; i < signatureBytes.Length; i++)
{
@@ -615,12 +653,14 @@ namespace AssetStudio
signature = Encoding.UTF8.GetString(signatureBytes[..7]);
if (signature == "UnityFS")
{
+ Logger.Verbose("Found UnityFS signature, key is valid, decrypting the rest of the stream");
var remaining = reader.ReadBytes((int)reader.Remaining);
for (int i = 0; i < remaining.Length; i++)
{
remaining[i] ^= xorKey;
}
+ Logger.Verbose("Decrypted Imaginary Fest file successfully !!");
var stream = new MemoryStream();
stream.Write(signatureBytes);
stream.Write(remaining);
@@ -635,25 +675,33 @@ namespace AssetStudio
var startIdx = Array.FindIndex(paths, x => x == dataRoot);
if (startIdx != -1 && startIdx != paths.Length - 1)
{
+ Logger.Verbose("File is in the data folder !!");
var path = string.Join(Path.AltDirectorySeparatorChar, paths[(startIdx+1)..]);
var offset = GetLoadAssetBundleOffset(path);
if (offset > 0 && offset < reader.Length)
{
+ Logger.Verbose($"Calculated offset is 0x{offset:X8}, attempting to read signature...");
reader.Position = offset;
signature = reader.ReadStringToNull(7);
if (signature == "UnityFS")
{
+ Logger.Verbose($"Found UnityFS signature, file starts at 0x{offset:X8} !!");
+ Logger.Verbose("Parsed Imaginary Fest file successfully !!");
reader.Position = offset;
return new FileReader(reader.FullPath, new MemoryStream(reader.ReadBytes((int)reader.Remaining)));
}
}
+ Logger.Verbose($"Invalid offset, attempting to generate key...");
reader.Position = 0;
var data = reader.ReadBytes((int)reader.Remaining);
var key_value = GetHashCode(path);
+ Logger.Verbose($"Generated key is 0x{key_value:X8}, decrypting...");
Decrypt(data, key_value);
+ Logger.Verbose("Decrypted Imaginary Fest file successfully !!");
return new FileReader(reader.FullPath, new MemoryStream(data));
}
+ Logger.Verbose("File doesn't match any of the encryption types");
reader.Position = 0;
return reader;
diff --git a/AssetStudio/LocalSerializedObjectIdentifier.cs b/AssetStudio/LocalSerializedObjectIdentifier.cs
index 24f4553..f06e39d 100644
--- a/AssetStudio/LocalSerializedObjectIdentifier.cs
+++ b/AssetStudio/LocalSerializedObjectIdentifier.cs
@@ -9,5 +9,13 @@ namespace AssetStudio
{
public int localSerializedFileIndex;
public long localIdentifierInFile;
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append($"localSerializedFileIndex: {localSerializedFileIndex} | ");
+ sb.Append($"localIdentifierInFile: {localIdentifierInFile}");
+ return sb.ToString();
+ }
}
}
diff --git a/AssetStudio/Logger.cs b/AssetStudio/Logger.cs
index 43d737a..c4d132a 100644
--- a/AssetStudio/Logger.cs
+++ b/AssetStudio/Logger.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Text;
@@ -7,21 +8,79 @@ namespace AssetStudio
{
public static class Logger
{
- public static ILogger Default = new DummyLogger();
- public static bool Silent = false;
+ private static bool _fileLogging = true;
- public static void Verbose(string message) => Default.Log(LoggerEvent.Verbose, message, Silent);
- public static void Debug(string message) => Default.Log(LoggerEvent.Debug, message, Silent);
- public static void Info(string message) => Default.Log(LoggerEvent.Info, message, Silent);
- public static void Warning(string message) => Default.Log(LoggerEvent.Warning, message, Silent);
- public static void Error(string message) => Default.Log(LoggerEvent.Error, message, Silent);
+ public static ILogger Default = new DummyLogger();
+ public static ILogger File;
+ public static bool Silent = false;
+ public static bool LogVerbose = false;
+
+ public static bool FileLogging
+ {
+ get => _fileLogging;
+ set
+ {
+ _fileLogging = value;
+ if (_fileLogging)
+ {
+ File = new FileLogger();
+ }
+ else
+ {
+ ((FileLogger)File)?.Writer?.Dispose();
+ File = null;
+ }
+ }
+ }
+
+ public static void Verbose(string message)
+ {
+ if (LogVerbose)
+ {
+ try
+ {
+ var callerMethod = new StackTrace().GetFrame(1).GetMethod();
+ var callerMethodClass = callerMethod.ReflectedType.Name;
+ if (!string.IsNullOrEmpty(callerMethodClass))
+ {
+ message = $"[{callerMethodClass}] {message}";
+ }
+ }
+ catch (Exception) { }
+ if (FileLogging) File.Log(LoggerEvent.Verbose, message);
+ Default.Log(LoggerEvent.Verbose, message, Silent);
+ }
+ }
+ public static void Debug(string message)
+ {
+ if (FileLogging) File.Log(LoggerEvent.Debug, message);
+ Default.Log(LoggerEvent.Debug, message, Silent);
+ }
+ public static void Info(string message)
+ {
+ if (FileLogging) File.Log(LoggerEvent.Info, message);
+ Default.Log(LoggerEvent.Info, message, Silent);
+ }
+ public static void Warning(string message)
+ {
+ if (FileLogging) File.Log(LoggerEvent.Warning, message);
+ Default.Log(LoggerEvent.Warning, message, Silent);
+ }
+ public static void Error(string message)
+ {
+ if (FileLogging) File.Log(LoggerEvent.Error, message);
+ Default.Log(LoggerEvent.Error, message, Silent);
+ }
public static void Error(string message, Exception e)
{
var sb = new StringBuilder();
sb.AppendLine(message);
sb.AppendLine(e.ToString());
- Default.Log(LoggerEvent.Error, sb.ToString(), Silent);
+
+ message = sb.ToString();
+ if (FileLogging) File.Log(LoggerEvent.Error, message);
+ Default.Log(LoggerEvent.Error, message, Silent);
}
}
}
diff --git a/AssetStudio/Math/Quaternion.cs b/AssetStudio/Math/Quaternion.cs
index ca129f5..66bf727 100644
--- a/AssetStudio/Math/Quaternion.cs
+++ b/AssetStudio/Math/Quaternion.cs
@@ -63,6 +63,7 @@ namespace AssetStudio
return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W);
}
+ public static Quaternion Zero => new Quaternion(0, 0, 0, 1);
public static float Dot(Quaternion a, Quaternion b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
diff --git a/AssetStudio/Mhy0File.cs b/AssetStudio/Mhy0File.cs
index cb2cb3b..bdf291f 100644
--- a/AssetStudio/Mhy0File.cs
+++ b/AssetStudio/Mhy0File.cs
@@ -15,6 +15,8 @@ namespace AssetStudio
public long Offset;
public Mhy0 mhy0;
+ public long TotalSize => 8 + m_Header.compressedBlocksInfoSize + m_BlocksInfo.Sum(x => x.compressedSize);
+
public Mhy0File(FileReader reader, string path, Mhy0 mhy0)
{
this.mhy0 = mhy0;
@@ -22,6 +24,10 @@ namespace AssetStudio
reader.Endian = EndianType.LittleEndian;
var signature = reader.ReadStringToNull(4);
+ Logger.Verbose($"Parsed signature {signature}");
+ if (signature != "mhy0")
+ throw new Exception("not a mhy0");
+
m_Header = new BundleFile.Header
{
version = 6,
@@ -30,6 +36,7 @@ namespace AssetStudio
compressedBlocksInfoSize = reader.ReadUInt32(),
flags = (ArchiveFlags)0x43
};
+ Logger.Verbose($"Header: {m_Header}");
ReadBlocksInfoAndDirectory(reader);
using var blocksStream = CreateBlocksStream(path);
ReadBlocks(reader, blocksStream);
@@ -41,9 +48,11 @@ namespace AssetStudio
var blocksInfo = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize);
DescrambleHeader(blocksInfo);
+ Logger.Verbose($"Descrambled blocksInfo signature {Convert.ToHexString(blocksInfo, 0 , 4)}");
using var blocksInfoStream = new MemoryStream(blocksInfo, 0x20, (int)m_Header.compressedBlocksInfoSize - 0x20);
using var blocksInfoReader = new EndianBinaryReader(blocksInfoStream);
m_Header.uncompressedBlocksInfoSize = blocksInfoReader.ReadMhy0UInt();
+ Logger.Verbose($"uncompressed blocksInfo size: 0x{m_Header.uncompressedBlocksInfoSize:X8}");
var compressedBlocksInfo = blocksInfoReader.ReadBytes((int)blocksInfoReader.Remaining);
var uncompressedBlocksInfo = new byte[(int)m_Header.uncompressedBlocksInfoSize];
var numWrite = LZ4Codec.Decode(compressedBlocksInfo, uncompressedBlocksInfo);
@@ -52,10 +61,12 @@ namespace AssetStudio
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {m_Header.uncompressedBlocksInfoSize} bytes");
}
+ Logger.Verbose($"Writing block and directory to blocks stream...");
using var blocksInfoUncompressedStream = new MemoryStream(uncompressedBlocksInfo);
using var blocksInfoUncompressedReader = new EndianBinaryReader(blocksInfoUncompressedStream);
var nodesCount = blocksInfoUncompressedReader.ReadMhy0Int();
m_DirectoryInfo = new BundleFile.Node[nodesCount];
+ Logger.Verbose($"Directory count: {nodesCount}");
for (int i = 0; i < nodesCount; i++)
{
m_DirectoryInfo[i] = new BundleFile.Node
@@ -65,10 +76,13 @@ namespace AssetStudio
offset = blocksInfoUncompressedReader.ReadMhy0Int(),
size = blocksInfoUncompressedReader.ReadMhy0UInt()
};
+
+ Logger.Verbose($"Directory {i} Info: {m_DirectoryInfo[i]}");
}
var blocksInfoCount = blocksInfoUncompressedReader.ReadMhy0Int();
m_BlocksInfo = new BundleFile.StorageBlock[blocksInfoCount];
+ Logger.Verbose($"Blocks count: {blocksInfoCount}");
for (int i = 0; i < blocksInfoCount; i++)
{
m_BlocksInfo[i] = new BundleFile.StorageBlock
@@ -77,6 +91,8 @@ namespace AssetStudio
uncompressedSize = blocksInfoUncompressedReader.ReadMhy0UInt(),
flags = (StorageBlockFlags)0x43
};
+
+ Logger.Verbose($"Block {i} Info: {m_BlocksInfo[i]}");
}
}
@@ -84,6 +100,7 @@ namespace AssetStudio
{
Stream blocksStream;
var uncompressedSizeSum = (int)m_BlocksInfo.Sum(x => x.uncompressedSize);
+ Logger.Verbose($"Total size of decompressed blocks: 0x{uncompressedSizeSum:X8}");
if (uncompressedSizeSum >= int.MaxValue)
blocksStream = new FileStream(path + ".temp", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
else
@@ -110,6 +127,7 @@ namespace AssetStudio
var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize);
DescrambleEntry(compressedBytesSpan);
+ Logger.Verbose($"Descrambled block signature {Convert.ToHexString(compressedBytes, 0, 4)}");
var numWrite = LZ4Codec.Decode(compressedBytesSpan[0xC..compressedSize], uncompressedBytesSpan);
if (numWrite != uncompressedSize)
{
@@ -124,6 +142,8 @@ namespace AssetStudio
private void ReadFiles(Stream blocksStream, string path)
{
+ Logger.Verbose($"Writing files from blocks stream...");
+
fileList = new StreamFile[m_DirectoryInfo.Length];
for (int i = 0; i < m_DirectoryInfo.Length; i++)
{
diff --git a/AssetStudio/ObjectInfo.cs b/AssetStudio/ObjectInfo.cs
index 23cace4..c6aa801 100644
--- a/AssetStudio/ObjectInfo.cs
+++ b/AssetStudio/ObjectInfo.cs
@@ -1,4 +1,6 @@
-namespace AssetStudio
+using System.Text;
+
+namespace AssetStudio
{
public class ObjectInfo
{
@@ -11,5 +13,18 @@
public long m_PathID;
public SerializedType serializedType;
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append($"byteStart: 0x{byteStart:X8} | ");
+ sb.Append($"byteSize: 0x{byteSize:X8} | ");
+ sb.Append($"typeID: {typeID} | ");
+ sb.Append($"classID: {classID} | ");
+ sb.Append($"isDestroyed: {isDestroyed} | ");
+ sb.Append($"stripped: {stripped} | ");
+ sb.Append($"PathID: {m_PathID}");
+ return sb.ToString();
+ }
}
}
diff --git a/AssetStudio/ObjectReader.cs b/AssetStudio/ObjectReader.cs
index a043c4a..8c704a4 100644
--- a/AssetStudio/ObjectReader.cs
+++ b/AssetStudio/ObjectReader.cs
@@ -39,10 +39,13 @@ namespace AssetStudio
serializedType = objectInfo.serializedType;
platform = assetsFile.m_TargetPlatform;
m_Version = assetsFile.header.m_Version;
+
+ Logger.Verbose($"Initialized reader for {type} object with {m_PathID} in file {assetsFile.fileName} !!");
}
public void Reset()
{
+ Logger.Verbose($"Resetting reader position to object offset 0x{byteStart:X8}...");
Position = byteStart;
}
}
diff --git a/AssetStudio/OffsetStream.cs b/AssetStudio/OffsetStream.cs
index 824890f..75e7eef 100644
--- a/AssetStudio/OffsetStream.cs
+++ b/AssetStudio/OffsetStream.cs
@@ -60,14 +60,7 @@ namespace AssetStudio
_baseStream.Seek(target, SeekOrigin.Begin);
return Position;
}
- public override int Read(byte[] buffer, int offset, int count)
- {
- if (offset > _baseStream.Length || Position + count > _baseStream.Length)
- {
- throw new IOException("Unable to read beyond stream bound");
- }
- return _baseStream.Read(buffer, offset, count);
- }
+ public override int Read(byte[] buffer, int offset, int count) => _baseStream.Read(buffer, offset, count);
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Flush() => throw new NotImplementedException();
diff --git a/AssetStudio/SerializedFile.cs b/AssetStudio/SerializedFile.cs
index e3167e5..3021eef 100644
--- a/AssetStudio/SerializedFile.cs
+++ b/AssetStudio/SerializedFile.cs
@@ -60,23 +60,28 @@ namespace AssetStudio
reader.Position = header.m_FileSize - header.m_MetadataSize;
m_FileEndianess = reader.ReadByte();
}
-
+
if (header.m_Version >= SerializedFileFormatVersion.LargeFilesSupport)
{
header.m_MetadataSize = reader.ReadUInt32();
header.m_FileSize = reader.ReadInt64();
header.m_DataOffset = reader.ReadInt64();
reader.ReadInt64(); // unknown
+
}
+ Logger.Verbose($"File {fileName} Info: {header}");
+
// ReadMetadata
if (m_FileEndianess == 0)
{
reader.Endian = EndianType.LittleEndian;
+ Logger.Verbose($"Endianness {reader.Endian}");
}
if (header.m_Version >= SerializedFileFormatVersion.Unknown_7)
{
unityVersion = reader.ReadStringToNull();
+ Logger.Verbose($"Unity version {unityVersion}");
SetVersion(unityVersion);
}
if (header.m_Version >= SerializedFileFormatVersion.Unknown_8)
@@ -84,12 +89,15 @@ namespace AssetStudio
m_TargetPlatform = (BuildTarget)reader.ReadInt32();
if (!Enum.IsDefined(typeof(BuildTarget), m_TargetPlatform))
{
+ Logger.Verbose($"Parsed target format {m_TargetPlatform} doesn't match any of supported formats, defaulting to {BuildTarget.UnknownPlatform}");
m_TargetPlatform = BuildTarget.UnknownPlatform;
}
else if (game.Type.IsMhyGroup())
{
+ Logger.Verbose($"Selected game {game.Name} is a mhy game, forcing target format {BuildTarget.StandaloneWindows64}");
m_TargetPlatform = BuildTarget.StandaloneWindows64;
}
+ Logger.Verbose($"Target format {m_TargetPlatform}");
}
if (header.m_Version >= SerializedFileFormatVersion.HasTypeTreeHashes)
{
@@ -99,6 +107,7 @@ namespace AssetStudio
// Read Types
int typeCount = reader.ReadInt32();
m_Types = new List(typeCount);
+ Logger.Verbose($"Found {typeCount} serialized types");
for (int i = 0; i < typeCount; i++)
{
m_Types.Add(ReadSerializedType(false));
@@ -114,6 +123,7 @@ namespace AssetStudio
m_Objects = new List(objectCount);
Objects = new List