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