mirror of
https://github.com/AssetRipper/Cpp2IL.git
synced 2026-11-13 04:26:33 -10:00
196 lines
7.1 KiB
C#
196 lines
7.1 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using Cpp2IL.Core.Api;
|
|
using Cpp2IL.Core.Logging;
|
|
using Cpp2IL.Core.Model.Contexts;
|
|
|
|
namespace Cpp2IL.Core.ProcessingLayers;
|
|
|
|
public class DeobfuscationMapProcessingLayer : Cpp2IlProcessingLayer
|
|
{
|
|
private static bool _logUnknownTypes;
|
|
private static int _numUnknownTypes;
|
|
|
|
public override string Name => "Deobfuscation Map";
|
|
public override string Id => "deobfmap";
|
|
|
|
public override void Process(ApplicationAnalysisContext appContext, Action<int, int>? progressCallback = null)
|
|
{
|
|
var mapPath = appContext.GetExtraData<string>("deobf-map-path");
|
|
_logUnknownTypes = appContext.GetExtraData<string>("deobf-map-log-unknown-types") != null;
|
|
|
|
if (mapPath == null)
|
|
{
|
|
Logger.WarnNewline("No deobfuscation map specified - processor will not run. You need to provide the deobf-map-path, either by programmatically adding it as extra data in the app context, or by specifying it in the --processor-config command line option.", "DeobfuscationMapProcessingLayer");
|
|
return;
|
|
}
|
|
|
|
byte[] deobfMap;
|
|
if (mapPath.StartsWith("http://") || mapPath.StartsWith("https://"))
|
|
{
|
|
try
|
|
{
|
|
Logger.InfoNewline($"Downloading deobfuscation map from {mapPath}...", "DeobfuscationMapProcessingLayer");
|
|
//Blocking call, but it's fine. All of cpp2il is blocking.
|
|
deobfMap = new HttpClient().GetByteArrayAsync(mapPath).Result;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.ErrorNewline($"Could not download remote deobfuscation map from {mapPath}: {e.Message}. Deobfuscation will not run.", "DeobfuscationMapProcessingLayer");
|
|
return;
|
|
}
|
|
}
|
|
else if (File.Exists(mapPath))
|
|
{
|
|
deobfMap = File.ReadAllBytes(mapPath);
|
|
}
|
|
else
|
|
{
|
|
Logger.ErrorNewline($"File not found: {Path.GetFullPath(mapPath)}. Deobfuscation will not run.", "DeobfuscationMapProcessingLayer");
|
|
return;
|
|
}
|
|
|
|
Logger.InfoNewline("Parsing deobfuscation map...", "DeobfuscationMapProcessingLayer");
|
|
|
|
string deobfMapContent;
|
|
//Check if gzipped.
|
|
if (deobfMap.Length > 2 && deobfMap[0] == 0x1F && deobfMap[1] == 0x8B)
|
|
{
|
|
using var ms = new MemoryStream(deobfMap);
|
|
using var gzip = new GZipStream(ms, CompressionMode.Decompress);
|
|
using var reader = new StreamReader(gzip);
|
|
|
|
deobfMapContent = reader.ReadToEnd();
|
|
}
|
|
else
|
|
{
|
|
deobfMapContent = Encoding.UTF8.GetString(deobfMap);
|
|
}
|
|
|
|
Deobfuscate(appContext, deobfMapContent);
|
|
}
|
|
|
|
private static void Deobfuscate(ApplicationAnalysisContext appContext, string deobfMap)
|
|
{
|
|
var lines = deobfMap.Split('\n');
|
|
_numUnknownTypes = 0;
|
|
|
|
var typeLines = lines.Where(t => !t.Contains("::"));
|
|
var memberLines = lines.Where(t => t.Contains("::"));
|
|
|
|
Logger.InfoNewline($"Applying deobfuscation map ({lines.Length} entries)...", "DeobfuscationMapProcessingLayer");
|
|
foreach (var line in typeLines)
|
|
ProcessLine(appContext, line);
|
|
|
|
foreach (var line in memberLines)
|
|
ProcessLine(appContext, line);
|
|
|
|
if (!_logUnknownTypes)
|
|
{
|
|
//Print a summary if we didn't log each individually.
|
|
Logger.WarnNewline($"Encountered {_numUnknownTypes} unknown types in deobf map. Add the configuration option deobf-map-log-unknown-types=yes to log them.", "DeobfuscationMapProcessingLayer");
|
|
}
|
|
}
|
|
|
|
private static void ProcessLine(ApplicationAnalysisContext appContext, string line)
|
|
{
|
|
//Obfuscated;deobfuscated[;priority]
|
|
var split = line.Split(';');
|
|
|
|
if (split.Length < 2)
|
|
return;
|
|
|
|
var (obfuscated, deobfuscated) = (split[0], split[1]);
|
|
|
|
ProcessRemapping(appContext, obfuscated, deobfuscated);
|
|
}
|
|
|
|
private static void ProcessRemapping(ApplicationAnalysisContext appContext, string obfuscated, string deobfuscated)
|
|
{
|
|
if (obfuscated.Contains("::"))
|
|
{
|
|
var index = obfuscated.IndexOf("::", StringComparison.Ordinal);
|
|
var typeName = obfuscated[..index];
|
|
var memberName = obfuscated[(index + 2)..];
|
|
|
|
var type = GetTypeByObfName(appContext, typeName);
|
|
|
|
var member = type?.Fields.FirstOrDefault(f => f.Name == memberName);
|
|
if (member == null)
|
|
{
|
|
//TODO Non-fields? Currently only used for enums
|
|
return;
|
|
}
|
|
|
|
member.OverrideName = deobfuscated;
|
|
return;
|
|
}
|
|
|
|
var matchingType = GetTypeByObfName(appContext, obfuscated);
|
|
|
|
if (matchingType == null)
|
|
return;
|
|
|
|
//The way the rename maps work is something like this:
|
|
// If the obfuscated name was a nested type, the deobfuscated name is just the new name of the nested type
|
|
// If the obfuscated name was a top-level type, the deobfuscated name is the new namespace + . + new name
|
|
|
|
// var originalName = matchingType.FullName;
|
|
|
|
if (matchingType.DeclaringType != null)
|
|
{
|
|
matchingType.OverrideName = deobfuscated;
|
|
// Logger.VerboseNewline($"Renamed nested type {originalName} to {matchingType.FullName}", "DeobfuscationMapProcessingLayer");
|
|
return;
|
|
}
|
|
|
|
var lastDot = deobfuscated.LastIndexOf('.');
|
|
|
|
if (lastDot != -1)
|
|
{
|
|
var namespaceName = deobfuscated[..lastDot];
|
|
var typeName = deobfuscated[(lastDot + 1)..];
|
|
|
|
matchingType.OverrideNamespace = namespaceName;
|
|
matchingType.OverrideName = typeName;
|
|
}
|
|
else
|
|
{
|
|
matchingType.OverrideName = deobfuscated;
|
|
}
|
|
|
|
// Logger.VerboseNewline($"Renamed {originalName} to {matchingType.FullName}", "DeobfuscationMapProcessingLayer");
|
|
}
|
|
|
|
private static TypeAnalysisContext? GetTypeByObfName(ApplicationAnalysisContext appContext, string obfuscated)
|
|
{
|
|
if (obfuscated.StartsWith("."))
|
|
//If there's no namespace, strip the leading dot
|
|
obfuscated = obfuscated[1..];
|
|
|
|
//Create another copy of obfuscated name with last . replaced with a /, for subclasses
|
|
var lastDot = obfuscated.LastIndexOf('.');
|
|
var withSlash = obfuscated;
|
|
if (lastDot > 0)
|
|
withSlash = obfuscated[..lastDot] + "/" + obfuscated[(lastDot + 1)..];
|
|
|
|
//TODO Change this to a dict at some point and recalculate between type and member names.
|
|
var matchingType = appContext.AllTypes.AsParallel().FirstOrDefault(t => t.FullName == obfuscated || t.FullName == withSlash);
|
|
|
|
if (matchingType == null)
|
|
{
|
|
if (_logUnknownTypes)
|
|
Logger.WarnNewline("Could not find type " + obfuscated, "DeobfuscationMapProcessingLayer");
|
|
else
|
|
_numUnknownTypes++;
|
|
return null;
|
|
}
|
|
|
|
return matchingType;
|
|
}
|
|
}
|