From 963cd6546b3f780753122979aaa5a6cd77bbfe03 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Wed, 27 Aug 2025 05:25:01 +0300 Subject: [PATCH] Improve mesh loading --- AssetStudio/AssetsManager.cs | 1 + AssetStudio/Classes/Mesh.cs | 43 ++++++++++++++----- AssetStudio/Classes/Object.cs | 13 ++++++ AssetStudioCLI/Exporter.cs | 11 ++--- AssetStudioGUI/AssetStudioGUIForm.Designer.cs | 33 +++++++++----- AssetStudioGUI/AssetStudioGUIForm.cs | 10 +++++ AssetStudioGUI/Exporter.cs | 4 ++ .../Properties/Settings.Designer.cs | 12 ++++++ AssetStudioGUI/Properties/Settings.settings | 3 ++ AssetStudioUtility/ModelConverter.cs | 2 + 10 files changed, 104 insertions(+), 28 deletions(-) diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index 4ed5ef4..2fde233 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -16,6 +16,7 @@ namespace AssetStudio public class AssetsManager { public bool LoadViaTypeTree = true; + public bool MeshLazyLoad = true; public ImportOptions Options = new ImportOptions(); public readonly List> OptionLoaders = new List>(); public readonly List AssetsFileList = new List(); diff --git a/AssetStudio/Classes/Mesh.cs b/AssetStudio/Classes/Mesh.cs index d4e0d2c..d2462b0 100644 --- a/AssetStudio/Classes/Mesh.cs +++ b/AssetStudio/Classes/Mesh.cs @@ -542,6 +542,7 @@ namespace AssetStudio public sealed class Mesh : NamedObject { + private bool isLoaded; private bool m_Use16BitIndices = true; public List m_SubMeshes; private uint[] m_IndexBuffer; @@ -587,6 +588,7 @@ namespace AssetStudio { indexBufferList.Add(reader.ReadUInt16()); } + reader.AlignStream(); m_IndexBuffer = indexBufferList.ToArray(); } @@ -656,14 +658,13 @@ namespace AssetStudio reader.AlignStream(); _ = new SharedClusterData(reader, rev: 3); } - } reader.AlignStream(); //Unity fixed it in 2017.3.1p1 and later versions if (version >= (2017, 4) //2017.4 || version == (2017, 3, 1) && version.IsPatch //fixed after 2017.3.1px - || version == (2017, 3) && m_MeshCompression == 0)//2017.3.xfx with no compression + || version == (2017, 3) && m_MeshCompression == 0) //2017.3.xfx with no compression { var m_IndexFormat = reader.ReadInt32(); m_Use16BitIndices = m_IndexFormat == 0; @@ -677,6 +678,7 @@ namespace AssetStudio { indexBufferList.Add(reader.ReadUInt16()); } + reader.AlignStream(); m_IndexBuffer = indexBufferList.ToArray(); } @@ -781,9 +783,12 @@ namespace AssetStudio if (version >= 5) //5.0 and up { - var m_BakedConvexCollisionMesh = reader.ReadUInt8Array(); + var m_BakedConvexCollisionMeshSize = reader.ReadInt32(); + reader.Position += m_BakedConvexCollisionMeshSize; //skip byte[] m_BakedConvexCollisionMesh reader.AlignStream(); - var m_BakedTriangleCollisionMesh = reader.ReadUInt8Array(); + + var m_BakedTriangleCollisionMeshSize = reader.ReadInt32(); + reader.Position += m_BakedTriangleCollisionMeshSize; //skip byte[] m_BakedTriangleCollisionMesh reader.AlignStream(); } @@ -805,18 +810,25 @@ namespace AssetStudio var m_GenerateGeometryBuffer = reader.ReadBoolean(); m_HasVirtualGeometryMesh = reader.ReadBoolean(); } - - ProcessData(); + + if (!assetsFile.assetsManager.MeshLazyLoad) + ProcessData(); } - private void ProcessData() + public void ProcessData() { + if (isLoaded) + return; + + var isStreamedDataSize = false; if (!string.IsNullOrEmpty(m_StreamData?.path)) { if (m_VertexData.m_VertexCount > 0) { + m_VertexData.m_DataSize = BigArrayPool.Shared.Rent((int)m_StreamData.size); var resourceReader = new ResourceReader(m_StreamData.path, assetsFile, m_StreamData.offset, m_StreamData.size); - m_VertexData.m_DataSize = resourceReader.GetData(); + resourceReader.GetData(m_VertexData.m_DataSize); + isStreamedDataSize = true; } } if (version >= (3, 5)) //3.5 and up @@ -829,10 +841,21 @@ namespace AssetStudio DecompressCompressedMesh(); } - if (m_HasVirtualGeometryMesh && m_IndexBuffer.Length == 0) - Logger.Warning($"Unsupported mesh type: Virtual Geometry | PathID: {m_PathID} | Name: \"{m_Name}\""); + if (m_IndexBuffer.Length == 0) + { + var msg = m_HasVirtualGeometryMesh + ? "Unsupported mesh type: Virtual Geometry" + : "Cannot process empty mesh"; + Logger.Warning($"{msg} | PathID: {m_PathID} | Name: \"{m_Name}\""); + } else + { GetTriangles(); + } + + isLoaded = true; + if (isStreamedDataSize) + BigArrayPool.Shared.Return(m_VertexData.m_DataSize, clearArray: true); } private void ReadVertexData() diff --git a/AssetStudio/Classes/Object.cs b/AssetStudio/Classes/Object.cs index fb21444..10018a9 100644 --- a/AssetStudio/Classes/Object.cs +++ b/AssetStudio/Classes/Object.cs @@ -67,6 +67,11 @@ namespace AssetStudio string str = null; try { + if (this is Mesh m_Mesh) + { + m_Mesh.ProcessData(); + } + str = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(this, GetType(), jsonOptions)) .ToJsonString(jsonOptions).Replace(" ", " "); } @@ -74,6 +79,7 @@ namespace AssetStudio { //ignore } + return str; } @@ -104,12 +110,19 @@ namespace AssetStudio { return JsonSerializer.SerializeToDocument(typeDict, jsonOptions); } + + if (this is Mesh m_Mesh) + { + m_Mesh.ProcessData(); + } + return JsonSerializer.SerializeToDocument(this, GetType(), jsonOptions); } catch { //ignore } + return null; } diff --git a/AssetStudioCLI/Exporter.cs b/AssetStudioCLI/Exporter.cs index c9bbaad..9214716 100644 --- a/AssetStudioCLI/Exporter.cs +++ b/AssetStudioCLI/Exporter.cs @@ -175,15 +175,17 @@ namespace AssetStudioCLI private static bool ExportMesh(AssetItem item, string exportPath) { var m_Mesh = (Mesh)item.Asset; + m_Mesh.ProcessData(); + if (m_Mesh.m_VertexCount <= 0) return false; if (!TryExportFile(exportPath, item, ".obj", out var exportFullPath)) return false; + var sb = new StringBuilder(); sb.AppendLine("g " + m_Mesh.m_Name); #region Vertices - if (m_Mesh.m_Vertices == null || m_Mesh.m_Vertices.Length == 0) { return false; @@ -199,11 +201,9 @@ namespace AssetStudioCLI { sb.Append($"v {-m_Mesh.m_Vertices[v * c]} {m_Mesh.m_Vertices[v * c + 1]} {m_Mesh.m_Vertices[v * c + 2]}\r\n"); } - #endregion #region UV - if (m_Mesh.m_UV0?.Length > 0) { c = 4; @@ -221,11 +221,9 @@ namespace AssetStudioCLI sb.AppendFormat("vt {0} {1}\r\n", m_Mesh.m_UV0[v * c], m_Mesh.m_UV0[v * c + 1]); } } - #endregion #region Normals - if (m_Mesh.m_Normals?.Length > 0) { if (m_Mesh.m_Normals.Length == m_Mesh.m_VertexCount * 3) @@ -242,11 +240,9 @@ namespace AssetStudioCLI sb.AppendFormat("vn {0} {1} {2}\r\n", -m_Mesh.m_Normals[v * c], m_Mesh.m_Normals[v * c + 1], m_Mesh.m_Normals[v * c + 2]); } } - #endregion #region Face - int sum = 0; for (var i = 0; i < m_Mesh.m_SubMeshes.Count; i++) { @@ -260,7 +256,6 @@ namespace AssetStudioCLI sum = end; } - #endregion sb.Replace("NaN", "0"); diff --git a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs index 8726ef9..a45e1b8 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.Designer.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.Designer.cs @@ -40,6 +40,7 @@ this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.displayAll = new System.Windows.Forms.ToolStripMenuItem(); this.useAssetLoadingViaTypetreeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.meshLazyLoadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.assetLoadingToolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); this.enablePreview = new System.Windows.Forms.ToolStripMenuItem(); this.displayInfo = new System.Windows.Forms.ToolStripMenuItem(); @@ -271,6 +272,7 @@ this.optionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.displayAll, this.useAssetLoadingViaTypetreeToolStripMenuItem, + this.meshLazyLoadToolStripMenuItem, this.assetLoadingToolStripSeparator, this.enablePreview, this.displayInfo, @@ -287,7 +289,7 @@ // this.displayAll.CheckOnClick = true; this.displayAll.Name = "displayAll"; - this.displayAll.Size = new System.Drawing.Size(241, 22); + this.displayAll.Size = new System.Drawing.Size(243, 22); this.displayAll.Text = "Display all assets"; this.displayAll.ToolTipText = "Check this option will display all types assets. Not extractable assets can expor" + "t the RAW file."; @@ -299,16 +301,26 @@ this.useAssetLoadingViaTypetreeToolStripMenuItem.CheckOnClick = true; this.useAssetLoadingViaTypetreeToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.useAssetLoadingViaTypetreeToolStripMenuItem.Name = "useAssetLoadingViaTypetreeToolStripMenuItem"; - this.useAssetLoadingViaTypetreeToolStripMenuItem.Size = new System.Drawing.Size(241, 22); + this.useAssetLoadingViaTypetreeToolStripMenuItem.Size = new System.Drawing.Size(243, 22); this.useAssetLoadingViaTypetreeToolStripMenuItem.Text = "Parse assets using their typetree"; this.useAssetLoadingViaTypetreeToolStripMenuItem.ToolTipText = "(Applies to assets with typetree included). Slower but can parse non-standard ass" + "ets. Only for Texture2D, AnimationClip and Material assets for now."; this.useAssetLoadingViaTypetreeToolStripMenuItem.CheckedChanged += new System.EventHandler(this.useAssetLoadingViaTypetreeToolStripMenuItem_CheckedChanged); // + // meshLazyLoadToolStripMenuItem + // + this.meshLazyLoadToolStripMenuItem.Checked = true; + this.meshLazyLoadToolStripMenuItem.CheckOnClick = true; + this.meshLazyLoadToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; + this.meshLazyLoadToolStripMenuItem.Name = "meshLazyLoadToolStripMenuItem"; + this.meshLazyLoadToolStripMenuItem.Size = new System.Drawing.Size(243, 22); + this.meshLazyLoadToolStripMenuItem.Text = "Use lazy loading for Mesh assets"; + this.meshLazyLoadToolStripMenuItem.CheckedChanged += new System.EventHandler(this.meshLazyLoadToolStripMenuItem_CheckedChanged); + // // assetLoadingToolStripSeparator // this.assetLoadingToolStripSeparator.Name = "assetLoadingToolStripSeparator"; - this.assetLoadingToolStripSeparator.Size = new System.Drawing.Size(238, 6); + this.assetLoadingToolStripSeparator.Size = new System.Drawing.Size(240, 6); // // enablePreview // @@ -316,7 +328,7 @@ this.enablePreview.CheckOnClick = true; this.enablePreview.CheckState = System.Windows.Forms.CheckState.Checked; this.enablePreview.Name = "enablePreview"; - this.enablePreview.Size = new System.Drawing.Size(241, 22); + this.enablePreview.Size = new System.Drawing.Size(243, 22); this.enablePreview.Text = "Enable preview"; this.enablePreview.ToolTipText = "Toggle the loading and preview of readable assets, such as images, sounds, text, " + "etc.\r\nDisable preview if you have performance or compatibility issues."; @@ -328,7 +340,7 @@ this.displayInfo.CheckOnClick = true; this.displayInfo.CheckState = System.Windows.Forms.CheckState.Checked; this.displayInfo.Name = "displayInfo"; - this.displayInfo.Size = new System.Drawing.Size(241, 22); + this.displayInfo.Size = new System.Drawing.Size(243, 22); this.displayInfo.Text = "Display asset information"; this.displayInfo.ToolTipText = "Toggle the overlay that shows information about each asset, eg. image size, forma" + "t, audio bitrate, etc."; @@ -338,7 +350,7 @@ // this.autoPlayAudioAssetsToolStripMenuItem.CheckOnClick = true; this.autoPlayAudioAssetsToolStripMenuItem.Name = "autoPlayAudioAssetsToolStripMenuItem"; - this.autoPlayAudioAssetsToolStripMenuItem.Size = new System.Drawing.Size(241, 22); + this.autoPlayAudioAssetsToolStripMenuItem.Size = new System.Drawing.Size(243, 22); this.autoPlayAudioAssetsToolStripMenuItem.Text = "Autoplay audio assets"; this.autoPlayAudioAssetsToolStripMenuItem.ToolTipText = "Autoplay AudioClip assets when selected"; this.autoPlayAudioAssetsToolStripMenuItem.CheckedChanged += new System.EventHandler(this.autoPlayAudioAssetsToolStripMenuItem_CheckedChanged); @@ -347,7 +359,7 @@ // this.useDumpTreeViewToolStripMenuItem.CheckOnClick = true; this.useDumpTreeViewToolStripMenuItem.Name = "useDumpTreeViewToolStripMenuItem"; - this.useDumpTreeViewToolStripMenuItem.Size = new System.Drawing.Size(241, 22); + this.useDumpTreeViewToolStripMenuItem.Size = new System.Drawing.Size(243, 22); this.useDumpTreeViewToolStripMenuItem.Text = "Use tree view to display dump"; this.useDumpTreeViewToolStripMenuItem.CheckedChanged += new System.EventHandler(this.useDumpTreeViewToolStripMenuItem_CheckedChanged); // @@ -357,7 +369,7 @@ this.buildTreeStructureToolStripMenuItem.CheckOnClick = true; this.buildTreeStructureToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.buildTreeStructureToolStripMenuItem.Name = "buildTreeStructureToolStripMenuItem"; - this.buildTreeStructureToolStripMenuItem.Size = new System.Drawing.Size(241, 22); + this.buildTreeStructureToolStripMenuItem.Size = new System.Drawing.Size(243, 22); this.buildTreeStructureToolStripMenuItem.Text = "Build tree structure"; this.buildTreeStructureToolStripMenuItem.ToolTipText = "You can disable tree structure building if you don\'t use the Scene Hierarchy tab"; this.buildTreeStructureToolStripMenuItem.CheckedChanged += new System.EventHandler(this.buildTreeStructureToolStripMenuItem_CheckedChanged); @@ -376,7 +388,7 @@ this.importOptionsToolStripSeparator, this.saveOptionsToDiskToolStripMenuItem}); this.importOptionsToolStripMenuItem.Name = "importOptionsToolStripMenuItem"; - this.importOptionsToolStripMenuItem.Size = new System.Drawing.Size(241, 22); + this.importOptionsToolStripMenuItem.Size = new System.Drawing.Size(243, 22); this.importOptionsToolStripMenuItem.Text = "Import options"; this.importOptionsToolStripMenuItem.DropDownClosed += new System.EventHandler(this.importOptions_DropDownClose); this.importOptionsToolStripMenuItem.DropDownOpened += new System.EventHandler(this.importOptions_DropDownOpened); @@ -479,7 +491,7 @@ // showExpOpt // this.showExpOpt.Name = "showExpOpt"; - this.showExpOpt.Size = new System.Drawing.Size(241, 22); + this.showExpOpt.Size = new System.Drawing.Size(243, 22); this.showExpOpt.Text = "Export options"; this.showExpOpt.Click += new System.EventHandler(this.showExpOpt_Click); // @@ -1858,6 +1870,7 @@ private System.Windows.Forms.ToolStripComboBox customBlockCompressionComboBox; private System.Windows.Forms.ToolStripMenuItem saveOptionsToDiskToolStripMenuItem; private System.Windows.Forms.ToolStripSeparator importOptionsToolStripSeparator; + private System.Windows.Forms.ToolStripMenuItem meshLazyLoadToolStripMenuItem; } } diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index d978b77..071e22c 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -145,6 +145,7 @@ namespace AssetStudioGUI useAssetLoadingViaTypetreeToolStripMenuItem.Checked = Properties.Settings.Default.useTypetreeLoading; useDumpTreeViewToolStripMenuItem.Checked = Properties.Settings.Default.useDumpTreeView; autoPlayAudioAssetsToolStripMenuItem.Checked = Properties.Settings.Default.autoplayAudio; + meshLazyLoadToolStripMenuItem.Checked = Properties.Settings.Default.meshLazyLoad; customBlockCompressionComboBox.SelectedIndex = 0; customBlockInfoCompressionComboBox.SelectedIndex = 0; assetsManager.Options.BundleOptions.DecompressToDisk = Properties.Settings.Default.decompressToDisk; @@ -1308,6 +1309,8 @@ namespace AssetStudioGUI private void PreviewMesh(Mesh m_Mesh) { + m_Mesh.ProcessData(); + if (m_Mesh.m_VertexCount > 0) { viewMatrixData = Matrix4.CreateRotationY(-MathF.PI / 4) * Matrix4.CreateRotationX(-MathF.PI / 6); @@ -2652,6 +2655,13 @@ namespace AssetStudioGUI Properties.Settings.Default.Save(); } + private void meshLazyLoadToolStripMenuItem_CheckedChanged(object sender, EventArgs e) + { + Properties.Settings.Default.meshLazyLoad = meshLazyLoadToolStripMenuItem.Checked; + assetsManager.MeshLazyLoad = meshLazyLoadToolStripMenuItem.Checked; + Properties.Settings.Default.Save(); + } + private static void FbxInitOptions(string base64String) { if (string.IsNullOrEmpty(base64String)) diff --git a/AssetStudioGUI/Exporter.cs b/AssetStudioGUI/Exporter.cs index b01929a..42268bf 100644 --- a/AssetStudioGUI/Exporter.cs +++ b/AssetStudioGUI/Exporter.cs @@ -147,12 +147,16 @@ namespace AssetStudioGUI private static bool ExportMesh(AssetItem item, string exportPath) { var m_Mesh = (Mesh)item.Asset; + m_Mesh.ProcessData(); + if (m_Mesh.m_VertexCount <= 0) return false; if (!TryExportFile(exportPath, item, ".obj", out var exportFullPath)) return false; + var sb = new StringBuilder(); sb.AppendLine("g " + m_Mesh.m_Name); + #region Vertices if (m_Mesh.m_Vertices == null || m_Mesh.m_Vertices.Length == 0) { diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 65694ad..86d69a6 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -346,5 +346,17 @@ namespace AssetStudioGUI.Properties { this["overwriteExistingFiles"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool meshLazyLoad { + get { + return ((bool)(this["meshLazyLoad"])); + } + set { + this["meshLazyLoad"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index 6ec4ac2..65bc0b1 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -83,5 +83,8 @@ False + + True + \ No newline at end of file diff --git a/AssetStudioUtility/ModelConverter.cs b/AssetStudioUtility/ModelConverter.cs index 8e84d0d..85d8924 100644 --- a/AssetStudioUtility/ModelConverter.cs +++ b/AssetStudioUtility/ModelConverter.cs @@ -256,6 +256,8 @@ namespace AssetStudio var mesh = GetMesh(meshR); if (mesh == null) return; + + mesh.ProcessData(); var iMesh = new ImportedMesh(); meshR.m_GameObject.TryGet(out var m_GameObject2); iMesh.Path = GetTransformPath(m_GameObject2.m_Transform);