From 682745177b317006a67226600b40fe2d788303b8 Mon Sep 17 00:00:00 2001 From: Lucidiot Date: Sun, 6 Dec 2020 00:06:08 +0100 Subject: [PATCH] Add quick and dirty path extractor --- AssetStudio.sln | 14 +++ PathIDExtractor/App.config | 6 + PathIDExtractor/PathIDExtractor.csproj | 59 ++++++++++ PathIDExtractor/Program.cs | 128 +++++++++++++++++++++ PathIDExtractor/Properties/AssemblyInfo.cs | 36 ++++++ 5 files changed, 243 insertions(+) create mode 100644 PathIDExtractor/App.config create mode 100644 PathIDExtractor/PathIDExtractor.csproj create mode 100644 PathIDExtractor/Program.cs create mode 100644 PathIDExtractor/Properties/AssemblyInfo.cs diff --git a/AssetStudio.sln b/AssetStudio.sln index 83a8f15..8e2739d 100644 --- a/AssetStudio.sln +++ b/AssetStudio.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Texture2DDecoderWrapper", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetStudio.PInvoke", "AssetStudio.PInvoke\AssetStudio.PInvoke.csproj", "{40C796B5-88CE-4ADC-ACD6-2F4862B7F136}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathIDExtractor", "PathIDExtractor\PathIDExtractor.csproj", "{D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +133,18 @@ Global {40C796B5-88CE-4ADC-ACD6-2F4862B7F136}.Release|x64.Build.0 = Release|Any CPU {40C796B5-88CE-4ADC-ACD6-2F4862B7F136}.Release|x86.ActiveCfg = Release|Any CPU {40C796B5-88CE-4ADC-ACD6-2F4862B7F136}.Release|x86.Build.0 = Release|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Debug|x64.Build.0 = Debug|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Debug|x86.Build.0 = Debug|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Release|x64.ActiveCfg = Release|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Release|x64.Build.0 = Release|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Release|x86.ActiveCfg = Release|Any CPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PathIDExtractor/App.config b/PathIDExtractor/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/PathIDExtractor/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PathIDExtractor/PathIDExtractor.csproj b/PathIDExtractor/PathIDExtractor.csproj new file mode 100644 index 0000000..a1474d6 --- /dev/null +++ b/PathIDExtractor/PathIDExtractor.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {D8AE6E83-EC67-47E3-8DAD-8BA96A67B5E8} + Exe + PathIDExtractor + PathIDExtractor + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {7662f8c2-7bfd-442e-a948-a43b4f7eb06e} + AssetStudio + + + + \ No newline at end of file diff --git a/PathIDExtractor/Program.cs b/PathIDExtractor/Program.cs new file mode 100644 index 0000000..872f8f8 --- /dev/null +++ b/PathIDExtractor/Program.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AssetStudio; + +namespace PathIDExtractor +{ + struct ParsedObject + { + public readonly long pathId; + public readonly string type; + public string name; + + public ParsedObject(long pathId, string type) + { + this.pathId = pathId; + this.type = type; + name = null; + } + + public override string ToString() => $"{pathId},{type},{(name is null ? "" : name)}"; + } + + class Program + { + private static string pathIdsFilePath; + private static string containersFilePath; + + static void ParseObjects (List objects) + { + /* + * As objects have no particular order and some objects hold other objects, + * and we want to keep a reference to each object's container, + * we will keep all the parsed objects and all the containers we find for those objects, + * then re-link them at the end. + */ + var parsedObjects = new Dictionary(objects.Count); + var containers = new List<(PPtr, string)>(); + + foreach (var asset in objects) + { + var parsedObject = new ParsedObject(asset.m_PathID, asset.type.ToString()); + // Find the name of an object depending on its type + switch (asset) + { + case Shader m_Shader: + parsedObject.name = m_Shader.m_ParsedForm?.m_Name ?? m_Shader.m_Name; + break; + case Animator m_Animator: + if (m_Animator.m_GameObject.TryGet(out var gameObject)) parsedObject.name = gameObject.m_Name; + break; + case MonoBehaviour m_MonoBehaviour: + if (m_MonoBehaviour.m_Name == "" && m_MonoBehaviour.m_Script.TryGet(out var m_Script)) parsedObject.name = m_Script.m_ClassName; + else parsedObject.name = m_MonoBehaviour.m_Name; + break; + case AssetBundle m_AssetBundle: + foreach (var m_Container in m_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++) + { + containers.Add((m_AssetBundle.m_PreloadTable[k], m_Container.Key)); + } + } + parsedObject.name = m_AssetBundle.m_Name; + break; + case ResourceManager m_ResourceManager: + foreach (var m_Container in m_ResourceManager.m_Container) + { + containers.Add((m_Container.Value, m_Container.Key)); + } + break; + case NamedObject namedObject: + parsedObject.name = namedObject.m_Name; + break; + } + parsedObjects.Add(asset, parsedObject); + } + File.AppendAllText(pathIdsFilePath, String.Join("\n", parsedObjects.Values.Select(obj => obj.ToString())) + "\n"); + File.AppendAllText(containersFilePath, String.Join("\n", containers.Select(tuple => tuple.Item1.TryGet(out var obj) ? $"{obj.m_PathID},{tuple.Item2}" : null).Where(output => output != null)) + "\n"); + Console.WriteLine($"Parsed {parsedObjects.Count} values"); + } + + static void Main(string[] args) + { + Console.Title = "Path ID Extractor"; + Console.BackgroundColor = ConsoleColor.DarkRed; + Console.ForegroundColor = ConsoleColor.White; + Console.Clear(); + Console.WriteLine("--- lucidiot's path ID extractor ---"); + + string basePath; + do + { + Console.WriteLine("Enter the base path of all Unity data files to analyze:"); + basePath = Path.GetFullPath(Console.ReadLine()); + } while (!Directory.Exists(basePath)); + + var files = Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories); + Console.WriteLine($"{files.Length} files to parse"); + + string baseOutputPath; + do + { + Console.WriteLine("Enter the base output path:"); + baseOutputPath = Path.GetFullPath(Console.ReadLine()); + } while (!Directory.Exists(baseOutputPath)); + pathIdsFilePath = Path.Combine(baseOutputPath, "path_ids.csv"); + containersFilePath = Path.Combine(baseOutputPath, "containers.csv"); + + Console.WriteLine($"Outputting to {pathIdsFilePath} and {containersFilePath}"); + File.WriteAllText(pathIdsFilePath, "path_id,type,name\n"); + File.WriteAllText(containersFilePath, "path_id,container\n"); + + var manager = new AssetsManager(); + foreach (var filePath in files) + { + Console.WriteLine($"Loading {filePath.Substring(basePath.Length)}"); + manager.LoadFiles(filePath); + manager.assetsFileList.ForEach(file => ParseObjects(file.Objects)); + manager.Clear(); + } + } + } +} diff --git a/PathIDExtractor/Properties/AssemblyInfo.cs b/PathIDExtractor/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c53d230 --- /dev/null +++ b/PathIDExtractor/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("PathIDExtractor")] +[assembly: AssemblyDescription("PathID extractor from Unity asset bundles")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PathIDExtractor")] +[assembly: AssemblyCopyright("Copyright © Lucidiot 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("d8ae6e83-ec67-47e3-8dad-8ba96a67b5e8")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]