mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
226 lines
8.3 KiB
C#
226 lines
8.3 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal static class CrashAddressMap
|
|
{
|
|
private const long DefaultTarget = 0x29a79ec;
|
|
|
|
internal static bool Enabled => !_disabled;
|
|
|
|
internal static long TargetRva => _target;
|
|
|
|
private static readonly bool _disabled = IsDisabled();
|
|
|
|
private static readonly long _target = ReadTarget();
|
|
|
|
private static bool IsDisabled()
|
|
{
|
|
try
|
|
{
|
|
var raw = Environment.GetEnvironmentVariable("RADICADE_ADDR_MAP");
|
|
if (string.IsNullOrWhiteSpace(raw)) return false;
|
|
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
|
|
}
|
|
catch { return false; }
|
|
}
|
|
|
|
private static long ReadTarget()
|
|
{
|
|
try
|
|
{
|
|
var raw = Environment.GetEnvironmentVariable("RADICADE_ADDR_TARGET");
|
|
if (string.IsNullOrWhiteSpace(raw)) return DefaultTarget;
|
|
raw = raw.Trim();
|
|
if (raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) raw = raw.Substring(2);
|
|
return long.TryParse(raw, System.Globalization.NumberStyles.HexNumber,
|
|
System.Globalization.CultureInfo.InvariantCulture, out var value) ? value : DefaultTarget;
|
|
}
|
|
catch { return DefaultTarget; }
|
|
}
|
|
|
|
internal static void Start()
|
|
{
|
|
if (!Enabled) return;
|
|
var thread = new System.Threading.Thread(Run) { IsBackground = true, Name = "RadicadeAddrMap" };
|
|
thread.Start();
|
|
}
|
|
|
|
private static void Run()
|
|
{
|
|
|
|
|
|
try { System.Threading.Thread.Sleep(5000); } catch { }
|
|
|
|
var log = RadicadeEacBypassPlugin.LogSource;
|
|
var started = Environment.TickCount;
|
|
|
|
IntPtr baseAddress = IntPtr.Zero;
|
|
var moduleSize = 0L;
|
|
try
|
|
{
|
|
using (var self = System.Diagnostics.Process.GetCurrentProcess())
|
|
{
|
|
foreach (System.Diagnostics.ProcessModule module in self.Modules)
|
|
{
|
|
if (!string.Equals(module.ModuleName, "GameAssembly.dll", StringComparison.OrdinalIgnoreCase)) continue;
|
|
baseAddress = module.BaseAddress;
|
|
moduleSize = module.ModuleMemorySize;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.LogError("[AddrMap] could not locate GameAssembly.dll: " + ex.Message);
|
|
return;
|
|
}
|
|
|
|
if (baseAddress == IntPtr.Zero || moduleSize <= 0)
|
|
{
|
|
log.LogError("[AddrMap] GameAssembly.dll is not loaded - nothing to map");
|
|
return;
|
|
}
|
|
|
|
var gameRoot = Path.GetDirectoryName(UnityEngine.Application.dataPath) ?? ".";
|
|
var path = Path.Combine(gameRoot, "BepInEx", "radicade-addr-map.txt");
|
|
|
|
var baseValue = baseAddress.ToInt64();
|
|
var mapped = 0;
|
|
var images = 0;
|
|
var unmappable = 0;
|
|
var bestBelow = -1L;
|
|
string bestBelowName = null;
|
|
long bestAbove = -1;
|
|
string bestAboveName = null;
|
|
var failures = 0;
|
|
|
|
try
|
|
{
|
|
using (var writer = new StreamWriter(path, false))
|
|
{
|
|
writer.WriteLine("# il2cpp RVA -> method map, written by RadicadeAugustClient (run 24)");
|
|
writer.WriteLine("# GameAssembly.dll base 0x" + baseValue.ToString("x") + ", mapped size 0x" + moduleSize.ToString("x"));
|
|
writer.WriteLine("# rva<TAB>image<TAB>Namespace.Class::Method - rva is relative to the base above");
|
|
|
|
unsafe
|
|
{
|
|
var domain = Il2CppInterop.Runtime.IL2CPP.il2cpp_domain_get();
|
|
if (domain == IntPtr.Zero)
|
|
{
|
|
log.LogError("[AddrMap] no il2cpp domain; nothing to map");
|
|
return;
|
|
}
|
|
|
|
uint assemblyCount = 0;
|
|
var assemblies = Il2CppInterop.Runtime.IL2CPP.il2cpp_domain_get_assemblies(domain, ref assemblyCount);
|
|
for (uint a = 0; a < assemblyCount; a++)
|
|
{
|
|
var image = Il2CppInterop.Runtime.IL2CPP.il2cpp_assembly_get_image(assemblies[a]);
|
|
if (image == IntPtr.Zero) continue;
|
|
images++;
|
|
|
|
string imageName;
|
|
try { imageName = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_image_get_name(image)) ?? "?"; }
|
|
catch { imageName = "?"; }
|
|
|
|
var classCount = Il2CppInterop.Runtime.IL2CPP.il2cpp_image_get_class_count(image);
|
|
for (uint c = 0; c < classCount; c++)
|
|
{
|
|
var klass = Il2CppInterop.Runtime.IL2CPP.il2cpp_image_get_class(image, c);
|
|
if (klass == IntPtr.Zero) continue;
|
|
|
|
string className;
|
|
try
|
|
{
|
|
var namespaze = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_class_get_namespace(klass));
|
|
var name = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_class_get_name(klass));
|
|
className = string.IsNullOrEmpty(namespaze) ? name : namespaze + "." + name;
|
|
}
|
|
catch { className = "?"; failures++; }
|
|
|
|
var iterator = IntPtr.Zero;
|
|
IntPtr methodInfo;
|
|
while ((methodInfo = Il2CppInterop.Runtime.IL2CPP.il2cpp_class_get_methods(klass, ref iterator)) != IntPtr.Zero)
|
|
{
|
|
long entry;
|
|
try { entry = Marshal.ReadIntPtr(methodInfo).ToInt64(); }
|
|
catch { entry = 0; }
|
|
|
|
if (entry <= baseValue || entry >= baseValue + moduleSize)
|
|
{
|
|
|
|
|
|
unmappable++;
|
|
continue;
|
|
}
|
|
|
|
var rva = entry - baseValue;
|
|
string methodName;
|
|
try { methodName = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_method_get_name(methodInfo)) ?? "?"; }
|
|
catch { methodName = "?"; failures++; }
|
|
|
|
writer.WriteLine("0x" + rva.ToString("x") + "\t" + imageName + "\t" + className + "::" + methodName);
|
|
mapped++;
|
|
|
|
var full = className + "::" + methodName;
|
|
if (rva <= _target && rva > bestBelow) { bestBelow = rva; bestBelowName = full; }
|
|
if (rva > _target && (bestAbove < 0 || rva < bestAbove)) { bestAbove = rva; bestAboveName = full; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.LogError("[AddrMap] pass failed after " + mapped + " methods: " + ex);
|
|
return;
|
|
}
|
|
|
|
var elapsed = Environment.TickCount - started;
|
|
log.LogInfo($"[AddrMap] mapped {mapped} methods from {images} images in {elapsed} ms -> {path} ({unmappable} without a mappable entry point)");
|
|
|
|
if (bestBelow < 0)
|
|
{
|
|
log.LogWarning("[AddrMap] no method starts below the target 0x" + _target.ToString("x")
|
|
+ " - the crash is in code no managed method owns (il2cpp runtime or native plugin)."
|
|
+ (bestAboveName != null ? " Nearest above: 0x" + bestAbove.ToString("x") + " " + bestAboveName : string.Empty));
|
|
return;
|
|
}
|
|
|
|
var span = bestAbove > bestBelow ? bestAbove - bestBelow : -1;
|
|
log.LogWarning("[AddrMap] crash target 0x" + _target.ToString("x") + " -> containing method 0x" + bestBelow.ToString("x")
|
|
+ " " + bestBelowName + " (+0x" + (_target - bestBelow).ToString("x") + " into it)"
|
|
+ (span > 0 ? "; next method 0x" + bestAbove.ToString("x") + " " + bestAboveName + " (span 0x" + span.ToString("x") + ")" : string.Empty)
|
|
+ (failures > 0 ? "; " + failures + " name reads failed" : string.Empty));
|
|
}
|
|
}
|