Skip to content

Commit

Permalink
Port UnhollowerPdbGen to Il2CppInterop
Browse files Browse the repository at this point in the history
  • Loading branch information
ds5678 committed Oct 6, 2024
1 parent 9d4599d commit 538e51a
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 9 deletions.
17 changes: 17 additions & 0 deletions Il2CppInterop.Pdb.Generator/Il2CppInterop.Pdb.Generator.csproj
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>
29 changes: 29 additions & 0 deletions Il2CppInterop.Pdb.Generator/MethodAddressToTokenMap.cs
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;
}
}
157 changes: 157 additions & 0 deletions Il2CppInterop.Pdb.Generator/MsPdbCore.cs
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);
}
}
101 changes: 101 additions & 0 deletions Il2CppInterop.Pdb.Generator/Program.cs
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;
}
}
17 changes: 17 additions & 0 deletions Il2CppInterop.Pdb.Generator/README.md
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\`.
24 changes: 15 additions & 9 deletions Il2CppInterop.sln
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,33 +10,35 @@ 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}"
ProjectSection(SolutionItems) = preProject
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
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
.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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 538e51a

Please sign in to comment.