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(objectCount); ObjectsDic = new Dictionary(objectCount); + Logger.Verbose($"Found {objectCount} objects"); for (int i = 0; i < objectCount; i++) { var objectInfo = new ObjectInfo(); @@ -164,12 +174,14 @@ namespace AssetStudio { objectInfo.stripped = reader.ReadByte(); } + Logger.Verbose($"Object Info: {objectInfo}"); m_Objects.Add(objectInfo); } if (header.m_Version >= SerializedFileFormatVersion.HasScriptTypeIndex) { int scriptCount = reader.ReadInt32(); + Logger.Verbose($"Found {scriptCount} scripts"); m_ScriptTypes = new List(scriptCount); for (int i = 0; i < scriptCount; i++) { @@ -184,12 +196,14 @@ namespace AssetStudio reader.AlignStream(); m_ScriptType.localIdentifierInFile = reader.ReadInt64(); } + Logger.Verbose($"Script Info: {m_ScriptType}"); m_ScriptTypes.Add(m_ScriptType); } } int externalsCount = reader.ReadInt32(); m_Externals = new List(externalsCount); + Logger.Verbose($"Found {externalsCount} externals"); for (int i = 0; i < externalsCount; i++) { var m_External = new FileIdentifier(); @@ -204,6 +218,7 @@ namespace AssetStudio } m_External.pathName = reader.ReadStringToNull(); m_External.fileName = Path.GetFileName(m_External.pathName); + Logger.Verbose($"External Info: {m_External}"); m_Externals.Add(m_External); } @@ -211,6 +226,7 @@ namespace AssetStudio { int refTypesCount = reader.ReadInt32(); m_RefTypes = new List(refTypesCount); + Logger.Verbose($"Found {refTypesCount} reference types"); for (int i = 0; i < refTypesCount; i++) { m_RefTypes.Add(ReadSerializedType(true)); @@ -239,12 +255,14 @@ namespace AssetStudio private SerializedType ReadSerializedType(bool isRefType) { + Logger.Verbose($"Attempting to parse serialized" + (isRefType ? " reference" : " ") + "type"); var type = new SerializedType(); type.classID = reader.ReadInt32(); if (game.Type.IsGIGroup() && BitConverter.ToBoolean(header.m_Reserved)) { + Logger.Verbose($"Encoded class ID {type.classID}, decoding..."); type.classID = DecodeClassID(type.classID); } @@ -273,6 +291,7 @@ namespace AssetStudio if (m_EnableTypeTree) { + Logger.Verbose($"File has type tree enabled !!"); type.m_Type = new TypeTree(); type.m_Type.m_Nodes = new List(); if (header.m_Version >= SerializedFileFormatVersion.Unknown_12 || header.m_Version == SerializedFileFormatVersion.Unknown_10) @@ -298,11 +317,13 @@ namespace AssetStudio } } + Logger.Verbose($"Serialized type info: {type}"); return type; } private void ReadTypeTree(TypeTree m_Type, int level = 0) { + Logger.Verbose($"Attempting to parse type tree..."); var typeTreeNode = new TypeTreeNode(); m_Type.m_Nodes.Add(typeTreeNode); typeTreeNode.m_Level = level; @@ -329,12 +350,16 @@ namespace AssetStudio { ReadTypeTree(m_Type, level + 1); } + + Logger.Verbose($"Type Tree Info: {m_Type}"); } private void TypeTreeBlobRead(TypeTree m_Type) { + Logger.Verbose($"Attempting to parse blob type tree..."); int numberOfNodes = reader.ReadInt32(); int stringBufferSize = reader.ReadInt32(); + Logger.Verbose($"Found {numberOfNodes} nodes and {stringBufferSize} strings"); for (int i = 0; i < numberOfNodes; i++) { var typeTreeNode = new TypeTreeNode(); @@ -364,6 +389,8 @@ namespace AssetStudio } } + Logger.Verbose($"Type Tree Info: {m_Type}"); + string ReadString(EndianBinaryReader stringBufferReader, uint value) { var isOffset = (value & 0x80000000) == 0; @@ -383,6 +410,7 @@ namespace AssetStudio public void AddObject(Object obj) { + Logger.Verbose($"Caching object with {obj.m_PathID} in file {fileName}..."); Objects.Add(obj); ObjectsDic.Add(obj.m_PathID, obj); } diff --git a/AssetStudio/SerializedFileHeader.cs b/AssetStudio/SerializedFileHeader.cs index 81f4f31..1da5b9e 100644 --- a/AssetStudio/SerializedFileHeader.cs +++ b/AssetStudio/SerializedFileHeader.cs @@ -13,5 +13,16 @@ namespace AssetStudio public long m_DataOffset; public byte m_Endianess; public byte[] m_Reserved; + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"MetadataSize: 0x{m_MetadataSize:X8} | "); + sb.Append($"FileSize: 0x{m_FileSize:X8} | "); + sb.Append($"Version: {m_Version} | "); + sb.Append($"DataOffset: 0x{m_DataOffset:X8} | "); + sb.Append($"Endianness: {(EndianType)m_Endianess}"); + return sb.ToString(); + } } } diff --git a/AssetStudio/SerializedType.cs b/AssetStudio/SerializedType.cs index 0a003e1..81d44c9 100644 --- a/AssetStudio/SerializedType.cs +++ b/AssetStudio/SerializedType.cs @@ -19,5 +19,16 @@ namespace AssetStudio public string m_AsmName; public bool Match(string hash) => Convert.ToHexString(m_OldTypeHash) == hash; + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"classID: {classID} | "); + sb.Append($"IsStrippedType: {m_IsStrippedType} | "); + sb.Append($"ScriptTypeIndex: {m_ScriptTypeIndex} | "); + sb.Append($"KlassName: {m_KlassName} | "); + sb.Append($"NameSpace: {m_NameSpace} | "); + sb.Append($"AsmName: {m_AsmName}"); + return sb.ToString(); + } } } diff --git a/AssetStudio/TypeTreeHelper.cs b/AssetStudio/TypeTreeHelper.cs index f79df45..b0e5210 100644 --- a/AssetStudio/TypeTreeHelper.cs +++ b/AssetStudio/TypeTreeHelper.cs @@ -186,6 +186,7 @@ namespace AssetStudio { var m_Node = m_Nodes[i]; var varTypeStr = m_Node.m_Type; + Logger.Verbose($"Reading {m_Node.m_Name} of type {varTypeStr}"); object value; var align = (m_Node.m_MetaFlag & 0x4000) != 0; switch (varTypeStr) diff --git a/AssetStudio/TypeTreeNode.cs b/AssetStudio/TypeTreeNode.cs index f5de3c0..7dbd785 100644 --- a/AssetStudio/TypeTreeNode.cs +++ b/AssetStudio/TypeTreeNode.cs @@ -28,5 +28,19 @@ namespace AssetStudio m_Level = level; m_MetaFlag = align ? 0x4000 : 0; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"Type: {m_Type} | "); + sb.Append($"Name: {m_Name} | "); + sb.Append($"ByteSize: 0x{m_ByteSize:X8} | "); + sb.Append($"Index: {m_Index} | "); + sb.Append($"TypeFlags: {m_TypeFlags} | "); + sb.Append($"Version: {m_Version} | "); + sb.Append($"TypeStrOffset: 0x{m_TypeStrOffset:X8} | "); + sb.Append($"NameStrOffset: 0x{m_NameStrOffset:X8}"); + return sb.ToString(); + } } } diff --git a/AssetStudio/WebFile.cs b/AssetStudio/WebFile.cs index ca61f85..9bce62c 100644 --- a/AssetStudio/WebFile.cs +++ b/AssetStudio/WebFile.cs @@ -13,6 +13,15 @@ namespace AssetStudio public int dataOffset; public int dataLength; public string path; + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"dataOffset: 0x{dataOffset:X8} | "); + sb.Append($"dataOffset: 0x{dataLength:X8} | "); + sb.Append($"path: {path}"); + return sb.ToString(); + } } public WebFile(EndianBinaryReader reader) @@ -21,15 +30,19 @@ namespace AssetStudio var signature = reader.ReadStringToNull(); var headLength = reader.ReadInt32(); var dataList = new List(); + Logger.Verbose($"Header size: 0x{headLength:X8}"); while (reader.BaseStream.Position < headLength) { var data = new WebData(); data.dataOffset = reader.ReadInt32(); data.dataLength = reader.ReadInt32(); var pathLength = reader.ReadInt32(); + Logger.Verbose($"Path length: {pathLength}"); data.path = Encoding.UTF8.GetString(reader.ReadBytes(pathLength)); + Logger.Verbose($"Web data Info: {data}"); dataList.Add(data); } + Logger.Verbose("Writing files to streams..."); fileList = new StreamFile[dataList.Count]; for (int i = 0; i < dataList.Count; i++) { diff --git a/AssetStudioCLI/AssetStudioCLI.csproj b/AssetStudioCLI/AssetStudioCLI.csproj index cfc04ba..4d51acd 100644 --- a/AssetStudioCLI/AssetStudioCLI.csproj +++ b/AssetStudioCLI/AssetStudioCLI.csproj @@ -3,9 +3,9 @@ Exe net6.0-windows;net7.0-windows Resources\as.ico - 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 ..\AssetStudioGUI\bin diff --git a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj index 895b132..ca0132a 100644 --- a/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj +++ b/AssetStudioFBXWrapper/AssetStudioFBXWrapper.csproj @@ -3,9 +3,9 @@ net6.0;net7.0 true - 0.90.00 - 0.90.00 - 0.90.00 + 0.90.10 + 0.90.10 + 0.90.10 Copyright © Perfare 2018-2022; Copyright © hozuki 2020 embedded diff --git a/AssetStudioFBXWrapper/FbxExporterContext.PInvoke.cs b/AssetStudioFBXWrapper/FbxExporterContext.PInvoke.cs index 3a606b8..28bef4e 100644 --- a/AssetStudioFBXWrapper/FbxExporterContext.PInvoke.cs +++ b/AssetStudioFBXWrapper/FbxExporterContext.PInvoke.cs @@ -27,9 +27,10 @@ namespace AssetStudio.FbxInterop [DllImport(FbxDll.DllName)] private static extern IntPtr AsFbxGetSceneRootNode(IntPtr context); - private static IntPtr AsFbxExportSingleFrame(IntPtr context, IntPtr parentNode, string framePath, string frameName, in Vector3 localPosition, in Vector3 localRotation, in Vector3 localScale) + private static IntPtr AsFbxExportSingleFrame(IntPtr context, IntPtr parentNode, string framePath, string frameName, in Vector3 localPosition, in Quaternion localRotation, in Vector3 localScale) { - return AsFbxExportSingleFrame(context, parentNode, framePath, frameName, localPosition.X, localPosition.Y, localPosition.Z, localRotation.X, localRotation.Y, localRotation.Z, localScale.X, localScale.Y, localScale.Z); + var localRotationEuler = Fbx.QuaternionToEuler(localRotation); + return AsFbxExportSingleFrame(context, parentNode, framePath, frameName, localPosition.X, localPosition.Y, localPosition.Z, localRotationEuler.X, localRotationEuler.Y, localRotationEuler.Z, localScale.X, localScale.Y, localScale.Z); } [DllImport(FbxDll.DllName)] diff --git a/AssetStudioFBXWrapper/FbxExporterContext.cs b/AssetStudioFBXWrapper/FbxExporterContext.cs index a37e260..12a33c6 100644 --- a/AssetStudioFBXWrapper/FbxExporterContext.cs +++ b/AssetStudioFBXWrapper/FbxExporterContext.cs @@ -16,6 +16,7 @@ namespace AssetStudio.FbxInterop public FbxExporterContext() { + Fbx.QuaternionToEuler(Quaternion.Zero); // workaround to init dll _pContext = AsFbxCreateContext(); _frameToNode = new Dictionary(); _createdMaterials = new List>(); @@ -554,7 +555,7 @@ namespace AssetStudio.FbxInterop foreach (var rotation in track.Rotations) { - var value = rotation.value; + var value = Fbx.QuaternionToEuler(rotation.value); AsFbxAnimAddRotationKey(pAnimContext, rotation.time, value.X, value.Y, value.Z); } diff --git a/AssetStudioGUI/App.config b/AssetStudioGUI/App.config index a6385df..c3e24f8 100644 --- a/AssetStudioGUI/App.config +++ b/AssetStudioGUI/App.config @@ -115,6 +115,15 @@ + + False + + + True + + + False + \ No newline at end of file diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 67cd5cd..713ccd1 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -5,9 +5,9 @@ net6.0-windows;net7.0-windows true Resources\as.ico - 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 @@ -69,7 +69,7 @@ - + Libraries\OpenTK.WinForms.dll diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs index a2cf5ea..df67e24 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs @@ -55,6 +55,8 @@ namespace AssetStudioGUI toolStripSeparator12 = new System.Windows.Forms.ToolStripSeparator(); toolStripMenuItem14 = new System.Windows.Forms.ToolStripMenuItem(); specifyUnityVersion = new System.Windows.Forms.ToolStripTextBox(); + specifyUnityCNKey = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator13 = new System.Windows.Forms.ToolStripSeparator(); toolStripMenuItem18 = new System.Windows.Forms.ToolStripMenuItem(); specifyGame = new System.Windows.Forms.ToolStripComboBox(); toolStripMenuItem19 = new System.Windows.Forms.ToolStripMenuItem(); @@ -101,6 +103,8 @@ namespace AssetStudioGUI exportClassStructuresMenuItem = new System.Windows.Forms.ToolStripMenuItem(); enableConsole = new System.Windows.Forms.ToolStripMenuItem(); clearConsoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + enableFileLogging = new System.Windows.Forms.ToolStripMenuItem(); + enableVerbose = new System.Windows.Forms.ToolStripMenuItem(); miscToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); MapNameComboBox = new System.Windows.Forms.ToolStripComboBox(); buildMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -167,8 +171,6 @@ namespace AssetStudioGUI exportAnimatorwithselectedAnimationClipMenuItem = new System.Windows.Forms.ToolStripMenuItem(); goToSceneHierarchyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); showOriginalFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - specifyUnityCNKey = new System.Windows.Forms.ToolStripMenuItem(); - toolStripSeparator13 = new System.Windows.Forms.ToolStripSeparator(); menuStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); splitContainer1.Panel1.SuspendLayout(); @@ -360,6 +362,18 @@ namespace AssetStudioGUI specifyUnityVersion.Name = "specifyUnityVersion"; specifyUnityVersion.Size = new System.Drawing.Size(100, 23); // + // specifyUnityCNKey + // + specifyUnityCNKey.Name = "specifyUnityCNKey"; + specifyUnityCNKey.Size = new System.Drawing.Size(225, 22); + specifyUnityCNKey.Text = "Specify UnityCN Key"; + specifyUnityCNKey.Click += specifyUnityCNKey_Click; + // + // toolStripSeparator13 + // + toolStripSeparator13.Name = "toolStripSeparator13"; + toolStripSeparator13.Size = new System.Drawing.Size(222, 6); + // // toolStripMenuItem18 // toolStripMenuItem18.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { specifyGame }); @@ -642,7 +656,7 @@ namespace AssetStudioGUI // // debugMenuItem // - debugMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripMenuItem15, exportClassStructuresMenuItem, enableConsole, clearConsoleToolStripMenuItem }); + debugMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripMenuItem15, exportClassStructuresMenuItem, enableConsole, clearConsoleToolStripMenuItem, enableFileLogging, enableVerbose }); debugMenuItem.Name = "debugMenuItem"; debugMenuItem.Size = new System.Drawing.Size(54, 20); debugMenuItem.Text = "Debug"; @@ -681,6 +695,24 @@ namespace AssetStudioGUI clearConsoleToolStripMenuItem.Text = "Clear Console"; clearConsoleToolStripMenuItem.Click += clearConsoleToolStripMenuItem_Click; // + // enableFileLogging + // + enableFileLogging.Checked = true; + enableFileLogging.CheckOnClick = true; + enableFileLogging.CheckState = System.Windows.Forms.CheckState.Checked; + enableFileLogging.Name = "enableFileLogging"; + enableFileLogging.Size = new System.Drawing.Size(191, 22); + enableFileLogging.Text = "Enable File Logging"; + enableFileLogging.CheckedChanged += enableFileLogging_CheckedChanged; + // + // enableVerbose + // + enableVerbose.CheckOnClick = true; + enableVerbose.Name = "enableVerbose"; + enableVerbose.Size = new System.Drawing.Size(191, 22); + enableVerbose.Text = "Enable Verbose"; + enableVerbose.CheckedChanged += enableVerbose_Click; + // // miscToolStripMenuItem // miscToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { MapNameComboBox, buildMapToolStripMenuItem, buildBothToolStripMenuItem, clearMapToolStripMenuItem, toolStripSeparator7, assetMapNameTextBox, buildAssetMapToolStripMenuItem, assetMapTypeComboBox, toolStripSeparator8, loadAIToolStripMenuItem, assetBrowserToolStripMenuItem }); @@ -843,16 +875,14 @@ namespace AssetStudioGUI // treeSearch // treeSearch.Dock = System.Windows.Forms.DockStyle.Top; - treeSearch.ForeColor = System.Drawing.SystemColors.GrayText; + treeSearch.ForeColor = System.Drawing.SystemColors.WindowText; treeSearch.Location = new System.Drawing.Point(0, 0); treeSearch.Name = "treeSearch"; treeSearch.Size = new System.Drawing.Size(472, 23); treeSearch.TabIndex = 0; - treeSearch.Text = " Search "; + treeSearch.PlaceholderText = "Search (with Ctrl to check result, with Shift for all)"; treeSearch.TextChanged += treeSearch_TextChanged; - treeSearch.Enter += treeSearch_Enter; treeSearch.KeyDown += treeSearch_KeyDown; - treeSearch.Leave += treeSearch_Leave; // // tabPage2 // @@ -910,15 +940,13 @@ namespace AssetStudioGUI // listSearch // listSearch.Dock = System.Windows.Forms.DockStyle.Top; - listSearch.ForeColor = System.Drawing.SystemColors.GrayText; + listSearch.ForeColor = System.Drawing.SystemColors.WindowText; listSearch.Location = new System.Drawing.Point(0, 0); listSearch.Name = "listSearch"; listSearch.Size = new System.Drawing.Size(472, 23); listSearch.TabIndex = 0; - listSearch.Text = " Filter "; - listSearch.TextChanged += ListSearchTextChanged; - listSearch.Enter += listSearch_Enter; - listSearch.Leave += listSearch_Leave; + listSearch.PlaceholderText = "Search"; + listSearch.KeyPress += listSearch_KeyPress; // // tabPage3 // @@ -1166,7 +1194,7 @@ namespace AssetStudioGUI // glControl // glControl.API = OpenTK.Windowing.Common.ContextAPI.OpenGL; - glControl.APIVersion = new Version(4, 6, 0, 0); + glControl.APIVersion = new Version(4, 5, 0, 0); glControl.BackColor = System.Drawing.SystemColors.ControlDarkDark; glControl.Dock = System.Windows.Forms.DockStyle.Fill; glControl.Flags = OpenTK.Windowing.Common.ContextFlags.Default; @@ -1308,18 +1336,6 @@ namespace AssetStudioGUI showOriginalFileToolStripMenuItem.Visible = false; showOriginalFileToolStripMenuItem.Click += showOriginalFileToolStripMenuItem_Click; // - // specifyUnityCNKey - // - specifyUnityCNKey.Name = "specifyUnityCNKey"; - specifyUnityCNKey.Size = new System.Drawing.Size(225, 22); - specifyUnityCNKey.Text = "Specify UnityCN Key"; - specifyUnityCNKey.Click += specifyUnityCNKey_Click; - // - // toolStripSeparator13 - // - toolStripSeparator13.Name = "toolStripSeparator13"; - toolStripSeparator13.Size = new System.Drawing.Size(222, 6); - // // AssetStudioGUIForm // AllowDrop = true; @@ -1506,6 +1522,8 @@ namespace AssetStudioGUI private System.Windows.Forms.ToolStripSeparator toolStripSeparator12; private System.Windows.Forms.ToolStripMenuItem specifyUnityCNKey; private System.Windows.Forms.ToolStripSeparator toolStripSeparator13; + private System.Windows.Forms.ToolStripMenuItem enableFileLogging; + private System.Windows.Forms.ToolStripMenuItem enableVerbose; } } diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 6d7096f..1c4b6dd 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -23,6 +23,7 @@ using Matrix4 = OpenTK.Mathematics.Matrix4; using OpenTK.Graphics; using OpenTK.Mathematics; using Newtonsoft.Json.Converters; +using System.Text.RegularExpressions; namespace AssetStudioGUI { @@ -50,14 +51,14 @@ namespace AssetStudioGUI private bool glControlLoaded; private int mdx, mdy; private bool lmdown, rmdown; - private ProgramHandle pgmID, pgmColorID, pgmBlackID; + private int pgmID, pgmColorID, pgmBlackID; private int attributeVertexPosition; private int attributeNormalDirection; private int attributeVertexColor; private int uniformModelMatrix; private int uniformViewMatrix; private int uniformProjMatrix; - private VertexArrayHandle vao; + private int vao; private Vector3[] vertexData; private Vector3[] normalData; private Vector3[] normal2Data; @@ -75,10 +76,6 @@ namespace AssetStudioGUI private int sortColumn = -1; private bool reverseSort; - //asset list filter - private System.Timers.Timer delayTimer; - private bool enableFiltering; - //tree search private int nextGObject; private List treeSrcResults = new List(); @@ -93,8 +90,6 @@ namespace AssetStudioGUI Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); InitializeComponent(); Text = $"Studio v{Application.ProductVersion}"; - delayTimer = new System.Timers.Timer(800); - delayTimer.Elapsed += new ElapsedEventHandler(delayTimer_Elapsed); InitializeExportOptions(); InitializeProgressBar(); InitializeLogger(); @@ -105,6 +100,8 @@ namespace AssetStudioGUI private void InitializeExportOptions() { enableConsole.Checked = Properties.Settings.Default.enableConsole; + enableFileLogging.Checked = Properties.Settings.Default.enableFileLogging; + enableVerbose.Checked = Properties.Settings.Default.enableVerbose; displayAll.Checked = Properties.Settings.Default.displayAll; displayInfo.Checked = Properties.Settings.Default.displayInfo; enablePreview.Checked = Properties.Settings.Default.enablePreview; @@ -119,10 +116,13 @@ namespace AssetStudioGUI AssetsHelper.Minimal = Properties.Settings.Default.minimalAssetMap; Renderer.Parsable = !Properties.Settings.Default.disableRenderer; Shader.Parsable = !Properties.Settings.Default.disableShader; + AnimationClip.Parsable = !Properties.Settings.Default.disableAnimationClip; } private void InitializeLogger() { + Logger.LogVerbose = enableVerbose.Checked; + Logger.FileLogging = enableFileLogging.Checked; logger = new GUILogger(StatusStripUpdate); ConsoleHelper.AllocConsole(); ConsoleHelper.SetConsoleTitle("Debug Console"); @@ -559,24 +559,6 @@ namespace AssetStudioGUI } } - private void treeSearch_Enter(object sender, EventArgs e) - { - if (treeSearch.Text == " Search ") - { - treeSearch.Text = ""; - treeSearch.ForeColor = SystemColors.WindowText; - } - } - - private void treeSearch_Leave(object sender, EventArgs e) - { - if (treeSearch.Text == "") - { - treeSearch.Text = " Search "; - treeSearch.ForeColor = SystemColors.GrayText; - } - } - private void treeSearch_TextChanged(object sender, EventArgs e) { treeSrcResults.Clear(); @@ -585,38 +567,52 @@ namespace AssetStudioGUI private void treeSearch_KeyDown(object sender, KeyEventArgs e) { - if (e.KeyCode == Keys.Enter) + if (e.KeyCode == Keys.Enter && !string.IsNullOrEmpty(treeSearch.Text)) { if (treeSrcResults.Count == 0) { + var regex = new Regex(treeSearch.Text, RegexOptions.IgnoreCase); foreach (TreeNode node in sceneTreeView.Nodes) { - TreeNodeSearch(node); + TreeNodeSearch(regex, node); } } if (treeSrcResults.Count > 0) { + if (e.Shift) + { + foreach(var node in treeSrcResults) + { + node.EnsureVisible(); + node.Checked = e.Control; + } + sceneTreeView.SelectedNode = treeSrcResults[0]; + } + else + { if (nextGObject >= treeSrcResults.Count) { nextGObject = 0; } treeSrcResults[nextGObject].EnsureVisible(); + treeSrcResults[nextGObject].Checked = e.Control; sceneTreeView.SelectedNode = treeSrcResults[nextGObject]; nextGObject++; } } } + } - private void TreeNodeSearch(TreeNode treeNode) + private void TreeNodeSearch(Regex regex, TreeNode treeNode) { - if (treeNode.Text.IndexOf(treeSearch.Text, StringComparison.OrdinalIgnoreCase) >= 0) + if (regex.IsMatch(treeNode.Text)) { treeSrcResults.Add(treeNode); } foreach (TreeNode node in treeNode.Nodes) { - TreeNodeSearch(node); + TreeNodeSearch(regex, node); } } @@ -628,48 +624,14 @@ namespace AssetStudioGUI } } - private void listSearch_Enter(object sender, EventArgs e) + private void listSearch_KeyPress(object sender, KeyPressEventArgs e) { - if (listSearch.Text == " Filter ") + if (e.KeyChar == (char)Keys.Enter) { - listSearch.Text = ""; - listSearch.ForeColor = SystemColors.WindowText; - enableFiltering = true; + Invoke(new Action(FilterAssetList)); } } - private void listSearch_Leave(object sender, EventArgs e) - { - if (listSearch.Text == "") - { - enableFiltering = false; - listSearch.Text = " Filter "; - listSearch.ForeColor = SystemColors.GrayText; - } - } - - private void ListSearchTextChanged(object sender, EventArgs e) - { - if (enableFiltering) - { - if (delayTimer.Enabled) - { - delayTimer.Stop(); - delayTimer.Start(); - } - else - { - delayTimer.Start(); - } - } - } - - private void delayTimer_Elapsed(object sender, ElapsedEventArgs e) - { - delayTimer.Stop(); - Invoke(new Action(FilterAssetList)); - } - private void assetListView_ColumnClick(object sender, ColumnClickEventArgs e) { if (sortColumn != e.Column) @@ -1429,8 +1391,7 @@ namespace AssetStudioGUI lastSelectedItem = null; sortColumn = -1; reverseSort = false; - enableFiltering = false; - listSearch.Text = " Filter "; + listSearch.Text = string.Empty; var count = filterTypeToolStripMenuItem.DropDownItems.Count; for (var i = 1; i < count; i++) @@ -1788,12 +1749,13 @@ namespace AssetStudioGUI } } } - if (listSearch.Text != " Filter ") + if (!string.IsNullOrEmpty(listSearch.Text)) { + var regex = new Regex(listSearch.Text, RegexOptions.IgnoreCase); visibleAssets = visibleAssets.FindAll( - x => x.Text.IndexOf(listSearch.Text, StringComparison.OrdinalIgnoreCase) >= 0 || - x.SubItems[1].Text.IndexOf(listSearch.Text, StringComparison.OrdinalIgnoreCase) >= 0 || - x.SubItems[3].Text.IndexOf(listSearch.Text, StringComparison.OrdinalIgnoreCase) >= 0); + x => regex.IsMatch(x.Text) || + regex.IsMatch(x.SubItems[1].Text) || + regex.IsMatch(x.SubItems[3].Text)); } assetListView.VirtualListSize = visibleAssets.Count; assetListView.EndUpdate(); @@ -2238,6 +2200,22 @@ namespace AssetStudioGUI } } + private void enableFileLogging_CheckedChanged(object sender, EventArgs e) + { + Properties.Settings.Default.enableFileLogging = enableFileLogging.Checked; + Properties.Settings.Default.Save(); + + Logger.FileLogging = enableFileLogging.Checked; + } + + private void enableVerbose_Click(object sender, EventArgs e) + { + Properties.Settings.Default.enableVerbose = enableVerbose.Checked; + Properties.Settings.Default.Save(); + + Logger.LogVerbose = enableVerbose.Checked; + } + private void abortStripMenuItem_Click(object sender, EventArgs e) { Logger.Info("Aborting...."); @@ -2607,10 +2585,10 @@ namespace AssetStudioGUI private void InitOpenTK() { ChangeGLSize(glControl.Size); - GL.ClearColor(Color4.Cadetblue); + GL.ClearColor(System.Drawing.Color.CadetBlue); pgmID = GL.CreateProgram(); - LoadShader("vs", ShaderType.VertexShader, pgmID, out ShaderHandle vsID); - LoadShader("fs", ShaderType.FragmentShader, pgmID, out ShaderHandle fsID); + LoadShader("vs", ShaderType.VertexShader, pgmID, out int vsID); + LoadShader("fs", ShaderType.FragmentShader, pgmID, out int fsID); GL.LinkProgram(pgmID); pgmColorID = GL.CreateProgram(); @@ -2631,7 +2609,7 @@ namespace AssetStudioGUI uniformProjMatrix = GL.GetUniformLocation(pgmID, "projMatrix"); } - private static void LoadShader(string filename, ShaderType type, ProgramHandle program, out ShaderHandle address) + private static void LoadShader(string filename, ShaderType type, int program, out int address) { address = GL.CreateShader(type); var str = (string)Properties.Resources.ResourceManager.GetObject(filename); @@ -2641,47 +2619,50 @@ namespace AssetStudioGUI GL.DeleteShader(address); } - private static void CreateVBO(out BufferHandle vboAddress, Vector3[] data, int address) + private static void CreateVBO(out int vboAddress, Vector3[] data, int address) { - GL.CreateBuffer(out vboAddress); - GL.BindBuffer(BufferTargetARB.ArrayBuffer, vboAddress); - GL.BufferData(BufferTargetARB.ArrayBuffer, + GL.GenBuffers(1, out vboAddress); + GL.BindBuffer(BufferTarget.ArrayBuffer, vboAddress); + GL.BufferData(BufferTarget.ArrayBuffer, + (IntPtr)(data.Length * Vector3.SizeInBytes), data, - BufferUsageARB.StaticDraw); - GL.VertexAttribPointer((uint)address, 3, VertexAttribPointerType.Float, false, 0, 0); - GL.EnableVertexAttribArray((uint)address); + BufferUsageHint.StaticDraw); + GL.VertexAttribPointer(address, 3, VertexAttribPointerType.Float, false, 0, 0); + GL.EnableVertexAttribArray(address); } - private static void CreateVBO(out BufferHandle vboAddress, Vector4[] data, int address) + private static void CreateVBO(out int vboAddress, Vector4[] data, int address) { - GL.CreateBuffer(out vboAddress); - GL.BindBuffer(BufferTargetARB.ArrayBuffer, vboAddress); - GL.BufferData(BufferTargetARB.ArrayBuffer, + GL.GenBuffers(1, out vboAddress); + GL.BindBuffer(BufferTarget.ArrayBuffer, vboAddress); + GL.BufferData(BufferTarget.ArrayBuffer, + (IntPtr)(data.Length * Vector4.SizeInBytes), data, - BufferUsageARB.StaticDraw); - GL.VertexAttribPointer((uint)address, 4, VertexAttribPointerType.Float, false, 0, 0); - GL.EnableVertexAttribArray((uint)address); + BufferUsageHint.StaticDraw); + GL.VertexAttribPointer(address, 4, VertexAttribPointerType.Float, false, 0, 0); + GL.EnableVertexAttribArray(address); } - private static void CreateVBO(out BufferHandle vboAddress, Matrix4 data, int address) + private static void CreateVBO(out int vboAddress, Matrix4 data, int address) { - GL.CreateBuffer(out vboAddress); - GL.UniformMatrix4f(address, false, in data); + GL.GenBuffers(1, out vboAddress); + GL.UniformMatrix4(address, false, ref data); } - private static void CreateEBO(out BufferHandle address, int[] data) + private static void CreateEBO(out int address, int[] data) { - GL.CreateBuffer(out address); - GL.BindBuffer(BufferTargetARB.ElementArrayBuffer, address); - GL.BufferData(BufferTargetARB.ElementArrayBuffer, + GL.GenBuffers(1, out address); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, address); + GL.BufferData(BufferTarget.ElementArrayBuffer, + (IntPtr)(data.Length * sizeof(int)), data, - BufferUsageARB.StaticDraw); + BufferUsageHint.StaticDraw); } private void CreateVAO() { GL.DeleteVertexArray(vao); - GL.CreateVertexArray(out vao); + GL.GenVertexArrays(1, out vao); GL.BindVertexArray(vao); CreateVBO(out var vboPositions, vertexData, attributeVertexPosition); if (normalMode == 0) @@ -2698,8 +2679,8 @@ namespace AssetStudioGUI CreateVBO(out var vboViewMatrix, viewMatrixData, uniformViewMatrix); CreateVBO(out var vboProjMatrix, projMatrixData, uniformProjMatrix); CreateEBO(out var eboElements, indiceData); - GL.BindBuffer(BufferTargetARB.ArrayBuffer, BufferHandle.Zero); - GL.BindVertexArray(VertexArrayHandle.Zero); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + GL.BindVertexArray(0); } private void ChangeGLSize(Size size) @@ -2734,10 +2715,10 @@ namespace AssetStudioGUI if (wireFrameMode == 0 || wireFrameMode == 2) { GL.UseProgram(shadeMode == 0 ? pgmID : pgmColorID); - GL.UniformMatrix4f(uniformModelMatrix, false, in modelMatrixData); - GL.UniformMatrix4f(uniformViewMatrix, false, in viewMatrixData); - GL.UniformMatrix4f(uniformProjMatrix, false, in projMatrixData); - GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill); + GL.UniformMatrix4(uniformModelMatrix, false, ref modelMatrixData); + GL.UniformMatrix4(uniformViewMatrix, false, ref viewMatrixData); + GL.UniformMatrix4(uniformProjMatrix, false, ref projMatrixData); + GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); GL.DrawElements(PrimitiveType.Triangles, indiceData.Length, DrawElementsType.UnsignedInt, 0); } //Wireframe @@ -2746,14 +2727,14 @@ namespace AssetStudioGUI GL.Enable(EnableCap.PolygonOffsetLine); GL.PolygonOffset(-1, -1); GL.UseProgram(pgmBlackID); - GL.UniformMatrix4f(uniformModelMatrix, false, in modelMatrixData); - GL.UniformMatrix4f(uniformViewMatrix, false, in viewMatrixData); - GL.UniformMatrix4f(uniformProjMatrix, false, in projMatrixData); - GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Line); + GL.UniformMatrix4(uniformModelMatrix, false, ref modelMatrixData); + GL.UniformMatrix4(uniformViewMatrix, false, ref viewMatrixData); + GL.UniformMatrix4(uniformProjMatrix, false, ref projMatrixData); + GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); GL.DrawElements(PrimitiveType.Triangles, indiceData.Length, DrawElementsType.UnsignedInt, 0); GL.Disable(EnableCap.PolygonOffsetLine); } - GL.BindVertexArray(VertexArrayHandle.Zero); + GL.BindVertexArray(0); GL.Flush(); glControl.SwapBuffers(); } diff --git a/AssetStudioGUI/AssetStudioGUIForm.resx b/AssetStudioGUI/AssetStudioGUIForm.resx index 500ca4f..de2aa35 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.resx +++ b/AssetStudioGUI/AssetStudioGUIForm.resx @@ -120,6 +120,9 @@ 312, 17 + + 432, 17 + abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWYZ 1234567890.:,;'\"(!?)+-*/= @@ -138,9 +141,6 @@ The quick brown fox jumps over the lazy dog. 1234567890 The quick brown fox jumps over the lazy dog. 1234567890 - - 432, 17 - 553, 17 diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index 82eca9f..51d5340 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -35,6 +35,8 @@ namespace AssetStudioGUI OKbutton = new System.Windows.Forms.Button(); Cancel = new System.Windows.Forms.Button(); groupBox1 = new System.Windows.Forms.GroupBox(); + assetGroupOptions = new System.Windows.Forms.ComboBox(); + label7 = new System.Windows.Forms.Label(); openAfterExport = new System.Windows.Forms.CheckBox(); restoreExtensionName = new System.Windows.Forms.CheckBox(); assetGroupOptions = new System.Windows.Forms.ComboBox(); @@ -71,11 +73,10 @@ namespace AssetStudioGUI key = new System.Windows.Forms.NumericUpDown(); keyToolTip = new System.Windows.Forms.ToolTip(components); groupBox4 = new System.Windows.Forms.GroupBox(); + disableAnimationClip = new System.Windows.Forms.CheckBox(); minimalAssetMap = new System.Windows.Forms.CheckBox(); disableShader = new System.Windows.Forms.CheckBox(); disableRenderer = new System.Windows.Forms.CheckBox(); - resolveToolTip = new System.Windows.Forms.ToolTip(components); - skipToolTip = new System.Windows.Forms.ToolTip(components); groupBox1.SuspendLayout(); panel1.SuspendLayout(); groupBox2.SuspendLayout(); @@ -112,6 +113,8 @@ namespace AssetStudioGUI // groupBox1 // groupBox1.AutoSize = true; + groupBox1.Controls.Add(assetGroupOptions); + groupBox1.Controls.Add(label7); groupBox1.Controls.Add(openAfterExport); groupBox1.Controls.Add(restoreExtensionName); groupBox1.Controls.Add(assetGroupOptions); @@ -123,17 +126,38 @@ namespace AssetStudioGUI groupBox1.Margin = new System.Windows.Forms.Padding(4); groupBox1.Name = "groupBox1"; groupBox1.Padding = new System.Windows.Forms.Padding(4); - groupBox1.Size = new System.Drawing.Size(271, 243); + groupBox1.Size = new System.Drawing.Size(271, 273); groupBox1.TabIndex = 9; groupBox1.TabStop = false; groupBox1.Text = "Export"; // + // assetGroupOptions + // + assetGroupOptions.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + assetGroupOptions.FormattingEnabled = true; + assetGroupOptions.Items.AddRange(new object[] { "type name", "container path", "source file name", "do not group" }); + assetGroupOptions.Location = new System.Drawing.Point(7, 83); + assetGroupOptions.Margin = new System.Windows.Forms.Padding(4); + assetGroupOptions.Name = "assetGroupOptions"; + assetGroupOptions.Size = new System.Drawing.Size(173, 23); + assetGroupOptions.TabIndex = 12; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new System.Drawing.Point(7, 64); + label7.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(140, 15); + label7.TabIndex = 11; + label7.Text = "Group exported assets by"; + // // openAfterExport // openAfterExport.AutoSize = true; openAfterExport.Checked = true; openAfterExport.CheckState = System.Windows.Forms.CheckState.Checked; - openAfterExport.Location = new System.Drawing.Point(7, 200); + openAfterExport.Location = new System.Drawing.Point(8, 230); openAfterExport.Margin = new System.Windows.Forms.Padding(4); openAfterExport.Name = "openAfterExport"; openAfterExport.Size = new System.Drawing.Size(153, 19); @@ -146,7 +170,7 @@ namespace AssetStudioGUI restoreExtensionName.AutoSize = true; restoreExtensionName.Checked = true; restoreExtensionName.CheckState = System.Windows.Forms.CheckState.Checked; - restoreExtensionName.Location = new System.Drawing.Point(7, 72); + restoreExtensionName.Location = new System.Drawing.Point(7, 109); restoreExtensionName.Margin = new System.Windows.Forms.Padding(4); restoreExtensionName.Name = "restoreExtensionName"; restoreExtensionName.Size = new System.Drawing.Size(204, 19); @@ -171,7 +195,7 @@ namespace AssetStudioGUI label6.Location = new System.Drawing.Point(7, 21); label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); label6.Name = "label6"; - label6.Size = new System.Drawing.Size(140, 15); + label6.Size = new System.Drawing.Size(80, 15); label6.TabIndex = 7; label6.Text = "Group exported assets by"; // @@ -180,7 +204,7 @@ namespace AssetStudioGUI convertAudio.AutoSize = true; convertAudio.Checked = true; convertAudio.CheckState = System.Windows.Forms.CheckState.Checked; - convertAudio.Location = new System.Drawing.Point(7, 172); + convertAudio.Location = new System.Drawing.Point(7, 204); convertAudio.Margin = new System.Windows.Forms.Padding(4); convertAudio.Name = "convertAudio"; convertAudio.Size = new System.Drawing.Size(200, 19); @@ -194,7 +218,7 @@ namespace AssetStudioGUI panel1.Controls.Add(tojpg); panel1.Controls.Add(topng); panel1.Controls.Add(tobmp); - panel1.Location = new System.Drawing.Point(23, 128); + panel1.Location = new System.Drawing.Point(23, 164); panel1.Margin = new System.Windows.Forms.Padding(4); panel1.Name = "panel1"; panel1.Size = new System.Drawing.Size(236, 38); @@ -251,7 +275,7 @@ namespace AssetStudioGUI converttexture.AutoSize = true; converttexture.Checked = true; converttexture.CheckState = System.Windows.Forms.CheckState.Checked; - converttexture.Location = new System.Drawing.Point(7, 100); + converttexture.Location = new System.Drawing.Point(7, 136); converttexture.Margin = new System.Windows.Forms.Padding(4); converttexture.Name = "converttexture"; converttexture.Size = new System.Drawing.Size(123, 19); @@ -536,20 +560,32 @@ namespace AssetStudioGUI // groupBox4 // groupBox4.AutoSize = true; + groupBox4.Controls.Add(disableAnimationClip); groupBox4.Controls.Add(minimalAssetMap); groupBox4.Controls.Add(disableShader); groupBox4.Controls.Add(disableRenderer); groupBox4.Controls.Add(key); groupBox4.Controls.Add(encrypted); - groupBox4.Location = new System.Drawing.Point(13, 258); + groupBox4.Location = new System.Drawing.Point(13, 287); groupBox4.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); groupBox4.Name = "groupBox4"; groupBox4.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - groupBox4.Size = new System.Drawing.Size(272, 187); + groupBox4.Size = new System.Drawing.Size(272, 146); groupBox4.TabIndex = 13; groupBox4.TabStop = false; groupBox4.Text = "Options"; // + // disableAnimationClip + // + disableAnimationClip.AutoSize = true; + disableAnimationClip.Location = new System.Drawing.Point(119, 72); + disableAnimationClip.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + disableAnimationClip.Name = "disableAnimationClip"; + disableAnimationClip.Size = new System.Drawing.Size(144, 19); + disableAnimationClip.TabIndex = 18; + disableAnimationClip.Text = "Disable AnimationClip"; + disableAnimationClip.UseVisualStyleBackColor = true; + // // minimalAssetMap // minimalAssetMap.AutoSize = true; @@ -569,7 +605,6 @@ namespace AssetStudioGUI disableShader.Size = new System.Drawing.Size(103, 19); disableShader.TabIndex = 16; disableShader.Text = "Disable Shader"; - skipToolTip.SetToolTip(disableShader, "Skips the container recovery step.\nImproves loading when dealing with a large number of files."); disableShader.UseVisualStyleBackColor = true; // // disableRenderer @@ -581,7 +616,6 @@ namespace AssetStudioGUI disableRenderer.Size = new System.Drawing.Size(114, 19); disableRenderer.TabIndex = 15; disableRenderer.Text = "Disable Renderer"; - skipToolTip.SetToolTip(disableRenderer, "Skips the container recovery step.\nImproves loading when dealing with a large number of files."); disableRenderer.UseVisualStyleBackColor = true; // // ExportOptions @@ -661,10 +695,11 @@ namespace AssetStudioGUI private System.Windows.Forms.ToolTip keyToolTip; private System.Windows.Forms.CheckBox exportUV0UV1; private System.Windows.Forms.GroupBox groupBox4; - private System.Windows.Forms.ToolTip resolveToolTip; - private System.Windows.Forms.ToolTip skipToolTip; private System.Windows.Forms.CheckBox disableShader; private System.Windows.Forms.CheckBox disableRenderer; private System.Windows.Forms.CheckBox minimalAssetMap; + private System.Windows.Forms.ComboBox assetGroupOptions; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.CheckBox disableAnimationClip; } } \ No newline at end of file diff --git a/AssetStudioGUI/ExportOptions.cs b/AssetStudioGUI/ExportOptions.cs index 0a54d4f..a23c5d5 100644 --- a/AssetStudioGUI/ExportOptions.cs +++ b/AssetStudioGUI/ExportOptions.cs @@ -44,6 +44,7 @@ namespace AssetStudioGUI key.Value = Properties.Settings.Default.key; disableRenderer.Checked = Properties.Settings.Default.disableRenderer; disableShader.Checked = Properties.Settings.Default.disableShader; + disableAnimationClip.Checked = Properties.Settings.Default.disableAnimationClip; minimalAssetMap.Checked = Properties.Settings.Default.minimalAssetMap; } @@ -80,6 +81,7 @@ namespace AssetStudioGUI Properties.Settings.Default.key = (byte)key.Value; Properties.Settings.Default.disableRenderer = disableRenderer.Checked; Properties.Settings.Default.disableShader = disableShader.Checked; + Properties.Settings.Default.disableAnimationClip = disableAnimationClip.Checked; Properties.Settings.Default.minimalAssetMap = minimalAssetMap.Checked; Properties.Settings.Default.Save(); MiHoYoBinData.Key = (byte)key.Value; @@ -87,6 +89,7 @@ namespace AssetStudioGUI AssetsHelper.Minimal = Properties.Settings.Default.minimalAssetMap; Renderer.Parsable = !Properties.Settings.Default.disableRenderer; Shader.Parsable = !Properties.Settings.Default.disableShader; + AnimationClip.Parsable = !Properties.Settings.Default.disableAnimationClip; DialogResult = DialogResult.OK; Close(); } diff --git a/AssetStudioGUI/ExportOptions.resx b/AssetStudioGUI/ExportOptions.resx index ea96e60..a0d6ca8 100644 --- a/AssetStudioGUI/ExportOptions.resx +++ b/AssetStudioGUI/ExportOptions.resx @@ -120,24 +120,9 @@ 17, 17 - - 17, 17 - 162, 17 - - 162, 17 - - - 404, 17 - - - 273, 17 - - - 404, 17 - 57 diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index f0f1c59..2c1ac5e 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -465,5 +465,41 @@ namespace AssetStudioGUI.Properties { this["selectedCABMapName"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool disableAnimationClip { + get { + return ((bool)(this["disableAnimationClip"])); + } + set { + this["disableAnimationClip"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool enableFileLogging { + get { + return ((bool)(this["enableFileLogging"])); + } + set { + this["enableFileLogging"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool enableVerbose { + get { + return ((bool)(this["enableVerbose"])); + } + set { + this["enableVerbose"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index 40ac230..29bd7c6 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -113,5 +113,14 @@ + + False + + + True + + + False + \ No newline at end of file diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index 257bf91..85d1436 100644 --- a/AssetStudioUtility/AssetStudioUtility.csproj +++ b/AssetStudioUtility/AssetStudioUtility.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 2018-2022 embedded true @@ -13,7 +13,7 @@ - + diff --git a/AssetStudioUtility/ModelConverter.cs b/AssetStudioUtility/ModelConverter.cs index 7acf0db..31e7082 100644 --- a/AssetStudioUtility/ModelConverter.cs +++ b/AssetStudioUtility/ModelConverter.cs @@ -244,7 +244,7 @@ namespace AssetStudio private static void SetFrame(ImportedFrame frame, Vector3 t, Quaternion q, Vector3 s) { frame.LocalPosition = new Vector3(-t.X, t.Y, t.Z); - frame.LocalRotation = Fbx.QuaternionToEuler(new Quaternion(q.X, -q.Y, -q.Z, q.W)); + frame.LocalRotation = new Quaternion(q.X, -q.Y, -q.Z, q.W); frame.LocalScale = s; } @@ -818,8 +818,8 @@ namespace AssetStudio for (int i = 0; i < numKeys; i++) { var quat = quats[i]; - var value = Fbx.QuaternionToEuler(new Quaternion(quat.X, -quat.Y, -quat.Z, quat.W)); - track.Rotations.Add(new ImportedKeyframe(times[i], value)); + var value = new Quaternion(quat.X, -quat.Y, -quat.Z, quat.W); + track.Rotations.Add(new ImportedKeyframe(times[i], value)); } } foreach (var m_RotationCurve in animationClip.m_RotationCurves) @@ -827,8 +827,8 @@ namespace AssetStudio var track = iAnim.FindTrack(FixBonePath(animationClip, m_RotationCurve.path)); foreach (var m_Curve in m_RotationCurve.curve.m_Curve) { - var value = Fbx.QuaternionToEuler(new Quaternion(m_Curve.value.X, -m_Curve.value.Y, -m_Curve.value.Z, m_Curve.value.W)); - track.Rotations.Add(new ImportedKeyframe(m_Curve.time, value)); + var value = new Quaternion(m_Curve.value.X, -m_Curve.value.Y, -m_Curve.value.Z, m_Curve.value.W); + track.Rotations.Add(new ImportedKeyframe(m_Curve.time, value)); } } foreach (var m_PositionCurve in animationClip.m_PositionCurves) @@ -854,7 +854,8 @@ namespace AssetStudio var track = iAnim.FindTrack(FixBonePath(animationClip, m_EulerCurve.path)); foreach (var m_Curve in m_EulerCurve.curve.m_Curve) { - track.Rotations.Add(new ImportedKeyframe(m_Curve.time, new Vector3(m_Curve.value.X, -m_Curve.value.Y, -m_Curve.value.Z))); + var value = Fbx.EulerToQuaternion(new Vector3(m_Curve.value.X, -m_Curve.value.Y, -m_Curve.value.Z)); + track.Rotations.Add(new ImportedKeyframe(m_Curve.time, value)); } } } @@ -1009,14 +1010,13 @@ namespace AssetStudio ))); break; case 2: - var value = Fbx.QuaternionToEuler(new Quaternion + track.Rotations.Add(new ImportedKeyframe(time, new Quaternion ( data[curveIndex++ + offset], -data[curveIndex++ + offset], -data[curveIndex++ + offset], data[curveIndex++ + offset] - )); - track.Rotations.Add(new ImportedKeyframe(time, value)); + ))); break; case 3: track.Scalings.Add(new ImportedKeyframe(time, new Vector3 @@ -1027,12 +1027,13 @@ namespace AssetStudio ))); break; case 4: - track.Rotations.Add(new ImportedKeyframe(time, new Vector3 + var value = Fbx.EulerToQuaternion(new Vector3 ( data[curveIndex++ + offset], -data[curveIndex++ + offset], -data[curveIndex++ + offset] - ))); + )); + track.Rotations.Add(new ImportedKeyframe(time, value)); break; default: curveIndex++;