diff --git a/AssetStudio/AIVersionManager.cs b/AssetStudio/AIVersionManager.cs index 54e59e9..03f03f4 100644 --- a/AssetStudio/AIVersionManager.cs +++ b/AssetStudio/AIVersionManager.cs @@ -78,7 +78,7 @@ namespace AssetStudio if (await NeedDownload(version, versionIndex.MappedPath)) { Logger.Info("Downloading..."); - var json = await DownloadString(url, TimeSpan.FromMinutes(1)); + var json = await DownloadString(url, TimeSpan.FromMinutes(2)); if (string.IsNullOrEmpty(json)) { Logger.Warning("Could not load AI !!"); diff --git a/AssetStudio/AssetsHelper.cs b/AssetStudio/AssetsHelper.cs index 0269494..5e09f93 100644 --- a/AssetStudio/AssetsHelper.cs +++ b/AssetStudio/AssetsHelper.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json; using System.Text.RegularExpressions; using System.Xml; +using System.Text; namespace AssetStudio { @@ -82,10 +83,11 @@ namespace AssetStudio public static void BuildMap(string[] files, string mapName, string baseFolder, Game game) { - Logger.Info($"Processing..."); + Logger.Info($"Building Map..."); try { Map.Clear(); + Progress.Reset(); var collision = 0; BaseFolder = baseFolder; assetsManager.Game = game; @@ -118,7 +120,8 @@ namespace AssetStudio } Map.Add(assetsFile.fileName, entry); } - Logger.Info($"Processed {Path.GetFileName(file)}"); + Logger.Info($"[{i + 1}/{files.Length}] Processed {Path.GetFileName(file)}"); + Progress.Report(i + 1, files.Length); } assetsManager.Clear(); } @@ -156,7 +159,9 @@ namespace AssetStudio public static void BuildBoth(string[] files, string mapName, string baseFolder, Game game, string savePath, ExportListType exportListType, ManualResetEvent resetEvent = null, Regex[] nameFilters = null, Regex[] containerFilters = null) { + Logger.Info($"Building Both..."); Map.Clear(); + Progress.Reset(); var collision = 0; BaseFolder = baseFolder; assetsManager.Game = game; @@ -203,74 +208,89 @@ namespace AssetStudio Source = file, PathID = objectReader.m_PathID, Type = objectReader.type, - Container = "" + Container = string.Empty, + Name = string.Empty }; var exportable = true; - switch (objectReader.type) + try { - case ClassIDType.AssetBundle: - var assetBundle = new AssetBundle(objectReader); - foreach (var m_Container in assetBundle.m_Container) - { - var preloadIndex = m_Container.Value.preloadIndex; - var preloadSize = m_Container.Value.preloadSize; - var preloadEnd = preloadIndex + preloadSize; - for (int k = preloadIndex; k < preloadEnd; k++) + switch (objectReader.type) + { + case ClassIDType.AssetBundle: + var assetBundle = new AssetBundle(objectReader); + foreach (var m_Container in assetBundle.m_Container) { - containers.Add((assetBundle.m_PreloadTable[k], m_Container.Key)); + var preloadIndex = m_Container.Value.preloadIndex; + var preloadSize = m_Container.Value.preloadSize; + var preloadEnd = preloadIndex + preloadSize; + for (int k = preloadIndex; k < preloadEnd; k++) + { + containers.Add((assetBundle.m_PreloadTable[k], m_Container.Key)); + } } - } - obj = null; - asset.Name = assetBundle.m_Name; - exportable = false; - break; - case ClassIDType.GameObject: - var gameObject = new GameObject(objectReader); - obj = gameObject; - asset.Name = gameObject.m_Name; - exportable = false; - break; - case ClassIDType.Shader: - asset.Name = objectReader.ReadAlignedString(); - if (string.IsNullOrEmpty(asset.Name)) - { - var m_parsedForm = new SerializedShader(objectReader); - asset.Name = m_parsedForm.m_Name; - } - break; - case ClassIDType.Animator: - var component = new PPtr(objectReader); - animators.Add((component, asset)); - break; - case ClassIDType.MiHoYoBinData: - var MiHoYoBinData = new MiHoYoBinData(objectReader); - obj = MiHoYoBinData; - exportable = true; - break; - case ClassIDType.IndexObject: - var indexObject = new IndexObject(objectReader); - obj = null; - foreach (var index in indexObject.AssetMap) - { - mihoyoBinDataNames.Add((index.Value.Object, index.Key)); - } - asset.Name = "IndexObject"; - break; - case ClassIDType.Font: - case ClassIDType.Material: - case ClassIDType.Texture: - case ClassIDType.Mesh: - case ClassIDType.Sprite: - case ClassIDType.TextAsset: - case ClassIDType.Texture2D: - case ClassIDType.VideoClip: - case ClassIDType.AudioClip: - asset.Name = objectReader.ReadAlignedString(); - break; - default: - exportable = false; - break; + obj = null; + asset.Name = assetBundle.m_Name; + exportable = false; + break; + case ClassIDType.GameObject: + var gameObject = new GameObject(objectReader); + obj = gameObject; + asset.Name = gameObject.m_Name; + exportable = false; + break; + case ClassIDType.Shader: + asset.Name = objectReader.ReadAlignedString(); + if (string.IsNullOrEmpty(asset.Name)) + { + var m_parsedForm = new SerializedShader(objectReader); + asset.Name = m_parsedForm.m_Name; + } + break; + case ClassIDType.Animator: + var component = new PPtr(objectReader); + animators.Add((component, asset)); + break; + case ClassIDType.MiHoYoBinData: + var MiHoYoBinData = new MiHoYoBinData(objectReader); + obj = MiHoYoBinData; + exportable = true; + break; + case ClassIDType.IndexObject: + var indexObject = new IndexObject(objectReader); + obj = null; + foreach (var index in indexObject.AssetMap) + { + mihoyoBinDataNames.Add((index.Value.Object, index.Key)); + } + asset.Name = "IndexObject"; + break; + case ClassIDType.Font: + case ClassIDType.Material: + case ClassIDType.Texture: + case ClassIDType.Mesh: + case ClassIDType.Sprite: + case ClassIDType.TextAsset: + case ClassIDType.Texture2D: + case ClassIDType.VideoClip: + case ClassIDType.AudioClip: + asset.Name = objectReader.ReadAlignedString(); + break; + default: + exportable = false; + break; + } + } + catch (Exception e) + { + var sb = new StringBuilder(); + sb.AppendLine("Unable to load object") + .AppendLine($"Assets {assetsFile.fileName}") + .AppendLine($"Path {assetsFile.originalPath}") + .AppendLine($"Type {objectReader.type}") + .AppendLine($"PathID {objectReader.m_PathID}") + .Append(e); + Logger.Error(sb.ToString()); } if (obj != null) { @@ -319,7 +339,8 @@ namespace AssetStudio } } } - Logger.Info($"Processed {Path.GetFileName(file)}"); + Logger.Info($"[{i + 1}/{files.Length}] Processed {Path.GetFileName(file)}"); + Progress.Report(i + 1, files.Length); } assetsManager.Clear(); } @@ -421,6 +442,7 @@ namespace AssetStudio public static AssetEntry[] BuildAssetMap(string[] files, Game game, Regex[] nameFilters = null, Regex[] containerFilters = null) { + Progress.Reset(); assetsManager.Game = game; var assets = new List(); for (int i = 0; i < files.Length; i++) @@ -448,70 +470,84 @@ namespace AssetStudio }; var exportable = true; - switch (objectReader.type) + try { - case ClassIDType.AssetBundle: - var assetBundle = new AssetBundle(objectReader); - foreach (var m_Container in assetBundle.m_Container) - { - var preloadIndex = m_Container.Value.preloadIndex; - var preloadSize = m_Container.Value.preloadSize; - var preloadEnd = preloadIndex + preloadSize; - for (int k = preloadIndex; k < preloadEnd; k++) + switch (objectReader.type) + { + case ClassIDType.AssetBundle: + var assetBundle = new AssetBundle(objectReader); + foreach (var m_Container in assetBundle.m_Container) { - containers.Add((assetBundle.m_PreloadTable[k], m_Container.Key)); + var preloadIndex = m_Container.Value.preloadIndex; + var preloadSize = m_Container.Value.preloadSize; + var preloadEnd = preloadIndex + preloadSize; + for (int k = preloadIndex; k < preloadEnd; k++) + { + containers.Add((assetBundle.m_PreloadTable[k], m_Container.Key)); + } } - } - obj = null; - asset.Name = assetBundle.m_Name; - exportable = false; - break; - case ClassIDType.GameObject: - var gameObject = new GameObject(objectReader); - obj = gameObject; - asset.Name = gameObject.m_Name; - exportable = false; - break; - case ClassIDType.Shader: - asset.Name = objectReader.ReadAlignedString(); - if (string.IsNullOrEmpty(asset.Name)) - { - var m_parsedForm = new SerializedShader(objectReader); - asset.Name = m_parsedForm.m_Name; - } - break; - case ClassIDType.Animator: - var component = new PPtr(objectReader); - animators.Add((component, asset)); - break; - case ClassIDType.MiHoYoBinData: - var MiHoYoBinData = new MiHoYoBinData(objectReader); - obj = MiHoYoBinData; - exportable = true; - break; - case ClassIDType.IndexObject: - var indexObject = new IndexObject(objectReader); - obj = null; - foreach (var index in indexObject.AssetMap) - { - mihoyoBinDataNames.Add((index.Value.Object, index.Key)); - } - asset.Name = "IndexObject"; - break; - case ClassIDType.Font: - case ClassIDType.Material: - case ClassIDType.Texture: - case ClassIDType.Mesh: - case ClassIDType.Sprite: - case ClassIDType.TextAsset: - case ClassIDType.Texture2D: - case ClassIDType.VideoClip: - case ClassIDType.AudioClip: - asset.Name = objectReader.ReadAlignedString(); - break; - default: - exportable = false; - break; + obj = null; + asset.Name = assetBundle.m_Name; + exportable = false; + break; + case ClassIDType.GameObject: + var gameObject = new GameObject(objectReader); + obj = gameObject; + asset.Name = gameObject.m_Name; + exportable = false; + break; + case ClassIDType.Shader: + asset.Name = objectReader.ReadAlignedString(); + if (string.IsNullOrEmpty(asset.Name)) + { + var m_parsedForm = new SerializedShader(objectReader); + asset.Name = m_parsedForm.m_Name; + } + break; + case ClassIDType.Animator: + var component = new PPtr(objectReader); + animators.Add((component, asset)); + break; + case ClassIDType.MiHoYoBinData: + var MiHoYoBinData = new MiHoYoBinData(objectReader); + obj = MiHoYoBinData; + exportable = true; + break; + case ClassIDType.IndexObject: + var indexObject = new IndexObject(objectReader); + obj = null; + foreach (var index in indexObject.AssetMap) + { + mihoyoBinDataNames.Add((index.Value.Object, index.Key)); + } + asset.Name = "IndexObject"; + break; + case ClassIDType.Font: + case ClassIDType.Material: + case ClassIDType.Texture: + case ClassIDType.Mesh: + case ClassIDType.Sprite: + case ClassIDType.TextAsset: + case ClassIDType.Texture2D: + case ClassIDType.VideoClip: + case ClassIDType.AudioClip: + asset.Name = objectReader.ReadAlignedString(); + break; + default: + exportable = false; + break; + } + } + catch (Exception e) + { + var sb = new StringBuilder(); + sb.AppendLine("Unable to load object") + .AppendLine($"Assets {assetsFile.fileName}") + .AppendLine($"Path {assetsFile.originalPath}") + .AppendLine($"Type {objectReader.type}") + .AppendLine($"PathID {objectReader.m_PathID}") + .Append(e); + Logger.Error(sb.ToString()); } if (obj != null) { @@ -560,7 +596,8 @@ namespace AssetStudio } } } - Logger.Info($"Processed {Path.GetFileName(file)}"); + Logger.Info($"[{i + 1}/{files.Length}] Processed {Path.GetFileName(file)}"); + Progress.Report(i + 1, files.Length); } assetsManager.Clear(); } diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index 56b7859..839a58d 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -107,11 +107,6 @@ namespace AssetStudio Logger.Info("Loading files has been aborted !!"); break; } - if (!SkipProcess && !tokenSource.IsCancellationRequested) - { - ReadAssets(); - ProcessAssets(); - } } importFiles.Clear(); @@ -120,12 +115,12 @@ namespace AssetStudio assetsFileListHash.Clear(); AssetsHelper.ClearOffsets(); - //if (!SkipProcess && !tokenSource.IsCancellationRequested) - //{ - // ReadAssets(); - // ProcessAssets(); - //} + if (!SkipProcess && !tokenSource.IsCancellationRequested) + { + ReadAssets(); + ProcessAssets(); } + } private void LoadFile(string fullName) { @@ -177,36 +172,36 @@ namespace AssetStudio assetsFileList.Add(assetsFile); assetsFileListHash.Add(assetsFile.fileName); - foreach (var sharedFile in assetsFile.m_Externals) - { - var sharedFileName = sharedFile.fileName; + foreach (var sharedFile in assetsFile.m_Externals) + { + var sharedFileName = sharedFile.fileName; - if (!importFilesHash.Contains(sharedFileName)) + if (!importFilesHash.Contains(sharedFileName)) + { + var sharedFilePath = Path.Combine(Path.GetDirectoryName(reader.FullPath), sharedFileName); + if (!noexistFiles.Contains(sharedFilePath)) { - var sharedFilePath = Path.Combine(Path.GetDirectoryName(reader.FullPath), sharedFileName); - if (!noexistFiles.Contains(sharedFilePath)) + if (!File.Exists(sharedFilePath)) { - if (!File.Exists(sharedFilePath)) + var findFiles = Directory.GetFiles(Path.GetDirectoryName(reader.FullPath), sharedFileName, SearchOption.AllDirectories); + if (findFiles.Length > 0) { - var findFiles = Directory.GetFiles(Path.GetDirectoryName(reader.FullPath), sharedFileName, SearchOption.AllDirectories); - if (findFiles.Length > 0) - { - sharedFilePath = findFiles[0]; - } - } - if (File.Exists(sharedFilePath)) - { - importFiles.Add(sharedFilePath); - importFilesHash.Add(sharedFileName); - } - else - { - noexistFiles.Add(sharedFilePath); + sharedFilePath = findFiles[0]; } } + if (File.Exists(sharedFilePath)) + { + importFiles.Add(sharedFilePath); + importFilesHash.Add(sharedFileName); + } + else + { + noexistFiles.Add(sharedFilePath); + } } } } + } catch (Exception e) { Logger.Error($"Error while reading assets file {reader.FullPath}", e); @@ -237,37 +232,37 @@ namespace AssetStudio 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); - // } - // } - // } - // } - //} + 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) { @@ -279,9 +274,12 @@ namespace AssetStudio Logger.Info($"Skipping {originalPath} ({reader.FileName})"); } - private void LoadBundleFile(FileReader reader, string originalPath = null, long originalOffset = 0) + private void LoadBundleFile(FileReader reader, string originalPath = null, long originalOffset = 0, bool log = true) { - Logger.Info("Loading " + reader.FullPath); + if (log) + { + Logger.Info("Loading " + reader.FullPath); + } try { var bundleFile = new BundleFile(reader, Game); @@ -459,15 +457,18 @@ namespace AssetStudio Logger.Info("Loading " + reader.FullPath); try { - using var stream = new BlockStream(reader.BaseStream, 0); + using var stream = new SubStream(reader.BaseStream, 0); if (AssetsHelper.TryGet(reader.FullPath, out var offsets)) { foreach (var offset in offsets) { + var name = offset.ToString("X8"); + Logger.Info($"Loading Block {name}"); + stream.Offset = offset; - var dummyPath = Path.Combine(reader.FileName, offset.ToString("X8")); + var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), name); var subReader = new FileReader(dummyPath, stream, true); - LoadBundleFile(subReader, reader.FullPath, offset); + LoadBundleFile(subReader, reader.FullPath, offset, false); } AssetsHelper.Remove(reader.FullPath); } @@ -475,12 +476,15 @@ namespace AssetStudio { do { - stream.Offset = stream.RelativePosition; - var dummyPath = Path.Combine(reader.FileName, stream.RelativePosition.ToString("X8")); + 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.RelativePosition); + LoadBundleFile(subReader, reader.FullPath, stream.AbsolutePosition, false); } while (stream.Remaining > 0); - } + } } catch (Exception e) { @@ -501,16 +505,19 @@ namespace AssetStudio { foreach (var offset in offsets) { + var name = offset.ToString("X8"); + Logger.Info($"Loading Block {name}"); + stream.Offset = offset; - var dummyPath = Path.Combine(reader.FileName, offset.ToString("X8")); + 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); + LoadBundleFile(subReader, reader.FullPath, offset, false); break; case FileType.Mhy0File: - LoadMhy0File(subReader, reader.FullPath, offset); + LoadMhy0File(subReader, reader.FullPath, offset, false); break; } } @@ -520,18 +527,22 @@ namespace AssetStudio { do { - stream.Offset = stream.RelativePosition; - var dummyPath = Path.Combine(reader.FileName, stream.RelativePosition.ToString("X8")); + var name = stream.Position.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.RelativePosition); + LoadBundleFile(subReader, reader.FullPath, stream.Position, false); break; case FileType.Mhy0File: - LoadMhy0File(subReader, reader.FullPath, stream.RelativePosition); + LoadMhy0File(subReader, reader.FullPath, stream.Position, false); break; } + + stream.Offset += stream.Position; } while (stream.Remaining > 0); } } @@ -548,9 +559,12 @@ namespace AssetStudio reader.Dispose(); } } - private void LoadMhy0File(FileReader reader, string originalPath = null, long originalOffset = 0) + private void LoadMhy0File(FileReader reader, string originalPath = null, long originalOffset = 0, bool log = true) { - Logger.Info("Loading " + reader.FullPath); + if (log) + { + Logger.Info("Loading " + reader.FullPath); + } try { var mhy0File = new Mhy0File(reader, reader.FullPath, (Mhy0)Game); diff --git a/AssetStudio/BlockStream.cs b/AssetStudio/BlockStream.cs deleted file mode 100644 index 645ac8d..0000000 --- a/AssetStudio/BlockStream.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.IO; - -namespace AssetStudio -{ - public class BlockStream : Stream - { - private readonly Stream _baseStream; - private readonly long _origin; - private long _startPosition; - - public override bool CanRead => _baseStream.CanRead; - public override bool CanSeek => _baseStream.CanSeek; - public override bool CanWrite => false; - - public override long Length => _baseStream.Length - _startPosition; - - public override long Position - { - get => _baseStream.Position - _startPosition; - set => Seek(value, SeekOrigin.Begin); - } - - public long Offset - { - get => _startPosition; - set => _startPosition = value + _origin; - } - - public long RelativePosition => _baseStream.Position - _origin; - public long Remaining => Length - Position; - - public BlockStream(Stream stream, long offset) - { - _baseStream = stream; - _origin = offset; - _startPosition = offset; - Seek(0, SeekOrigin.Begin); - } - - public override long Seek(long offset, SeekOrigin origin) - { - var target = origin switch - { - SeekOrigin.Begin => offset + _startPosition, - SeekOrigin.Current => offset + _baseStream.Position, - SeekOrigin.End => offset + Length, - _ => throw new NotSupportedException() - }; - - _baseStream.Seek(target, SeekOrigin.Begin); - return Position; - } - 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/BundleFile.cs b/AssetStudio/BundleFile.cs index 866bf16..279d669 100644 --- a/AssetStudio/BundleFile.cs +++ b/AssetStudio/BundleFile.cs @@ -112,7 +112,7 @@ namespace AssetStudio switch (header.signature) { case "UnityFS": - if (Game.Type.IsBH3()) + if (Game.Type.IsBH3Group()) { var version = reader.ReadUInt32(); if (version > 11) @@ -277,7 +277,7 @@ namespace AssetStudio private void ReadHeader(FileReader reader) { - if (Game.Type.IsBH3() && XORShift128.Init) + if ((Game.Type.IsBH3Group()) && XORShift128.Init) { m_Header.flags = (ArchiveFlags)reader.ReadUInt32(); m_Header.size = reader.ReadInt64(); diff --git a/AssetStudio/Classes/AnimationClip.cs b/AssetStudio/Classes/AnimationClip.cs index f589d94..bc49c6f 100644 --- a/AssetStudio/Classes/AnimationClip.cs +++ b/AssetStudio/Classes/AnimationClip.cs @@ -672,7 +672,7 @@ namespace AssetStudio { m_ConstantClip = new ConstantClip(reader); } - if (reader.Game.Type.IsGIGroup() || reader.Game.Type.IsBH3() || reader.Game.Type.IsZZZCB1()) + if (reader.Game.Type.IsGIGroup() || reader.Game.Type.IsBH3Group() || reader.Game.Type.IsZZZCB1()) { m_ACLClip.Read(reader); } diff --git a/AssetStudio/Classes/PPtr.cs b/AssetStudio/Classes/PPtr.cs index 54529ed..b31de67 100644 --- a/AssetStudio/Classes/PPtr.cs +++ b/AssetStudio/Classes/PPtr.cs @@ -41,26 +41,6 @@ namespace AssetStudio var name = m_External.fileName; if (!assetsFileIndexCache.TryGetValue(name, out index)) { - if (assetsManager.ResolveDependencies && !assetsManager.importFilesHash.Contains(name)) - { - var sharedFilePath = Path.Combine(Path.GetDirectoryName(assetsFile.originalPath), name); - if (!assetsManager.noexistFiles.Contains(sharedFilePath)) - { - if (TryAdd(name, out var path)) - { - sharedFilePath = path; - } - if (File.Exists(sharedFilePath)) - { - assetsManager.importFilesHash.Add(name); - assetsManager.LoadFiles(sharedFilePath); - } - else - { - assetsManager.noexistFiles.Add(sharedFilePath); - } - } - } index = assetsFileList.FindIndex(x => x.fileName.Equals(name, StringComparison.OrdinalIgnoreCase)); assetsFileIndexCache.Add(name, index); } diff --git a/AssetStudio/Classes/Renderer.cs b/AssetStudio/Classes/Renderer.cs index bbc9d5f..6c01922 100644 --- a/AssetStudio/Classes/Renderer.cs +++ b/AssetStudio/Classes/Renderer.cs @@ -48,7 +48,7 @@ namespace AssetStudio { var m_DynamicOccludee = reader.ReadByte(); } - if (reader.Game.Type.IsBH3()) + if (reader.Game.Type.IsBH3Group()) { var m_AllowHalfResolution = reader.ReadByte(); } diff --git a/AssetStudio/Classes/Shader.cs b/AssetStudio/Classes/Shader.cs index 0ec15d8..e8c94b0 100644 --- a/AssetStudio/Classes/Shader.cs +++ b/AssetStudio/Classes/Shader.cs @@ -585,7 +585,7 @@ namespace AssetStudio m_BlobIndex = reader.ReadUInt32(); m_Channels = new ParserBindChannels(reader); - if ((version[0] >= 2019 && version[0] < 2021) || (version[0] == 2021 && version[1] < 2)) //2019 ~2021.1 + if ((version[0] >= 2019 && version[0] < 2021) || (version[0] == 2021 && version[1] < 2) || reader.Match("E99740711222CD922E9A6F92FF1EB07A") || reader.Match("450A058C218DAF000647948F2F59DA6D")) //2019 ~2021.1 { var m_GlobalKeywordIndices = reader.ReadUInt16Array(); reader.AlignStream(); @@ -605,17 +605,6 @@ namespace AssetStudio m_GpuProgramType = (ShaderGpuProgramType)reader.ReadSByte(); reader.AlignStream(); - if (reader.Game.Type.IsGI() && (m_GpuProgramType == ShaderGpuProgramType.Unknown || !Enum.IsDefined(typeof(ShaderGpuProgramType), m_GpuProgramType))) - { - reader.Position -= 4; - var m_LocalKeywordIndices = reader.ReadUInt16Array(); - reader.AlignStream(); - - m_ShaderHardwareTier = reader.ReadSByte(); - m_GpuProgramType = (ShaderGpuProgramType)reader.ReadSByte(); - reader.AlignStream(); - } - if ((version[0] == 2020 && version[1] > 3) || (version[0] == 2020 && version[1] == 3 && version[2] >= 2) || //2020.3.2f1 and up (version[0] > 2021) || @@ -697,6 +686,16 @@ namespace AssetStudio var m_ShaderRequirements = reader.ReadInt32(); } } + + if (reader.Match("E99740711222CD922E9A6F92FF1EB07A")) + { + int numInstancedStructuredBuffers = reader.ReadInt32(); + var m_InstancedStructuredBuffers = new ConstantBuffer[numInstancedStructuredBuffers]; + for (int i = 0; i < numInstancedStructuredBuffers; i++) + { + m_InstancedStructuredBuffers[i] = new ConstantBuffer(reader); + } + } } } diff --git a/AssetStudio/Crypto/BlkUtils.cs b/AssetStudio/Crypto/BlkUtils.cs index adab1c8..b451f25 100644 --- a/AssetStudio/Crypto/BlkUtils.cs +++ b/AssetStudio/Crypto/BlkUtils.cs @@ -5,7 +5,7 @@ namespace AssetStudio { public static class BlkUtils { - private const int DataPos = 0x2A; + private const int DataOffset = 0x2A; private const int KeySize = 0x1000; private const int SeedBlockSize = 0x800; @@ -54,7 +54,7 @@ namespace AssetStudio BinaryPrimitives.WriteUInt64LittleEndian(xorpad.AsSpan(i, 8), mt64.Int64()); } - return new XORStream(reader.BaseStream, DataPos, xorpad); + return new XORStream(reader.BaseStream, DataOffset, xorpad); } } } \ No newline at end of file diff --git a/AssetStudio/FileReader.cs b/AssetStudio/FileReader.cs index 9448599..dfab944 100644 --- a/AssetStudio/FileReader.cs +++ b/AssetStudio/FileReader.cs @@ -126,6 +126,14 @@ namespace AssetStudio } return true; } + + public static bool IsReadable(string path, Game game) + { + var reader = new FileReader(path); + reader = reader.PreProcessing(game); + reader.Dispose(); + return reader.FileType != FileType.ResourceFile; + } } public static class FileReaderExtensions diff --git a/AssetStudio/GameManager.cs b/AssetStudio/GameManager.cs index 1b018f4..e9962b4 100644 --- a/AssetStudio/GameManager.cs +++ b/AssetStudio/GameManager.cs @@ -19,6 +19,7 @@ namespace AssetStudio Games.Add(index++, new Blk(GameType.GI_CB3, GI_CBXExpansionKey, initVector: GI_CBXInitVector, initSeed: GI_CBXInitSeed)); Games.Add(index++, new Mhy0(GameType.GI_CB3Pre, GI_CBXMhy0ShiftRow, GI_CBXMhy0Key, GI_CBXMhy0Mul, GI_CBXExpansionKey, GI_CBXSBox, GI_CBXInitVector, GI_CBXInitSeed)); Games.Add(index++, new Mr0k(GameType.BH3, BH3ExpansionKey, BH3SBox, BH3InitVector, BH3BlockKey)); + Games.Add(index++, new Mr0k(GameType.BH3_Pre, PackExpansionKey, blockKey: PackBlockKey)); Games.Add(index++, new Mr0k(GameType.SR_CB2, Mr0kExpansionKey, initVector: Mr0kInitVector, blockKey: Mr0kBlockKey)); Games.Add(index++, new Mr0k(GameType.SR_CB3, Mr0kExpansionKey, initVector: Mr0kInitVector, blockKey: Mr0kBlockKey)); Games.Add(index++, new Mr0k(GameType.ZZZ_CB1, Mr0kExpansionKey, initVector: Mr0kInitVector, blockKey: Mr0kBlockKey)); @@ -120,6 +121,7 @@ namespace AssetStudio GI_CB3, GI_CB3Pre, BH3, + BH3_Pre, ZZZ_CB1, SR_CB2, SR_CB3, @@ -143,6 +145,7 @@ namespace AssetStudio public static bool IsGICB3(this GameType type) => type == GameType.GI_CB3; public static bool IsGICB3Pre(this GameType type) => type == GameType.GI_CB3Pre; public static bool IsBH3(this GameType type) => type == GameType.BH3; + public static bool IsBH3Pre(this GameType type) => type == GameType.BH3_Pre; public static bool IsZZZCB1(this GameType type) => type == GameType.ZZZ_CB1; public static bool IsSRCB2(this GameType type) => type == GameType.SR_CB2; public static bool IsSRCB3(this GameType type) => type == GameType.SR_CB3; @@ -161,6 +164,12 @@ namespace AssetStudio _ => false, }; + public static bool IsBH3Group(this GameType type) => type switch + { + GameType.BH3 or GameType.BH3_Pre => true, + _ => false, + }; + public static bool IsSRGroup(this GameType type) => type switch { GameType.SR_CB2 or GameType.SR_CB3 => true, @@ -169,7 +178,7 @@ namespace AssetStudio public static bool IsBlockFile(this GameType type) => type switch { - GameType.BH3 or GameType.SR_CB3 or GameType.GI_Pack or GameType.TOT => true, + GameType.BH3 or GameType.BH3_Pre or GameType.SR_CB3 or GameType.GI_Pack or GameType.TOT => true, _ => false, }; diff --git a/AssetStudio/ImportHelper.cs b/AssetStudio/ImportHelper.cs index 1c14f2f..a26bcc3 100644 --- a/AssetStudio/ImportHelper.cs +++ b/AssetStudio/ImportHelper.cs @@ -279,7 +279,7 @@ namespace AssetStudio var idx = data.Search("UnityFS"); if (idx != -1) { - stream = new BlockStream(stream, idx); + stream = new SubStream(stream, idx); } return new FileReader(reader.FullPath, stream); @@ -295,11 +295,11 @@ namespace AssetStudio var idx2 = data[(idx + 1)..].Search("UnityFS"); if (idx2 != -1) { - stream = new BlockStream(stream, idx + idx2 + 1); + stream = new SubStream(stream, idx + idx2 + 1); } else { - stream = new BlockStream(stream, idx); + stream = new SubStream(stream, idx); } } @@ -371,7 +371,7 @@ namespace AssetStudio reader.Position = 0; return reader; } - var stream = new BlockStream(reader.BaseStream, idx); + var stream = new SubStream(reader.BaseStream, idx); return new FileReader(reader.FullPath, stream); } public static FileReader ParseHelixWaltz2(FileReader reader) diff --git a/AssetStudio/ObjectReader.cs b/AssetStudio/ObjectReader.cs index a043c4a..4f902e9 100644 --- a/AssetStudio/ObjectReader.cs +++ b/AssetStudio/ObjectReader.cs @@ -21,7 +21,7 @@ namespace AssetStudio public int[] version => assetsFile.version; public BuildType buildType => assetsFile.buildType; - public ObjectReader(EndianBinaryReader reader, SerializedFile assetsFile, ObjectInfo objectInfo, Game game) : base(reader.BaseStream, reader.Endian) + public ObjectReader(EndianBinaryReader reader, SerializedFile assetsFile, ObjectInfo objectInfo, Game game) : base(new SubStream(reader.BaseStream, objectInfo.byteStart, objectInfo.byteSize), reader.Endian) { this.assetsFile = assetsFile; Game = game; @@ -41,9 +41,11 @@ namespace AssetStudio m_Version = assetsFile.header.m_Version; } + public bool Match(string hash) => Convert.ToHexString(serializedType.m_OldTypeHash) == hash; + public void Reset() { - Position = byteStart; + Position = 0; } } } diff --git a/AssetStudio/SubStream.cs b/AssetStudio/SubStream.cs new file mode 100644 index 0000000..aa8ce62 --- /dev/null +++ b/AssetStudio/SubStream.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; + +namespace AssetStudio +{ + public class SubStream : Stream + { + private readonly Stream _baseStream; + private long _offset; + private long _size; + + public override bool CanRead => _baseStream.CanRead; + public override bool CanSeek => _baseStream.CanSeek; + public override bool CanWrite => false; + + public long Size + { + get => _size; + set + { + if (value < 0 || value > _baseStream.Length || value + _offset > _baseStream.Length) + { + throw new IOException($"{nameof(Size)} is out of stream bound"); + } + _size = value; + } + } + + public long Offset + { + get => _offset; + set + { + if (value < 0 || value > _baseStream.Length) + { + throw new IOException($"{nameof(Offset)} is out of stream bound"); + } + if (value + _size > _baseStream.Length) + { + _size = _baseStream.Length - value; + } + _offset = value; + } + } + public long AbsolutePosition => _baseStream.Position; + public long Remaining => Length - Position; + + public override long Length => Size; + public override long Position + { + get => _baseStream.Position - _offset; + set => Seek(value, SeekOrigin.Begin); + } + + public SubStream(Stream stream, long offset) + { + _baseStream = stream; + + Offset = offset; + Size = _baseStream.Length - _offset; + Seek(0, SeekOrigin.Begin); + } + + public SubStream(Stream stream, long offset, long size) + { + _baseStream = stream; + + Offset = offset; + Size = size; + Seek(0, SeekOrigin.Begin); + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (offset > _size) + { + throw new IOException("Unable to seek beyond stream bound"); + } + + var target = origin switch + { + SeekOrigin.Begin => offset + _offset, + SeekOrigin.Current => offset + Position, + SeekOrigin.End => offset + _size, + _ => throw new NotSupportedException() + }; + + _baseStream.Seek(target, SeekOrigin.Begin); + return Position; + } + public override int Read(byte[] buffer, int offset, int count) + { + if (offset > _size || Position + count > _size) + { + throw new IOException("Unable to read beyond stream bound"); + } + return _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/XORStream.cs b/AssetStudio/XORStream.cs index 599ad39..3ad7106 100644 --- a/AssetStudio/XORStream.cs +++ b/AssetStudio/XORStream.cs @@ -2,18 +2,20 @@ namespace AssetStudio { - public class XORStream : BlockStream + public class XORStream : SubStream { private readonly byte[] _xorpad; + private readonly long _offset; - public XORStream(Stream stream, long pos, byte[] xorpad) : base(stream, pos) + public XORStream(Stream stream, long offset, byte[] xorpad) : base(stream, offset) { _xorpad = xorpad; + _offset = offset; } public override int Read(byte[] buffer, int offset, int count) { - var pos = RelativePosition; + var pos = AbsolutePosition - _offset; var read = base.Read(buffer, offset, count); for (int i = 0; i < count; i++) { diff --git a/AssetStudioCLI/Program.cs b/AssetStudioCLI/Program.cs index 4db5a27..012fdfb 100644 --- a/AssetStudioCLI/Program.cs +++ b/AssetStudioCLI/Program.cs @@ -79,8 +79,9 @@ namespace AssetStudioCLI assemblyLoader.Load(o.DummyDllFolder.FullName); } - Logger.Info("Scanning for files"); + Logger.Info("Scanning for files..."); var files = o.Input.Attributes.HasFlag(FileAttributes.Directory) ? Directory.GetFiles(o.Input.FullName, "*.*", SearchOption.AllDirectories).OrderBy(x => x.Length).ToArray() : new string[] { o.Input.FullName }; + files = files.Where(x => FileReader.IsReadable(x, game)).ToArray(); Logger.Info(string.Format("Found {0} file(s)", files.Length)); if (o.MapOp.HasFlag(MapOpType.Build)) @@ -107,6 +108,12 @@ namespace AssetStudioCLI AssetsHelper.ExportAssetsMap(assets, o.MapName, o.Output.FullName, o.MapType, resetEvent); resetEvent.WaitOne(); } + if (o.MapOp.HasFlag(MapOpType.Both)) + { + var resetEvent = new ManualResetEvent(false); + AssetsHelper.BuildBoth(files, o.MapName, o.Input.FullName, game, o.Output.FullName, o.MapType, resetEvent, o.NameFilter, o.ContainerFilter); + resetEvent.WaitOne(); + } if (o.MapOp.Equals(MapOpType.None) || o.MapOp.HasFlag(MapOpType.Load)) { var i = 0; diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 0318c48..50fabb7 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -20,9 +20,9 @@ namespace AssetStudioCLI None, Load, Build, - Both, - List, - All = Build | Load | List + List = 4, + Both = 8, + All = Both | Load, } public enum AssetGroupOption @@ -127,8 +127,8 @@ namespace AssetStudioCLI using var stream = BlkUtils.Decrypt(reader, (Blk)Game); do { - stream.Offset = stream.RelativePosition; - var dummyPath = Path.Combine(reader.FullPath, stream.RelativePosition.ToString("X8")); + stream.Offset = stream.AbsolutePosition; + var dummyPath = Path.Combine(reader.FullPath, stream.AbsolutePosition.ToString("X8")); var subReader = new FileReader(dummyPath, stream, true); var subSavePath = Path.Combine(savePath, reader.FileName + "_unpacked"); switch (subReader.FileType) @@ -153,12 +153,12 @@ namespace AssetStudioCLI { int total = 0; Logger.Info($"Decompressing {reader.FileName} ..."); - using var stream = new BlockStream(reader.BaseStream, 0); + using var stream = new SubStream(reader.BaseStream, 0); do { - stream.Offset = stream.RelativePosition; + stream.Offset = stream.AbsolutePosition; var subSavePath = Path.Combine(savePath, reader.FileName + "_unpacked"); - var dummyPath = Path.Combine(reader.FullPath, stream.RelativePosition.ToString("X8")); + var dummyPath = Path.Combine(reader.FullPath, stream.AbsolutePosition.ToString("X8")); var subReader = new FileReader(dummyPath, stream, true); total += ExtractBundleFile(subReader, subSavePath); } while (stream.Remaining > 0); diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 9c55efa..5648ffc 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -1899,8 +1899,9 @@ namespace AssetStudioGUI openFolderDialog.Title = "Select Game Folder"; if (openFolderDialog.ShowDialog(this) == DialogResult.OK) { - Logger.Info("Scanning for files"); + Logger.Info("Scanning for files..."); var files = Directory.GetFiles(openFolderDialog.Folder, "*.*", SearchOption.AllDirectories).ToArray(); + files = files.Where(x => FileReader.IsReadable(x, Studio.Game)).ToArray(); Logger.Info($"Found {files.Length} files"); await Task.Run(() => AssetsHelper.BuildMap(files, name, openFolderDialog.Folder, Studio.Game)); } @@ -2017,8 +2018,9 @@ namespace AssetStudioGUI openFolderDialog.Title = $"Select Game Folder"; if (openFolderDialog.ShowDialog(this) == DialogResult.OK) { - Logger.Info("Scanning for files"); + Logger.Info("Scanning for files..."); var files = Directory.GetFiles(openFolderDialog.Folder, "*.*", SearchOption.AllDirectories).ToArray(); + files = files.Where(x => FileReader.IsReadable(x, Studio.Game)).ToArray(); Logger.Info($"Found {files.Length} files"); var saveFolderDialog = new OpenFolderDialog(); @@ -2317,7 +2319,7 @@ namespace AssetStudioGUI private void InitOpenTK() { ChangeGLSize(glControl.Size); - GL.ClearColor(Color4.Darkgray); + GL.ClearColor(Color4.Cadetblue); pgmID = GL.CreateProgram(); LoadShader("vs", ShaderType.VertexShader, pgmID, out ShaderHandle vsID); LoadShader("fs", ShaderType.FragmentShader, pgmID, out ShaderHandle fsID); diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 3450ce9..dd5b650 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -128,8 +128,8 @@ namespace AssetStudioGUI using var stream = BlkUtils.Decrypt(reader, (Blk)Game); do { - stream.Offset = stream.RelativePosition; - var dummyPath = Path.Combine(reader.FullPath, stream.RelativePosition.ToString("X8")); + stream.Offset = stream.AbsolutePosition; + var dummyPath = Path.Combine(reader.FullPath, stream.AbsolutePosition.ToString("X8")); var subReader = new FileReader(dummyPath, stream, true); var subSavePath = Path.Combine(savePath, reader.FileName + "_unpacked"); switch (subReader.FileType) @@ -154,12 +154,12 @@ namespace AssetStudioGUI { int total = 0; StatusStripUpdate($"Decompressing {reader.FileName} ..."); - using var stream = new BlockStream(reader.BaseStream, 0); + using var stream = new SubStream(reader.BaseStream, 0); do { - stream.Offset = stream.RelativePosition; + stream.Offset = stream.AbsolutePosition; var subSavePath = Path.Combine(savePath, reader.FileName + "_unpacked"); - var dummyPath = Path.Combine(reader.FullPath, stream.RelativePosition.ToString("X8")); + var dummyPath = Path.Combine(reader.FullPath, stream.AbsolutePosition.ToString("X8")); var subReader = new FileReader(dummyPath, stream, true); total += ExtractBundleFile(subReader, subSavePath); } while (stream.Remaining > 0);