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")]