-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port UnhollowerPdbGen to Il2CppInterop
- Loading branch information
Showing
6 changed files
with
336 additions
and
9 deletions.
There are no files selected for viewing
17 changes: 17 additions & 0 deletions
17
Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<OutputType>Exe</OutputType> | ||
<RootNamespace>Il2CppInterop.Pdb.Generator</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="AsmResolver.DotNet" Version="6.0.0-beta.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Il2CppInterop.Common\Il2CppInterop.Common.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using AsmResolver.DotNet; | ||
using Il2CppInterop.Common.Maps; | ||
|
||
#nullable enable | ||
|
||
namespace Il2CppInterop.Pdb.Generator; | ||
|
||
public class MethodAddressToTokenMap : MethodAddressToTokenMapBase<AssemblyDefinition, MethodDefinition> | ||
{ | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: Il2CppInterop.Pdb.Generator.exe <path to GameAssembly.dll> <path to {MethodAddressToTokenMap.FileName}>"); | ||
} | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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\`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters