diff --git a/Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj b/Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
new file mode 100644
index 00000000..a064321a
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net9.0
+ Exe
+ Il2CppInterop.Pdb.Generator
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Il2CppInterop.Pdb.Generator/MethodAddressToTokenMap.cs b/Il2CppInterop.Pdb.Generator/MethodAddressToTokenMap.cs
new file mode 100644
index 00000000..a06323a0
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/MethodAddressToTokenMap.cs
@@ -0,0 +1,29 @@
+using AsmResolver.DotNet;
+using Il2CppInterop.Common.Maps;
+
+#nullable enable
+
+namespace Il2CppInterop.Pdb.Generator;
+
+public class MethodAddressToTokenMap : MethodAddressToTokenMapBase
+{
+ public MethodAddressToTokenMap(string filePath) : base(filePath)
+ {
+ }
+
+ protected override AssemblyDefinition? LoadAssembly(string assemblyName)
+ {
+ var filesDirt = Path.GetDirectoryName(myFilePath)!;
+ assemblyName = assemblyName.Substring(0, assemblyName.IndexOf(','));
+ return AssemblyDefinition.FromFile(Path.Combine(filesDirt, assemblyName + ".dll"));
+ }
+
+ protected override MethodDefinition? ResolveMethod(AssemblyDefinition? assembly, int token)
+ {
+ if (assembly?.ManifestModule?.TryLookupMember(token, out MethodDefinition? result) ?? false)
+ {
+ return result;
+ }
+ return null;
+ }
+}
diff --git a/Il2CppInterop.Pdb.Generator/Program.cs b/Il2CppInterop.Pdb.Generator/Program.cs
new file mode 100644
index 00000000..0727db5f
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/Program.cs
@@ -0,0 +1,102 @@
+using AssetRipper.Bindings.MsPdbCore;
+using System.Reflection.PortableExecutable;
+
+namespace Il2CppInterop.Pdb.Generator;
+
+internal static unsafe class Program
+{
+ public static void Main(string[] args)
+ {
+ if (args.Length <= 1)
+ {
+ Console.WriteLine($"Usage: Il2CppInterop.Pdb.Generator.exe ");
+ }
+ var rootPath = Path.GetDirectoryName(args[0])!;
+ var map = new MethodAddressToTokenMap(args[1]);
+
+ using var peStream = new FileStream(args[0], FileMode.Open, FileAccess.Read);
+ using var peReader = new PEReader(peStream);
+
+
+ string openError;
+ PDBErrors err;
+ var pdbFilePath = Path.Combine(rootPath, "GameAssembly.pdb");
+ MsPdbCore.PDBOpen2W(pdbFilePath, "w", out err, out openError, out var pdb);
+
+ MsPdbCore.PDBOpenDBI(pdb, "w", "", out var dbi);
+
+ MsPdbCore.DBIOpenModW(dbi, "__Globals", "__Globals", out var mod);
+
+ ushort secNum = 1;
+ ushort i2cs = 1;
+ foreach (var sectionHeader in peReader.PEHeaders.SectionHeaders)
+ {
+ if (sectionHeader.Name == "il2cpp") i2cs = secNum;
+ MsPdbCore.DBIAddSec(dbi, secNum++, 0 /* TODO? */, sectionHeader.VirtualAddress, sectionHeader.VirtualSize);
+ }
+
+ foreach (var valueTuple in map)
+ {
+ ushort targetSect = 0;
+ long tsva = 0;
+ ushort sc = 1;
+ foreach (var sectionHeader in peReader.PEHeaders.SectionHeaders)
+ {
+ if (valueTuple.Item1 > sectionHeader.VirtualAddress)
+ {
+ targetSect = sc;
+ tsva = sectionHeader.VirtualAddress;
+ }
+ else
+ break;
+
+ sc++;
+ }
+
+ if (targetSect == 0) throw new ApplicationException("Bad segment");
+ MsPdbCore.ModAddPublic2(mod, valueTuple.Item2.FullName, targetSect, (int)(valueTuple.Item1 - tsva * 2), CV_PUBSYMFLAGS_e.Function);
+ }
+
+ MsPdbCore.ModClose(mod);
+ MsPdbCore.DBIClose(dbi);
+
+ MsPdbCore.PDBCommit(pdb);
+
+ MsPdbCore.PDBQuerySignature2(pdb, out var wrongGuid);
+
+ MsPdbCore.PDBClose(pdb);
+
+ // Hack: manually replace guid and age in generated .pdb, because there's no API on mspdbcore to set them manually
+ var targetDebugInfo = peReader.ReadCodeViewDebugDirectoryData(peReader.ReadDebugDirectory()
+ .Single(it => it.Type == DebugDirectoryEntryType.CodeView));
+
+ var wrongGuidBytes = wrongGuid.ToByteArray();
+ var allPdbBytes = File.ReadAllBytes(pdbFilePath);
+
+ var patchTarget = IndexOfBytes(allPdbBytes, wrongGuidBytes);
+ targetDebugInfo.Guid.TryWriteBytes(allPdbBytes.AsSpan(patchTarget));
+
+ Console.WriteLine(targetDebugInfo.Guid);
+ Console.WriteLine(targetDebugInfo.Age);
+
+ BitConverter.TryWriteBytes(allPdbBytes.AsSpan(patchTarget - 4), targetDebugInfo.Age);
+ File.WriteAllBytes(pdbFilePath, allPdbBytes);
+ }
+
+ private static int IndexOfBytes(byte[] haystack, byte[] needle)
+ {
+ for (var i = 0; i < haystack.Length - needle.Length; i++)
+ {
+ for (var j = 0; j < needle.Length; j++)
+ {
+ if (haystack[i + j] != needle[j])
+ goto moveOn;
+ }
+
+ return i;
+ moveOn:;
+ }
+
+ return -1;
+ }
+}
diff --git a/Il2CppInterop.Pdb.Generator/README.md b/Il2CppInterop.Pdb.Generator/README.md
new file mode 100644
index 00000000..70be3bb9
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/README.md
@@ -0,0 +1,17 @@
+# PDB generator
+
+This is an executable that can be ran to generate a Microsoft PDB file (debug symbols) for GameAssembly.dll based on unhollower-generated names.
+
+This can be useful for analyzing code of obfuscated games. For unobfuscated games, using [Il2CppInspector](https://github.com/djkaty/Il2CppInspector) might provide better results for code analysis.
+
+Generated PDBs were tested with windbg, lldb, WPA viewer/ETL performance analysis and IDA.
+
+Generated PDBs only include generated methods, and don't include type info, generic method info and IL2CPP internals.
+
+You need to manually copy the following Microsoft-provided libraries from Visual Studio (or other build tools) for this to work. They cannot be redistributed because the license on them is not clear.
+
+ * `mspdbcore.dll`
+ * `msobj140.dll`
+ * `tbbmalloc.dll`
+
+These need to be placed next to the built executable file. Use file search to find `mspdbcore` in the Visual Studio install directory. By default, they are in `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\`.
diff --git a/Il2CppInterop.sln b/Il2CppInterop.sln
index 80d120c6..a4aa5698 100644
--- a/Il2CppInterop.sln
+++ b/Il2CppInterop.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31402.337
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35222.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.Generator", "Il2CppInterop.Generator\Il2CppInterop.Generator.csproj", "{7C3FD45B-A563-47AF-90DF-8B051A8C33A0}"
EndProject
@@ -10,9 +10,9 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
- README.md = README.md
- Directory.Build.props = Directory.Build.props
build.cake = build.cake
+ Directory.Build.props = Directory.Build.props
+ README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{99E71726-78FB-4376-89DA-90E5C08F5CD4}"
@@ -20,11 +20,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentat
Documentation\Class-Injection.md = Documentation\Class-Injection.md
Documentation\Command-Line-Usage.md = Documentation\Command-Line-Usage.md
Documentation\Common-Problems.md = Documentation\Common-Problems.md
- Documentation\Injected-Components-In-Asset-Bundles.md = Documentation\Injected-Components-In-Asset-Bundles.md
Documentation\Implementing-Interfaces.md = Documentation\Implementing-Interfaces.md
+ Documentation\Injected-Components-In-Asset-Bundles.md = Documentation\Injected-Components-In-Asset-Bundles.md
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.CLI", "Il2CppInterop.CLI\Il2CppInterop.CLI.csproj", "{F5AA88D1-5E62-46B8-A11C-3FAC40C25600}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.CLI", "Il2CppInterop.CLI\Il2CppInterop.CLI.csproj", "{F5AA88D1-5E62-46B8-A11C-3FAC40C25600}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{771D8BE3-C373-4754-94EA-4A68B117BAEF}"
ProjectSection(SolutionItems) = preProject
@@ -32,11 +32,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{
.github\workflows\format_check.yml = .github\workflows\format_check.yml
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.StructGenerator", "Il2CppInterop.StructGenerator\Il2CppInterop.StructGenerator.csproj", "{DE781BD4-650F-4ED8-B615-5CB36E6D476A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.StructGenerator", "Il2CppInterop.StructGenerator\Il2CppInterop.StructGenerator.csproj", "{DE781BD4-650F-4ED8-B615-5CB36E6D476A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.Common", "Il2CppInterop.Common\Il2CppInterop.Common.csproj", "{E7D3A81B-11CD-402C-A447-015B00DCA3FD}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.Common", "Il2CppInterop.Common\Il2CppInterop.Common.csproj", "{E7D3A81B-11CD-402C-A447-015B00DCA3FD}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.HarmonySupport", "Il2CppInterop.HarmonySupport\Il2CppInterop.HarmonySupport.csproj", "{EBC23884-3417-4F24-8623-E913C5151CC3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.HarmonySupport", "Il2CppInterop.HarmonySupport\Il2CppInterop.HarmonySupport.csproj", "{EBC23884-3417-4F24-8623-E913C5151CC3}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.Pdb.Generator", "Il2CppInterop.Pdb.Generator\Il2CppInterop.Pdb.Generator.csproj", "{55566454-FF96-4C35-9CF4-479F7130E4D1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -68,6 +70,10 @@ Global
{EBC23884-3417-4F24-8623-E913C5151CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBC23884-3417-4F24-8623-E913C5151CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBC23884-3417-4F24-8623-E913C5151CC3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {55566454-FF96-4C35-9CF4-479F7130E4D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {55566454-FF96-4C35-9CF4-479F7130E4D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {55566454-FF96-4C35-9CF4-479F7130E4D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {55566454-FF96-4C35-9CF4-479F7130E4D1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE