obfuz/Editor/ConfigurablePassPolicy.cs

565 lines
20 KiB
C#
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// Copyright 2025 Code Philosophy
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using dnlib.DotNet;
using Obfuz.ObfusPasses;
using Obfuz.Utils;
using System;
using System.Collections.Generic;
using System.Xml;
using UnityEngine;
namespace Obfuz
{
public class ConfigurablePassPolicy
{
class PassRule
{
public ObfuscationPassType? enablePasses;
public ObfuscationPassType? disablePasses;
public ObfuscationPassType? addPasses;
public ObfuscationPassType? removePasses;
public ObfuscationPassType finalPasses;
public void InheritParent(PassRule parentRule, ObfuscationPassType globalEnabledPasses)
{
finalPasses = parentRule.finalPasses;
if (enablePasses != null)
{
finalPasses = enablePasses.Value;
}
if (disablePasses != null)
{
finalPasses = ~disablePasses.Value;
}
if (addPasses != null)
{
finalPasses |= addPasses.Value;
}
if (removePasses != null)
{
finalPasses &= ~removePasses.Value;
}
finalPasses &= globalEnabledPasses;
}
}
class SpecBase
{
public string name;
public NameMatcher nameMatcher;
public PassRule rule;
}
class MethodSpec : SpecBase
{
}
class FieldSpec : SpecBase
{
}
class PropertySpec : SpecBase
{
}
class EventSpec : SpecBase
{
}
class TypeSpec : SpecBase
{
public List<FieldSpec> fields = new List<FieldSpec>();
public List<MethodSpec> methods = new List<MethodSpec>();
public List<PropertySpec> properties = new List<PropertySpec>();
public List<EventSpec> events = new List<EventSpec>();
}
class AssemblySpec
{
public string name;
public NameMatcher nameMatcher;
public PassRule rule;
public List<TypeSpec> types = new List<TypeSpec>();
}
private readonly ObfuscationPassType _enabledPasses;
private readonly HashSet<string> _toObfuscatedAssemblyNames;
private readonly List<AssemblySpec> _assemblySpecs = new List<AssemblySpec>();
private readonly PassRule _defaultPassRule;
private string _curLoadingConfig;
public ConfigurablePassPolicy(IEnumerable<string> toObfuscatedAssemblyNames, ObfuscationPassType enabledPasses, List<string> configFiles)
{
_toObfuscatedAssemblyNames = new HashSet<string>(toObfuscatedAssemblyNames);
_enabledPasses = enabledPasses;
_defaultPassRule = new PassRule { finalPasses = enabledPasses };
LoadConfigs(configFiles);
InheritParentRules(enabledPasses);
}
private void LoadConfigs(IEnumerable<string> configFiles)
{
foreach (var configFile in configFiles)
{
LoadConfig(configFile);
}
}
private void InheritParentRules(ObfuscationPassType enablePasses)
{
var defaultRule = new PassRule
{
enablePasses = enablePasses,
finalPasses = enablePasses,
};
foreach (AssemblySpec assSpec in _assemblySpecs)
{
assSpec.rule.InheritParent(defaultRule, enablePasses);
foreach (TypeSpec typeSpec in assSpec.types)
{
typeSpec.rule.InheritParent(assSpec.rule, enablePasses);
foreach (FieldSpec fieldSpec in typeSpec.fields)
{
fieldSpec.rule.InheritParent(typeSpec.rule, enablePasses);
}
foreach (MethodSpec methodSpec in typeSpec.methods)
{
methodSpec.rule.InheritParent(typeSpec.rule, enablePasses);
}
foreach (PropertySpec propertySpec in typeSpec.properties)
{
propertySpec.rule.InheritParent(typeSpec.rule, enablePasses);
}
foreach (EventSpec eventSpec in typeSpec.events)
{
eventSpec.rule.InheritParent(typeSpec.rule, enablePasses);
}
}
}
}
public void LoadConfig(string configFile)
{
if (string.IsNullOrEmpty(configFile))
{
throw new Exception($"Invalid xml file {configFile}, file name is empty");
}
_curLoadingConfig = configFile;
Debug.Log($"ConfigurablePassPolicy::LoadConfig {configFile}");
var doc = new XmlDocument();
doc.Load(configFile);
var root = doc.DocumentElement;
if (root.Name != "obfuz")
{
throw new Exception($"Invalid xml file {configFile}, root name should be 'obfuz'");
}
foreach (XmlNode node in root.ChildNodes)
{
if (!(node is XmlElement ele))
{
continue;
}
switch (ele.Name)
{
case "assembly":
{
AssemblySpec assSpec = ParseAssembly(ele);
_assemblySpecs.Add(assSpec);
break;
}
default:
{
throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}");
}
}
}
}
(bool, ObfuscationPassType) ParseObfuscationType(string obfuscationPassTypesStr)
{
bool delta = false;
if (obfuscationPassTypesStr[0] == '+' || obfuscationPassTypesStr[0] == '-')
{
delta = true;
obfuscationPassTypesStr = obfuscationPassTypesStr.Substring(1);
}
ObfuscationPassType passType = ObfuscationPassType.None;
foreach (var passName in obfuscationPassTypesStr.Split('|'))
{
if (Enum.TryParse<ObfuscationPassType>(passName, out var pass))
{
passType |= pass;
}
else
{
throw new Exception($"Invalid xml file {_curLoadingConfig}, unknown pass type {passName}");
}
}
return (delta, passType);
}
private PassRule ParseRule(XmlElement ele)
{
var r = new PassRule();
if (ele.HasAttribute("enable"))
{
string enablePassStr = ele.GetAttribute("enable");
if (string.IsNullOrEmpty(enablePassStr))
{
throw new Exception($"Invalid xml file {_curLoadingConfig}, enable attribute is empty");
}
var (delta, passType) = ParseObfuscationType(enablePassStr);
if (delta)
{
r.addPasses = passType;
}
else
{
r.enablePasses = passType;
}
}
if (ele.HasAttribute("disable"))
{
string disablePassStr = ele.GetAttribute("disable");
if (string.IsNullOrEmpty(disablePassStr))
{
throw new Exception($"Invalid xml file {_curLoadingConfig}, disable attribute is empty");
}
var (delta, passType) = ParseObfuscationType(disablePassStr);
if (delta)
{
r.removePasses = passType;
}
else
{
r.disablePasses = passType;
}
}
if (r.enablePasses != null && (r.disablePasses != null || r.addPasses != null || r.removePasses != null))
{
throw new Exception($"Invalid xml file {_curLoadingConfig}, enable and disable can't be used together");
}
if (r.disablePasses != null && (r.enablePasses != null || r.addPasses != null || r.removePasses != null))
{
throw new Exception($"Invalid xml file {_curLoadingConfig}, disable and enable can't be used together");
}
return r;
}
private AssemblySpec ParseAssembly(XmlElement ele)
{
var assemblySpec = new AssemblySpec();
string name = ele.GetAttribute("name");
if (!_toObfuscatedAssemblyNames.Contains(name))
{
throw new Exception($"Invalid xml file {_curLoadingConfig}, assembly name {name} isn't in toObfuscatedAssemblyNames");
}
assemblySpec.name = name;
assemblySpec.nameMatcher = new NameMatcher(name);
assemblySpec.rule = ParseRule(ele);
var types = assemblySpec.types;
foreach (XmlNode node in ele.ChildNodes)
{
if (!(node is XmlElement childEle))
{
continue;
}
switch (childEle.Name)
{
case "type":
{
types.Add(ParseType(childEle));
break;
}
default:
{
throw new Exception($"Invalid xml file, unknown node {childEle.Name}");
}
}
}
return assemblySpec;
}
private TypeSpec ParseType(XmlElement element)
{
var typeSpec = new TypeSpec();
string name = element.GetAttribute("name");
typeSpec.name = name;
typeSpec.nameMatcher = new NameMatcher(name);
typeSpec.rule = ParseRule(element);
List<FieldSpec> fields = typeSpec.fields;
List<MethodSpec> methods = typeSpec.methods;
List<PropertySpec> properties = typeSpec.properties;
List<EventSpec> events = typeSpec.events;
foreach (XmlNode node in element.ChildNodes)
{
if (!(node is XmlElement ele))
{
continue;
}
switch (ele.Name)
{
case "field":
{
fields.Add(ParseField(ele));
break;
}
case "method":
{
methods.Add(ParseMethod(ele));
break;
}
case "property":
{
properties.Add(ParseProperty(ele));
break;
}
case "event":
{
events.Add(ParseEvent(ele));
break;
}
default:
{
throw new Exception($"Invalid xml file, unknown node {ele.Name}");
}
}
}
return typeSpec;
}
private void ParseSpecObject(XmlElement element, SpecBase obj)
{
string name = element.GetAttribute("name");
obj.name = name;
obj.nameMatcher = new NameMatcher(name);
obj.rule = ParseRule(element);
}
private FieldSpec ParseField(XmlElement element)
{
var fieldSpec = new FieldSpec();
ParseSpecObject(element, fieldSpec);
return fieldSpec;
}
private MethodSpec ParseMethod(XmlElement element)
{
var methodSpec = new MethodSpec();
ParseSpecObject(element, methodSpec);
return methodSpec;
}
private PropertySpec ParseProperty(XmlElement element)
{
var propertySpec = new PropertySpec();
ParseSpecObject(element, propertySpec);
return propertySpec;
}
private EventSpec ParseEvent(XmlElement element)
{
var eventSpec = new EventSpec();
ParseSpecObject(element, eventSpec);
return eventSpec;
}
private readonly Dictionary<ModuleDef, (AssemblySpec, PassRule)> _modulePassRuleCaches = new Dictionary<ModuleDef, (AssemblySpec, PassRule)>();
private readonly Dictionary<TypeDef, (TypeSpec, PassRule)> _typePassRuleCaches = new Dictionary<TypeDef, (TypeSpec, PassRule)>();
private readonly Dictionary<MethodDef, (MethodSpec, PassRule)> _methodPassRuleCaches = new Dictionary<MethodDef, (MethodSpec, PassRule)>();
private readonly Dictionary<FieldDef, (FieldSpec, PassRule)> _fieldPassRuleCaches = new Dictionary<FieldDef, (FieldSpec, PassRule)>();
private readonly Dictionary<PropertyDef, (PropertySpec, PassRule)> _propertyPassRuleCaches = new Dictionary<PropertyDef, (PropertySpec, PassRule)>();
private readonly Dictionary<EventDef, (EventSpec, PassRule)> _eventPassRuleCaches = new Dictionary<EventDef, (EventSpec, PassRule)>();
private (AssemblySpec, PassRule) GetAssemblySpec(ModuleDef module)
{
if (!_modulePassRuleCaches.TryGetValue(module, out var result))
{
result = (null, _defaultPassRule);
string assName = module.Assembly.Name;
foreach (var ass in _assemblySpecs)
{
if (ass.nameMatcher.IsMatch(assName))
{
result = (ass, ass.rule);
break;
}
}
_modulePassRuleCaches.Add(module, result);
}
return result;
}
private (TypeSpec, PassRule) GetTypeSpec(TypeDef type)
{
if (!_typePassRuleCaches.TryGetValue(type, out var result))
{
var assResult = GetAssemblySpec(type.Module);
result = (null, assResult.Item2);
if (assResult.Item1 != null)
{
string typeName = type.FullName;
foreach (var typeSpec in assResult.Item1.types)
{
if (typeSpec.nameMatcher.IsMatch(typeName))
{
result = (typeSpec, typeSpec.rule);
break;
}
}
}
_typePassRuleCaches.Add(type, result);
}
return result;
}
private (MethodSpec, PassRule) GetMethodSpec(MethodDef method)
{
if (!_methodPassRuleCaches.TryGetValue(method, out var result))
{
var typeResult = GetTypeSpec(method.DeclaringType);
result = (null, typeResult.Item2);
if (typeResult.Item1 != null)
{
string methodName = method.Name;
foreach (var methodSpec in typeResult.Item1.methods)
{
if (methodSpec.nameMatcher.IsMatch(methodName))
{
result = (methodSpec, methodSpec.rule);
break;
}
}
}
_methodPassRuleCaches.Add(method, result);
}
return result;
}
private (FieldSpec, PassRule) GetFieldSpec(FieldDef field)
{
if (!_fieldPassRuleCaches.TryGetValue(field, out var result))
{
var typeResult = GetTypeSpec(field.DeclaringType);
result = (null, typeResult.Item2);
if (typeResult.Item1 != null)
{
string fieldName = field.Name;
foreach (var fieldSpec in typeResult.Item1.fields)
{
if (fieldSpec.nameMatcher.IsMatch(fieldName))
{
result = (fieldSpec, fieldSpec.rule);
break;
}
}
}
_fieldPassRuleCaches.Add(field, result);
}
return result;
}
private (PropertySpec, PassRule) GetPropertySpec(PropertyDef property)
{
if (!_propertyPassRuleCaches.TryGetValue(property, out var result))
{
var typeResult = GetTypeSpec(property.DeclaringType);
result = (null, typeResult.Item2);
if (typeResult.Item1 != null)
{
string propertyName = property.Name;
foreach (var propertySpec in typeResult.Item1.properties)
{
if (propertySpec.nameMatcher.IsMatch(propertyName))
{
result = (propertySpec, propertySpec.rule);
break;
}
}
}
_propertyPassRuleCaches.Add(property, result);
}
return result;
}
private (EventSpec, PassRule) GetEventSpec(EventDef eventDef)
{
if (!_eventPassRuleCaches.TryGetValue(eventDef, out var result))
{
var typeResult = GetTypeSpec(eventDef.DeclaringType);
result = (null, typeResult.Item2);
if (typeResult.Item1 != null)
{
string eventName = eventDef.Name;
foreach (var eventSpec in typeResult.Item1.events)
{
if (eventSpec.nameMatcher.IsMatch(eventName))
{
result = (eventSpec, eventSpec.rule);
break;
}
}
}
_eventPassRuleCaches.Add(eventDef, result);
}
return result;
}
public ObfuscationPassType GetAssemblyObfuscationPasses(ModuleDef module)
{
return GetAssemblySpec(module).Item2.finalPasses;
}
public ObfuscationPassType GetTypeObfuscationPasses(TypeDef type)
{
return GetTypeSpec(type).Item2.finalPasses;
}
public ObfuscationPassType GetMethodObfuscationPasses(MethodDef method)
{
return GetMethodSpec(method).Item2.finalPasses;
}
public ObfuscationPassType GetFieldObfuscationPasses(FieldDef field)
{
return GetFieldSpec(field).Item2.finalPasses;
}
public ObfuscationPassType GetPropertyObfuscationPasses(PropertyDef property)
{
return GetPropertySpec(property).Item2.finalPasses;
}
public ObfuscationPassType GetEventObfuscationPasses(EventDef eventDef)
{
return GetEventSpec(eventDef).Item2.finalPasses;
}
}
}