From 86bb9caf00d2a2e8e85a47b8c1b8938cb92d88ec Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Sun, 8 Sep 2024 10:27:14 -0700
Subject: [PATCH] Port UnhollowerPdbGen to Il2CppInterop
---
.../Il2CppInterop.Pdb.Generator.csproj | 17 ++
.../MethodAddressToTokenMap.cs | 29 ++++
Il2CppInterop.Pdb.Generator/MsPdbCore.cs | 157 ++++++++++++++++++
Il2CppInterop.Pdb.Generator/Program.cs | 101 +++++++++++
Il2CppInterop.sln | 24 ++-
5 files changed, 319 insertions(+), 9 deletions(-)
create mode 100644 Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
create mode 100644 Il2CppInterop.Pdb.Generator/MethodAddressToTokenMap.cs
create mode 100644 Il2CppInterop.Pdb.Generator/MsPdbCore.cs
create mode 100644 Il2CppInterop.Pdb.Generator/Program.cs
diff --git a/Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj b/Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
new file mode 100644
index 00000000..fdb093bc
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.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/MsPdbCore.cs b/Il2CppInterop.Pdb.Generator/MsPdbCore.cs
new file mode 100644
index 00000000..0f5c5821
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/MsPdbCore.cs
@@ -0,0 +1,157 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+// Source/reference: https://github.com/microsoft/microsoft-pdb, MIT license
+namespace Il2CppInterop.Pdb.Generator;
+
+enum PDBErrors : int
+{
+ EC_OK, // no problem
+ EC_USAGE, // invalid parameter or call order
+ EC_OUT_OF_MEMORY, // out of heap
+ EC_FILE_SYSTEM, // "pdb name", can't write file, out of disk, etc.
+ EC_NOT_FOUND, // "pdb name", PDB file not found
+ EC_INVALID_SIG, // "pdb name", PDB::OpenValidate() and its clients only
+ EC_INVALID_AGE, // "pdb name", PDB::OpenValidate() and its clients only
+ EC_PRECOMP_REQUIRED, // "obj name", Mod::AddTypes() only
+ EC_OUT_OF_TI, // "pdb name", TPI::QueryTiForCVRecord() only
+ EC_NOT_IMPLEMENTED, // -
+ EC_V1_PDB, // "pdb name", PDB::Open* only (obsolete)
+ EC_UNKNOWN_FORMAT = EC_V1_PDB, // pdb can't be opened because it has newer versions of stuff
+ EC_FORMAT, // accessing pdb with obsolete format
+ EC_LIMIT,
+ EC_CORRUPT, // cv info corrupt, recompile mod
+ EC_TI16, // no 16-bit type interface present
+ EC_ACCESS_DENIED, // "pdb name", PDB file read-only
+ EC_ILLEGAL_TYPE_EDIT, // trying to edit types in read-only mode
+ EC_INVALID_EXECUTABLE, // not recogized as a valid executable
+ EC_DBG_NOT_FOUND, // A required .DBG file was not found
+ EC_NO_DEBUG_INFO, // No recognized debug info found
+ EC_INVALID_EXE_TIMESTAMP, // Invalid timestamp on Openvalidate of exe
+ EC_CORRUPT_TYPEPOOL, // A corrupted type record was found in a PDB
+ EC_DEBUG_INFO_NOT_IN_PDB, // returned by OpenValidateX
+ EC_RPC, // Error occured during RPC
+ EC_UNKNOWN, // Unknown error
+ EC_BAD_CACHE_PATH, // bad cache location specified with symsrv
+ EC_CACHE_FULL, // symsrv cache is full
+ EC_TOO_MANY_MOD_ADDTYPE, // Addtype is called more then once per mod
+ EC_MAX
+}
+
+[Flags]
+enum CV_PUBSYMFLAGS_e : int
+{
+ cvpsfNone = 0,
+ cvpsfCode = 0x00000001,
+ cvpsfFunction = 0x00000002,
+ cvpsfManaged = 0x00000004,
+ cvpsfMSIL = 0x00000008,
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct PdbPtr
+{
+ public IntPtr InnerPtr;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct DbiPtr
+{
+ public IntPtr InnerPtr;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct ModPtr
+{
+ public IntPtr InnerPtr;
+}
+
+public static unsafe class MsPdbCore
+{
+ private const string dllName = "mspdbcore.dll";
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool PDBOpen2W(char* wszPDB, byte* szMode, out PDBErrors pec, char* wszError, nuint cchErrMax, out PdbPtr pppdb);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool PDBCommit(PdbPtr ppdb);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool PDBOpenDBI(PdbPtr ppdb, byte* szMode, byte* szTarget, out DbiPtr ppdbi);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool DBIOpenModW(DbiPtr pdbi, char* szModule, char* szFile, out ModPtr ppmod);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool DBIAddPublic2(DbiPtr pdbi, byte* szPublic, ushort isect, int off, CV_PUBSYMFLAGS_e cvpsf = 0);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool ModAddPublic2(ModPtr pmod, byte* szPublic, ushort isect, int off, CV_PUBSYMFLAGS_e cvpsf = 0);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool DBIAddSec(DbiPtr pdbi, ushort isect, ushort flags, int off, int cb);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool ModClose(ModPtr ppdb);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool DBIClose(DbiPtr ppdb);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool PDBClose(PdbPtr ppdb);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern bool PDBQuerySignature2(PdbPtr ppdb, out Guid guid);
+
+ [DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern uint PDBQueryAge(PdbPtr ppdb);
+
+
+ internal static bool PDBOpen2W(string wszPDB, string szMode, out PDBErrors pec, out string error, out PdbPtr pppdb)
+ {
+ wszPDB += '\0';
+ szMode += '\0';
+
+ var chars = wszPDB.ToCharArray();
+ var bytes = Encoding.UTF8.GetBytes(szMode);
+ var errorChars = new char[2048];
+ bool result = false;
+
+ fixed (char* cp = chars)
+ fixed (byte* bp = bytes)
+ fixed (char* ep = errorChars)
+ result = PDBOpen2W(cp, bp, out pec, ep, (nuint)errorChars.Length, out pppdb);
+
+ var firstZero = Array.IndexOf(errorChars, '\0');
+ error = new string(errorChars, 0, firstZero);
+
+ return result;
+ }
+
+ internal static bool PDBOpenDBI(PdbPtr ppdb, string szMode, string szTarget, out DbiPtr ppdbi)
+ {
+ szMode += '\0';
+ szTarget += '\0';
+
+ fixed (byte* mb = Encoding.UTF8.GetBytes(szMode))
+ fixed (byte* tb = Encoding.UTF8.GetBytes(szTarget))
+ return PDBOpenDBI(ppdb, mb, tb, out ppdbi);
+ }
+
+ internal static bool DBIOpenModW(DbiPtr pdbi, string szModule, string szFile, out ModPtr ppmod)
+ {
+ szFile += '\0';
+ szModule += '\0';
+
+ fixed (char* fp = szFile)
+ fixed (char* mp = szModule)
+ return DBIOpenModW(pdbi, mp, fp, out ppmod);
+ }
+
+ internal static bool ModAddPublic2(ModPtr pmod, string szPublic, ushort isect, int off, CV_PUBSYMFLAGS_e cvpsf = 0)
+ {
+ szPublic += '\0';
+ fixed (byte* mb = Encoding.UTF8.GetBytes(szPublic))
+ return ModAddPublic2(pmod, mb, isect, off, cvpsf);
+ }
+}
diff --git a/Il2CppInterop.Pdb.Generator/Program.cs b/Il2CppInterop.Pdb.Generator/Program.cs
new file mode 100644
index 00000000..0c10fe85
--- /dev/null
+++ b/Il2CppInterop.Pdb.Generator/Program.cs
@@ -0,0 +1,101 @@
+using System.Reflection.PortableExecutable;
+
+namespace Il2CppInterop.Pdb.Generator;
+
+internal static class Program
+{
+ public static void Main(string[] args)
+ {
+ if (args.Length <= 1)
+ {
+ Console.WriteLine($"Usage: UnhollowerPdbGen.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.cvpsfFunction);
+ }
+
+ 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.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