diff --git a/.editorconfig b/.editorconfig index aaee4ed2a..9561aef9f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -39,6 +39,44 @@ dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = never:info [*.cs] +# .NET Analyzer severity overrides +dotnet_diagnostic.CA1002.severity = none # Do not expose generic lists (too restrictive for internal code) +dotnet_diagnostic.CA1031.severity = none # Do not catch general exceptions (too many existing catch blocks) +dotnet_diagnostic.CA1032.severity = none # Implement standard exception constructors +dotnet_diagnostic.CA1034.severity = none # Nested types should not be visible +dotnet_diagnostic.CA1062.severity = none # Validate arguments of public methods (too noisy initially) +dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters +dotnet_diagnostic.CA1304.severity = none # Specify CultureInfo +dotnet_diagnostic.CA1305.severity = none # Specify IFormatProvider +dotnet_diagnostic.CA1307.severity = none # Specify StringComparison for clarity +dotnet_diagnostic.CA1310.severity = none # Specify StringComparison for correctness +dotnet_diagnostic.CA1707.severity = none # Identifiers should not contain underscores (test naming) +dotnet_diagnostic.CA1710.severity = none # Identifiers should have correct suffix +dotnet_diagnostic.CA1711.severity = none # Identifiers should not have incorrect suffix +dotnet_diagnostic.CA1720.severity = none # Identifiers should not contain type names +dotnet_diagnostic.CA1822.severity = suggestion # Mark members as static (informational only) +dotnet_diagnostic.CA1825.severity = warning # Avoid zero-length array allocations +dotnet_diagnostic.CA1859.severity = suggestion # Use concrete types for improved performance +dotnet_diagnostic.CA2000.severity = suggestion # Dispose objects before losing scope +dotnet_diagnostic.CA2007.severity = none # ConfigureAwait (not needed for desktop app) +dotnet_diagnostic.CA2227.severity = none # Collection properties should be read only + +# Roslynator severity overrides +dotnet_diagnostic.RCS1018.severity = none # Add accessibility modifiers (conflicts with existing style) +dotnet_diagnostic.RCS1102.severity = none # Make class static (many are non-static for runtime injection) +dotnet_diagnostic.RCS1037.severity = none # Remove trailing white-space +dotnet_diagnostic.RCS1057.severity = none # Add empty line between declarations +dotnet_diagnostic.RCS1085.severity = none # Use auto-implemented property +dotnet_diagnostic.RCS1090.severity = none # ConfigureAwait +dotnet_diagnostic.RCS1118.severity = none # Mark local variable as const +dotnet_diagnostic.RCS1124.severity = none # Inline local variable +dotnet_diagnostic.RCS1138.severity = none # Add summary to documentation comment +dotnet_diagnostic.RCS1139.severity = none # Add summary element to documentation comment +dotnet_diagnostic.RCS1163.severity = none # Unused parameter +dotnet_diagnostic.RCS1194.severity = none # Implement exception constructors +dotnet_diagnostic.RCS1213.severity = none # Remove unused member declaration (too aggressive) +dotnet_diagnostic.RCS1228.severity = none # Unused element in documentation comment + csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bfc63b43..50713d395 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: ci on: push: - branches: [master, feature/**, fix/**] + branches: [master, pre-release, feature/**, fix/**] pull_request: - branches: [master] + branches: [master, pre-release] jobs: build: @@ -82,6 +82,56 @@ jobs: ConfuserEx.zip Confuser.MSBuild.Tasks/bin/Release/*.nupkg + # Pre-release: on push to pre-release branch + pre-release: + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/pre-release' + runs-on: windows-2022 + timeout-minutes: 5 + permissions: + contents: write + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Install nbgv + run: dotnet tool install -g nbgv + + - name: Compute version + id: version + shell: pwsh + run: | + $ver = nbgv get-version -v NuGetPackageVersion + echo "VERSION=$ver" >> $env:GITHUB_OUTPUT + Write-Host "Pre-release version: $ver" + + - name: Download artifacts + uses: actions/download-artifact@v5 + with: + name: confuserex-packages + + - name: Create or update pre-release + uses: softprops/action-gh-release@v2 + with: + tag_name: pre-release + name: "Pre-release v${{ steps.version.outputs.VERSION }}" + prerelease: true + make_latest: false + body: | + **Pre-release build** — for testing only, not production use. + + Version: `${{ steps.version.outputs.VERSION }}` + Branch: `pre-release` + Commit: ${{ github.sha }} + + Download the binaries below to test recent fixes and features before they are included in a stable release. + files: | + ConfuserEx-CLI.zip + ConfuserEx-GUI.zip + ConfuserEx.zip + *.nupkg + # Release: only on PR merge to master release: needs: build diff --git a/Confuser.CLI/Program.cs b/Confuser.CLI/Program.cs index 267d82d0d..de11d40a2 100644 --- a/Confuser.CLI/Program.cs +++ b/Confuser.CLI/Program.cs @@ -19,6 +19,8 @@ static int Main(string[] args) { bool noPause = false; bool debug = false; string outDir = null; + string snKeyPath = null; + string snKeyPass = null; List probePaths = new List(); List plugins = new List(); var p = new OptionSet { @@ -37,6 +39,12 @@ static int Main(string[] args) { }, { "debug", "specifies debug symbol generation.", value => { debug = (value != null); } + }, { + "snkey=", "specifies strong name key file path.", + value => { snKeyPath = value; } + }, { + "snkeypass=", "specifies strong name key password.", + value => { snKeyPass = value; } } }; @@ -60,10 +68,8 @@ static int Main(string[] args) { try { var xmlDoc = new XmlDocument(); xmlDoc.Load(files[0]); - proj.Load(xmlDoc); - string crprojDir = Path.GetDirectoryName(Path.GetFullPath(files[0])); - proj.BaseDirectory = Path.GetFullPath(Path.Combine(crprojDir, proj.BaseDirectory)); - proj.OutputDirectory = Path.GetFullPath(Path.Combine(crprojDir, proj.OutputDirectory)); + proj.Load(xmlDoc, Path.GetDirectoryName(Path.GetFullPath(files[0]))); + proj.OutputDirectory = Path.GetFullPath(Path.Combine(proj.BaseDirectory, proj.OutputDirectory)); } catch (Exception ex) { WriteLineWithColor(ConsoleColor.Red, "Failed to load project:"); @@ -103,10 +109,13 @@ static int Main(string[] args) { modulePath = modulePath.Substring(proj.BaseDirectory.Length + 1); } - if (TryMatchTemplateProject(templateModules, proj.BaseDirectory, modulePath, out var matchedModule)) + if (TryMatchTemplateProject(templateModules, proj.BaseDirectory, modulePath, out var matchedModule)) { + if (snKeyPath != null) matchedModule.SNKeyPath = snKeyPath; + if (snKeyPass != null) matchedModule.SNKeyPassword = snKeyPass; proj.Add(matchedModule); + } else - proj.Add(new ProjectModule { Path = modulePath }); + proj.Add(new ProjectModule { Path = modulePath, SNKeyPath = snKeyPath, SNKeyPassword = snKeyPass }); } proj.OutputDirectory = outDir; @@ -202,6 +211,8 @@ static void PrintUsage() { WriteLine(" -probe : specifies probe directory."); WriteLine(" -plugin : specifies plugin path."); WriteLine(" -debug : specifies debug symbol generation."); + WriteLine(" -snkey : specifies strong name key file path."); + WriteLine(" -snkeypass : specifies strong name key password."); } static void WriteLineWithColor(ConsoleColor color, string txt) { diff --git a/Confuser.Core/ConfuserAssemblyResolver.cs b/Confuser.Core/ConfuserAssemblyResolver.cs index 2fced6a6b..0b6a39da3 100644 --- a/Confuser.Core/ConfuserAssemblyResolver.cs +++ b/Confuser.Core/ConfuserAssemblyResolver.cs @@ -33,8 +33,50 @@ public AssemblyDef Resolve(IAssembly assembly, ModuleDef sourceModule) { if (assembly is AssemblyDef assemblyDef) return assemblyDef; - var resolvedAssemblyDef = InternalExactResolver.Resolve(assembly, sourceModule); - return resolvedAssemblyDef ?? InternalFuzzyResolver.Resolve(assembly, sourceModule); + var resolvedAssemblyDef = + InternalExactResolver.Resolve(assembly, sourceModule) ?? + InternalFuzzyResolver.Resolve(assembly, sourceModule); + + // Remove AssemblyAttributes.PA_NoPlatform + if (null != resolvedAssemblyDef && + (AssemblyAttributes.PA_Mask & resolvedAssemblyDef.Attributes) == AssemblyAttributes.PA_NoPlatform) { + resolvedAssemblyDef.Attributes = + resolvedAssemblyDef.Attributes & ~AssemblyAttributes.PA_FullMask; + } + + if (resolvedAssemblyDef?.Name == "netstandard" && 0 < resolvedAssemblyDef.ManifestModule.ExportedTypes.Count) { + // Move types from AssemblyRef to here + var module = resolvedAssemblyDef.ManifestModule; + var newTypes = new List(); + var allAssemblyRefs = new List(); + + module.ExportedTypes.Clear(); + + foreach (var assemblyRef in module.GetAssemblyRefs()) { + var subAss = + InternalExactResolver.Resolve(assemblyRef, module) ?? + InternalFuzzyResolver.Resolve(assemblyRef, module); + allAssemblyRefs.Add(subAss); + foreach (var subModule in subAss?.Modules) { + foreach (var defType in subModule.Types) { + newTypes.Add(defType); + } + subModule.Types.Clear(); + foreach (var defType in newTypes) { + module.Types.Add(defType); + } + newTypes.Clear(); + } + } + + // Remove them because their types has been removed. + foreach (var subAss in allAssemblyRefs) { + InternalExactResolver.Remove(subAss); + InternalFuzzyResolver.Remove(subAss); + } + } + + return resolvedAssemblyDef; } public void Clear() { diff --git a/Confuser.Core/ConfuserEngine.cs b/Confuser.Core/ConfuserEngine.cs index 741efbf79..2b90fb315 100644 --- a/Confuser.Core/ConfuserEngine.cs +++ b/Confuser.Core/ConfuserEngine.cs @@ -101,6 +101,16 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token) foreach (string probePath in context.Project.ProbePaths) asmResolver.PostSearchPaths.Insert(0, Path.Combine(context.BaseDirectory, probePath)); + // Auto-detect .NET Core/5+/6/7/8+ runtime paths from first module + var firstModule = context.Project.FirstOrDefault(m => !m.IsExternal); + if (firstModule != null) { + var modulePath = Path.Combine(context.BaseDirectory, firstModule.Path); + foreach (var runtimePath in DotNetCorePathResolver.ResolveRuntimePaths(modulePath, context.Logger)) { + asmResolver.PostSearchPaths.Add(runtimePath); + context.Logger.DebugFormat("Auto-detected .NET runtime path: {0}", runtimePath); + } + } + context.CheckCancellation(); Marker marker = parameters.GetMarker(); @@ -265,13 +275,10 @@ static void Inspection(ConfuserContext context) { context.Logger.Info("Resolving dependencies..."); foreach (var dependency in context.Modules .SelectMany(module => module.GetAssemblyRefs().Select(asmRef => Tuple.Create(asmRef, module)))) { - try { - context.Resolver.ResolveThrow(dependency.Item1, dependency.Item2); - } - catch (AssemblyResolveException ex) { - context.Logger.ErrorException("Failed to resolve dependency of '" + dependency.Item2.Name + "'.", ex); - throw new ConfuserException(ex); - } + var resolved = context.Resolver.Resolve(dependency.Item1, dependency.Item2); + if (resolved == null) + context.Logger.WarnFormat("Failed to resolve dependency '{0}' of '{1}'. Some protections may not work correctly.", + dependency.Item1.FullName, dependency.Item2.Name); } context.Logger.Debug("Checking Strong Name..."); @@ -423,6 +430,8 @@ static void WriteModule(ConfuserContext context) { static void Debug(ConfuserContext context) { context.Logger.Info("Finalizing..."); + if (!context.Project.Debug) + return; for (int i = 0; i < context.OutputModules.Count; i++) { if (context.OutputSymbols[i] == null) continue; diff --git a/Confuser.Core/DnlibUtils.cs b/Confuser.Core/DnlibUtils.cs index 42d0ba439..7570da946 100644 --- a/Confuser.Core/DnlibUtils.cs +++ b/Confuser.Core/DnlibUtils.cs @@ -209,15 +209,12 @@ public static bool IsDelegate(this TypeDef type) { /// The full name of base type. /// true if the specified type is inherited from a base type; otherwise, false. public static bool InheritsFromCorlib(this TypeDef type, string baseType) { - if (type.BaseType == null) - return false; - - TypeDef bas = type; - do { - bas = bas.BaseType.ResolveTypeDefThrow(); + var bas = type.BaseType?.ResolveTypeDef(); + while (bas != null && bas.DefinitionAssembly.IsCorLib()) { if (bas.ReflectionFullName == baseType) return true; - } while (bas.BaseType != null && bas.BaseType.DefinitionAssembly.IsCorLib()); + bas = bas.BaseType?.ResolveTypeDef(); + } return false; } @@ -228,15 +225,12 @@ public static bool InheritsFromCorlib(this TypeDef type, string baseType) { /// The full name of base type. /// true if the specified type is inherited from a base type; otherwise, false. public static bool InheritsFrom(this TypeDef type, string baseType) { - if (type.BaseType == null) - return false; - - TypeDef bas = type; - do { - bas = bas.BaseType.ResolveTypeDefThrow(); + var bas = type.BaseType?.ResolveTypeDef(); + while (bas != null) { if (bas.ReflectionFullName == baseType) return true; - } while (bas.BaseType != null); + bas = bas.BaseType?.ResolveTypeDef(); + } return false; } @@ -247,18 +241,12 @@ public static bool InheritsFrom(this TypeDef type, string baseType) { /// The full name of the type of interface. /// true if the specified type implements the interface; otherwise, false. public static bool Implements(this TypeDef type, string fullName) { - do { - foreach (InterfaceImpl iface in type.Interfaces) { - if (iface.Interface.ReflectionFullName == fullName) - return true; - } - - if (type.BaseType == null) - return false; - - type = type.BaseType.ResolveTypeDefThrow(); - } while (type != null); - throw new UnreachableException(); + while (type != null) { + if (type.Interfaces.Any(iface => iface.Interface.ReflectionFullName == fullName)) + return true; + type = type.BaseType?.ResolveTypeDef(); + } + return false; } /// @@ -451,8 +439,8 @@ public static bool IsImplicitImplementedInterfaceMember(this MethodDef method) { if (method.IsPublic && method.IsNewSlot) { foreach (var iFace in method.DeclaringType.Interfaces) { - var iFaceDef = iFace.Interface.ResolveTypeDefThrow(); - if (iFaceDef.FindMethod(method.Name, (MethodSig)method.Signature) != null) + var iFaceDef = iFace.Interface.ResolveTypeDef(); + if (iFaceDef != null && iFaceDef.FindMethod(method.Name, (MethodSig)method.Signature) != null) return true; } } diff --git a/Confuser.Core/DotNetCorePathResolver.cs b/Confuser.Core/DotNetCorePathResolver.cs new file mode 100644 index 000000000..e056613d6 --- /dev/null +++ b/Confuser.Core/DotNetCorePathResolver.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Confuser.Core { + /// + /// Discovers .NET Core/5+/6/7/8+ runtime assembly directories for assembly resolution. + /// + internal static class DotNetCorePathResolver { + /// + /// Resolves runtime assembly paths for a given module by parsing its runtimeconfig.json + /// or probing standard dotnet installation directories. + /// + public static IEnumerable ResolveRuntimePaths(string modulePath, ILogger logger) { + // 1. Try runtimeconfig.json next to the module (target framework gets priority) + var runtimeConfigPaths = Enumerable.Empty(); + var runtimeConfig = FindRuntimeConfig(modulePath); + if (runtimeConfig != null) + runtimeConfigPaths = GetPathsFromRuntimeConfig(runtimeConfig, logger); + + // 2. Always add ALL installed runtime versions to handle cross-version + // dependencies (e.g., net10.0 app referencing a library compiled against net8.0) + return runtimeConfigPaths + .Concat(ProbeAllInstalledRuntimes(logger)) + .Where(Directory.Exists) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + + static string FindRuntimeConfig(string modulePath) { + if (string.IsNullOrEmpty(modulePath)) + return null; + var dir = Path.GetDirectoryName(modulePath); + if (dir == null) + return null; + var name = Path.GetFileNameWithoutExtension(modulePath); + var configPath = Path.Combine(dir, name + ".runtimeconfig.json"); + return File.Exists(configPath) ? configPath : null; + } + + static IEnumerable GetPathsFromRuntimeConfig(string configPath, ILogger logger) { + string content; + try { + content = File.ReadAllText(configPath); + } + catch (IOException ex) { + logger.WarnFormat("Failed to read runtime config '{0}': {1}", configPath, ex.Message); + yield break; + } + catch (UnauthorizedAccessException ex) { + logger.WarnFormat("Access denied reading runtime config '{0}': {1}", configPath, ex.Message); + yield break; + } + + var frameworks = ParseFrameworks(content); + if (frameworks.Count == 0) { + logger.DebugFormat("No framework references found in '{0}'.", configPath); + yield break; + } + + var dotnetRoot = GetDotNetRoot(); + if (dotnetRoot == null) { + logger.Warn("Could not locate .NET installation directory. Set DOTNET_ROOT environment variable if installed in a non-standard location."); + yield break; + } + + foreach (var fw in frameworks) { + var sharedDir = Path.Combine(dotnetRoot, "shared", fw.Name); + if (!Directory.Exists(sharedDir)) { + logger.DebugFormat("Framework directory not found: {0}", sharedDir); + continue; + } + + // Try exact version first + var exactPath = Path.Combine(sharedDir, fw.Version); + if (Directory.Exists(exactPath)) { + yield return exactPath; + continue; + } + + // Try latest matching major.minor + var majorMinor = GetMajorMinor(fw.Version); + var best = Directory.EnumerateDirectories(sharedDir) + .Where(d => Path.GetFileName(d).StartsWith(majorMinor, StringComparison.Ordinal)) + .OrderByDescending(d => d) + .FirstOrDefault(); + if (best != null) + yield return best; + else + logger.WarnFormat("No installed runtime found matching {0} {1} in {2}", fw.Name, fw.Version, sharedDir); + } + } + + static List ParseFrameworks(string json) { + var results = new List(); + // Match framework objects: "name": "...", "version": "..." + var matches = Regex.Matches(json, @"""name""\s*:\s*""([^""]+)""\s*,\s*""version""\s*:\s*""([^""]+)"""); + foreach (Match m in matches) + results.Add(new FrameworkRef { Name = m.Groups[1].Value, Version = m.Groups[2].Value }); + + // Also try reversed order (version before name) + if (results.Count == 0) { + matches = Regex.Matches(json, @"""version""\s*:\s*""([^""]+)""\s*,\s*""name""\s*:\s*""([^""]+)"""); + foreach (Match m in matches) + results.Add(new FrameworkRef { Name = m.Groups[2].Value, Version = m.Groups[1].Value }); + } + + return results; + } + + static string GetDotNetRoot() { + // Check DOTNET_ROOT env var first + var root = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + if (!string.IsNullOrEmpty(root) && Directory.Exists(root)) + return root; + + // Standard locations + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var path = Path.Combine(programFiles, "dotnet"); + if (Directory.Exists(path)) + return path; + } + else { + if (Directory.Exists("/usr/share/dotnet")) + return "/usr/share/dotnet"; + if (Directory.Exists("/usr/local/share/dotnet")) + return "/usr/local/share/dotnet"; + } + + return null; + } + + static string GetMajorMinor(string version) { + var parts = version.Split('.'); + return parts.Length >= 2 ? parts[0] + "." + parts[1] : version; + } + + static IEnumerable ProbeAllInstalledRuntimes(ILogger logger) { + var dotnetRoot = GetDotNetRoot(); + if (dotnetRoot == null) { + logger.Warn("Could not locate .NET installation directory for runtime probing."); + yield break; + } + + var sharedDir = Path.Combine(dotnetRoot, "shared"); + if (!Directory.Exists(sharedDir)) { + logger.WarnFormat("Shared framework directory not found: {0}", sharedDir); + yield break; + } + + // Return ALL installed runtime versions (newest first) across all frameworks. + // This handles cross-version dependencies where e.g., a net10.0 app references + // a library compiled against net8.0 which needs System.Runtime 8.0.0.0. + foreach (var frameworkDir in Directory.EnumerateDirectories(sharedDir)) { + foreach (var versionDir in Directory.EnumerateDirectories(frameworkDir).OrderByDescending(d => d)) + yield return versionDir; + } + } + + struct FrameworkRef { + public string Name; + public string Version; + } + } +} diff --git a/Confuser.Core/Helpers/InjectHelper.cs b/Confuser.Core/Helpers/InjectHelper.cs index 6aaf566a0..6cb066c23 100644 --- a/Confuser.Core/Helpers/InjectHelper.cs +++ b/Confuser.Core/Helpers/InjectHelper.cs @@ -292,7 +292,16 @@ public override ITypeDefOrRef Map(ITypeDefOrRef source) { // check if the assembly reference needs to be fixed. if (source is TypeRef sourceRef) { var targetAssemblyRef = TargetModule.GetAssemblyRef(sourceRef.DefinitionAssembly.Name); - if (!(targetAssemblyRef is null) && !string.Equals(targetAssemblyRef.FullName, source.DefinitionAssembly.FullName, StringComparison.Ordinal)) { + if (targetAssemblyRef is null) { + // Handle assemblies referenced by netstandard + var corLibAssemblyRef = TargetModule.CorLibTypes.AssemblyRef; + var corLibAssembly = TargetModule.Context.AssemblyResolver.Resolve(corLibAssemblyRef, TargetModule); + if (null != corLibAssembly?.ManifestModule.GetAssemblyRef(sourceRef.DefinitionAssembly.Name)) { + var fixedTypeRef = new TypeRefUser(sourceRef.Module, sourceRef.Namespace, sourceRef.Name, corLibAssemblyRef); + return Importer.Import(fixedTypeRef); + } + } + else if (!string.Equals(targetAssemblyRef.FullName, source.DefinitionAssembly.FullName, StringComparison.Ordinal)) { // We got a matching assembly by the simple name, but not by the full name. // This means the injected code uses a different assembly version than the target assembly. // We'll fix the assembly reference, to avoid breaking anything. diff --git a/Confuser.Core/ObfAttrMarker.cs b/Confuser.Core/ObfAttrMarker.cs index 70212f9cf..0738d5ca4 100644 --- a/Confuser.Core/ObfAttrMarker.cs +++ b/Confuser.Core/ObfAttrMarker.cs @@ -326,6 +326,14 @@ protected internal override MarkerResult MarkProject(ConfuserProject proj, Confu } catch (BadImageFormatException ex) { context.Logger.ErrorFormat("Failed to load \"{0}\" - Assembly does not appear to be a .NET assembly: \"{1}\".", module.Path, ex.Message); + if (module.Path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { + var dllPath = Path.ChangeExtension(module.Path, ".dll"); + var fullDllPath = Path.Combine(proj.BaseDirectory, dllPath); + if (File.Exists(fullDllPath)) + context.Logger.ErrorFormat("Hint: .NET 6+ apps use a native host .exe — try obfuscating \"{0}\" instead.", dllPath); + else + context.Logger.Error("Hint: For .NET 6+ apps, obfuscate the .dll file, not the .exe (which is a native host stub)."); + } throw new ConfuserException(ex); } } @@ -447,18 +455,18 @@ void MarkModule(ProjectModule projModule, ModuleDefMD module, Rules rules, bool snPubSigKeyPath = snPubSigKeyPath == null ? null : Path.Combine(project.BaseDirectory, snPubSigKeyPath); var snKey = LoadSNKey(context, snKeyPath, snKeyPass); - context.Annotations.Set(module, SNKey, snKey); - + context.Annotations.Set(module, SNKey, snKey); + var snPubKey = LoadSNPubKey(context, snPubKeyPath); - context.Annotations.Set(module, SNPubKey, snPubKey); - - context.Annotations.Set(module, SNDelaySig, snDelaySig); - + context.Annotations.Set(module, SNPubKey, snPubKey); + + context.Annotations.Set(module, SNDelaySig, snDelaySig); + var snSigKey = LoadSNKey(context, snSigKeyPath, snSigKeyPass); - context.Annotations.Set(module, SNSigKey, snSigKey); - + context.Annotations.Set(module, SNSigKey, snSigKey); + var snSigPubKey = LoadSNPubKey(context, snPubSigKeyPath); - context.Annotations.Set(module, SNSigPubKey, snSigPubKey); + context.Annotations.Set(module, SNSigPubKey, snSigPubKey); using (stack.Apply(module, layer)) ProcessModule(module, stack); diff --git a/Confuser.Core/Project/ConfuserProject.cs b/Confuser.Core/Project/ConfuserProject.cs index d8dc25a54..6f0073637 100644 --- a/Confuser.Core/Project/ConfuserProject.cs +++ b/Confuser.Core/Project/ConfuserProject.cs @@ -626,7 +626,7 @@ public XmlDocument Save() { /// /// The project XML contains schema errors. /// - public void Load(XmlDocument doc) { + public void Load(XmlDocument doc, string baseDirRoot = null) { doc.Schemas.Add(Schema); var exceptions = new List(); doc.Validate((sender, e) => { @@ -641,6 +641,10 @@ public void Load(XmlDocument doc) { OutputDirectory = docElem.Attributes["outputDir"].Value; BaseDirectory = docElem.Attributes["baseDir"].Value; + if (!string.IsNullOrEmpty(baseDirRoot)) + { + BaseDirectory = Path.Combine(baseDirRoot, BaseDirectory); + } if (docElem.Attributes["seed"] != null) Seed = docElem.Attributes["seed"].Value.NullIfEmpty(); @@ -674,13 +678,46 @@ public void Load(XmlDocument doc) { PluginPaths.Add(i.InnerText); } else { - var asm = new ProjectModule(); - asm.Load(i); - Add(asm); + AddModule(i); } } } + internal void AddModule(XmlElement elem) { + if (IsWildcard(elem.Attributes["path"].Value)) { + BatchLoadModules(elem); + } + else { + var asm = new ProjectModule(); + asm.Load(elem); + Add(asm); + } + } + + internal bool IsWildcard(string path) { + return !string.IsNullOrEmpty(path) && path.Contains(@"*"); + } + + internal bool BatchLoadModules(XmlElement elem) { + string wildCardPath = elem.Attributes["path"].Value; + string[] files = Directory.GetFiles(BaseDirectory, wildCardPath, SearchOption.TopDirectoryOnly); + if (files.Length <= 0) + { + return false; + } + + var asmPrototype = new ProjectModule(); + asmPrototype.Load(elem); + + foreach (string fileName in files) { + var moduleEntry = asmPrototype.Clone(); + moduleEntry.Path = fileName; + Add(moduleEntry); + } + + return true; + } + /// /// Clones this instance. /// diff --git a/Confuser.Core/ProtectionParameters.cs b/Confuser.Core/ProtectionParameters.cs index af166f910..00a6dfb00 100644 --- a/Confuser.Core/ProtectionParameters.cs +++ b/Confuser.Core/ProtectionParameters.cs @@ -14,7 +14,7 @@ public class ProtectionParameters { /// /// A empty instance of . /// - public static readonly ProtectionParameters Empty = new ProtectionParameters(null, new IDnlibDef[0]); + public static readonly ProtectionParameters Empty = new ProtectionParameters(null, Array.Empty()); readonly ConfuserComponent comp; diff --git a/Confuser.Core/Services/TraceService.cs b/Confuser.Core/Services/TraceService.cs index bec151207..44efd0d0c 100644 --- a/Confuser.Core/Services/TraceService.cs +++ b/Confuser.Core/Services/TraceService.cs @@ -204,7 +204,7 @@ internal MethodTrace Trace() { public int[] TraceArguments(Instruction instr) { instr.CalculateStackUsage(Method.HasReturnType, out _, out int pop); // pop is number of arguments if (pop == 0) - return new int[0]; + return Array.Empty(); int instrIndex = offset2index[instr.Offset]; int argCount = pop; diff --git a/Confuser.Protections/AntiTamper/JITBody.cs b/Confuser.Protections/AntiTamper/JITBody.cs index ae306d349..fcc719f40 100644 --- a/Confuser.Protections/AntiTamper/JITBody.cs +++ b/Confuser.Protections/AntiTamper/JITBody.cs @@ -134,7 +134,7 @@ public void Write() { jitBody.LocalVars = SignatureWriter.Write(metadata, local); } else - jitBody.LocalVars = new byte[0]; + jitBody.LocalVars = Array.Empty(); { var newCode = new byte[codeSize]; diff --git a/Confuser.Protections/Compress/Compressor.cs b/Confuser.Protections/Compress/Compressor.cs index 33c0ea6b7..abbbd13c6 100644 --- a/Confuser.Protections/Compress/Compressor.cs +++ b/Confuser.Protections/Compress/Compressor.cs @@ -175,7 +175,7 @@ void PackModules(ConfuserContext context, CompressorContext compCtx, ModuleDef s context.Logger.EndProgress(); } - void InjectData(ModuleDef stubModule, MethodDef method, byte[] data) { + void InjectData(ConfuserContext context, ModuleDef stubModule, MethodDef method, byte[] data) { var dataType = new TypeDefUser("", "DataType", stubModule.CorLibTypes.GetTypeRef("System", "ValueType")); dataType.Layout = TypeAttributes.ExplicitLayout; dataType.Visibility = TypeAttributes.NestedPrivate; @@ -197,7 +197,7 @@ void InjectData(ModuleDef stubModule, MethodDef method, byte[] data) { repl.Add(Instruction.Create(OpCodes.Dup)); repl.Add(Instruction.Create(OpCodes.Ldtoken, dataField)); repl.Add(Instruction.Create(OpCodes.Call, stubModule.Import( - typeof(RuntimeHelpers).GetMethod("InitializeArray")))); + context, typeof(RuntimeHelpers), "InitializeArray"))); return repl.ToArray(); }); } @@ -254,7 +254,7 @@ void InjectStub(ConfuserContext context, CompressorContext compCtx, ProtectionPa MutationHelper.InjectKeys(entryPoint, new[] { 0, 1 }, new[] { encryptedModule.Length >> 2, (int)seed }); - InjectData(stubModule, entryPoint, encryptedModule); + InjectData(context, stubModule, entryPoint, encryptedModule); // Decrypt MethodDef decrypter = defs.OfType().Single(method => method.Name == "Decrypt"); diff --git a/Confuser.Protections/Constants/EncodePhase.cs b/Confuser.Protections/Constants/EncodePhase.cs index 4eba9118e..ade681d17 100644 --- a/Confuser.Protections/Constants/EncodePhase.cs +++ b/Confuser.Protections/Constants/EncodePhase.cs @@ -122,7 +122,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa repl.Add(Instruction.Create(OpCodes.Dup)); repl.Add(Instruction.Create(OpCodes.Ldtoken, moduleCtx.DataField)); repl.Add(Instruction.Create(OpCodes.Call, moduleCtx.Module.Import( - typeof(RuntimeHelpers).GetMethod("InitializeArray")))); + context, typeof(RuntimeHelpers), "InitializeArray"))); return repl.ToArray(); }); } diff --git a/Confuser.Protections/ControlFlow/SwitchMangler.cs b/Confuser.Protections/ControlFlow/SwitchMangler.cs index 5e259e289..7dca5f55c 100644 --- a/Confuser.Protections/ControlFlow/SwitchMangler.cs +++ b/Confuser.Protections/ControlFlow/SwitchMangler.cs @@ -260,16 +260,8 @@ public override void Mangle(CilBody body, ScopeBlock root, CFContext ctx) { src.Offset >= block.Instructions.Last().Offset)) return true; - // Disable flow obfuscation for blocks reached by jump instructions. - // Bug in #153 caused exactly this behaviour, expect for allowing wrong jump instructions - // There is another issue present here tracked here: - // https://github.com/mkaring/ConfuserEx/issues/162 - // Until this issue is resolved, the ctrl flow obfuscation will be severely reduced. - if (srcs.Any()) - return true; - // Not targeted by the last of statements - if (srcs.Any(src => !statementLast.Contains(src))) + if (srcs.Any(src => statementLast.Contains(src))) return true; } return false; diff --git a/Confuser.Protections/ReferenceProxy/MildMode.cs b/Confuser.Protections/ReferenceProxy/MildMode.cs index fcc50f435..fc5c2f169 100644 --- a/Confuser.Protections/ReferenceProxy/MildMode.cs +++ b/Confuser.Protections/ReferenceProxy/MildMode.cs @@ -14,10 +14,12 @@ public override void ProcessCall(RPContext ctx, int instrIndex) { var target = (IMethod)invoke.Operand; // Value type proxy is not supported in mild mode. - if (target.DeclaringType.ResolveTypeDefThrow().IsValueType) + var declaringTypeDef = target.DeclaringType.ResolveTypeDef(); + if (declaringTypeDef == null || declaringTypeDef.IsValueType) return; // Skipping visibility is not supported in mild mode. - if (!target.ResolveThrow().IsPublic && !target.ResolveThrow().IsAssembly) + var targetMethodDef = (target as IMethodDefOrRef)?.ResolveMethodDef(); + if (targetMethodDef == null || (!targetMethodDef.IsPublic && !targetMethodDef.IsAssembly)) return; Tuple key = Tuple.Create(invoke.OpCode.Code, ctx.Method.DeclaringType, target); @@ -31,7 +33,7 @@ public override void ProcessCall(RPContext ctx, int instrIndex) { ctx.Method.DeclaringType.Methods.Add(proxy); // Fix peverify --- Non-virtual call to virtual methods must be done on this pointer - if (invoke.OpCode.Code == Code.Call && target.ResolveThrow().IsVirtual) { + if (invoke.OpCode.Code == Code.Call && targetMethodDef != null && targetMethodDef.IsVirtual) { proxy.IsStatic = false; sig.HasThis = true; sig.Params.RemoveAt(0); @@ -65,9 +67,8 @@ public override void ProcessCall(RPContext ctx, int instrIndex) { else invoke.Operand = proxy; - var targetDef = target.ResolveMethodDef(); - if (targetDef != null) - ctx.Context.Annotations.Set(targetDef, ReferenceProxyProtection.Targeted, ReferenceProxyProtection.Targeted); + if (targetMethodDef != null) + ctx.Context.Annotations.Set(targetMethodDef, ReferenceProxyProtection.Targeted, ReferenceProxyProtection.Targeted); } public override void Finalize(RPContext ctx) { } diff --git a/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs b/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs index f9f8c64b0..98e0c8bb5 100644 --- a/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs +++ b/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs @@ -147,7 +147,9 @@ void ProcessMethod(RPContext ctx) { if (operand.MethodSig.ParamsAfterSentinel != null && operand.MethodSig.ParamsAfterSentinel.Count > 0) continue; - TypeDef declType = operand.DeclaringType.ResolveTypeDefThrow(); + TypeDef declType = operand.DeclaringType.ResolveTypeDef(); + if (declType == null) + continue; // No delegates if (declType.IsDelegate()) continue; diff --git a/Confuser.Protections/Resources/InjectPhase.cs b/Confuser.Protections/Resources/InjectPhase.cs index 540591750..aa07f7d05 100644 --- a/Confuser.Protections/Resources/InjectPhase.cs +++ b/Confuser.Protections/Resources/InjectPhase.cs @@ -97,7 +97,7 @@ void InjectHelpers(ConfuserContext context, ICompressionService compression, IRu moduleCtx.DataField = new FieldDefUser(moduleCtx.Name.RandomName(), new FieldSig(dataType.ToTypeSig())) { IsStatic = true, HasFieldRVA = true, - InitialValue = new byte[0], + InitialValue = Array.Empty(), Access = FieldAttributes.CompilerControlled }; context.CurrentModule.GlobalType.Fields.Add(moduleCtx.DataField); @@ -137,10 +137,10 @@ void MutateInitializer(REContext moduleCtx, MethodDef decomp) { repl.Add(Instruction.Create(OpCodes.Dup)); repl.Add(Instruction.Create(OpCodes.Ldtoken, moduleCtx.DataField)); repl.Add(Instruction.Create(OpCodes.Call, moduleCtx.Module.Import( - typeof(RuntimeHelpers).GetMethod("InitializeArray")))); + moduleCtx.Context, typeof(RuntimeHelpers), "InitializeArray"))); return repl.ToArray(); }); moduleCtx.Context.Registry.GetService().ExcludeMethod(moduleCtx.Context, moduleCtx.InitMethod); } } -} \ No newline at end of file +} diff --git a/Confuser.Protections/Utils.cs b/Confuser.Protections/Utils.cs new file mode 100644 index 000000000..e46f701c1 --- /dev/null +++ b/Confuser.Protections/Utils.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Confuser.Core; +using dnlib.DotNet; + +namespace Confuser.Protections { + internal static class Utils { + public static IMethod Import(this ModuleDef module, ConfuserContext context, Type classType, string method) { + var corLib = context.Resolver.Resolve(context.CurrentModule?.CorLibTypes.AssemblyRef, context.CurrentModule); + var typeInfo = corLib?.ManifestModule.Find(classType.FullName, true); + return (typeInfo == null) ? module.Import(classType.GetMethod(method)) : module.Import(typeInfo.FindMethod(method)); + } + } +} diff --git a/Confuser.Renamer/AnalyzePhase.cs b/Confuser.Renamer/AnalyzePhase.cs index 875a681ad..efd1ae693 100644 --- a/Confuser.Renamer/AnalyzePhase.cs +++ b/Confuser.Renamer/AnalyzePhase.cs @@ -206,6 +206,10 @@ void Analyze(NameService service, ConfuserContext context, ProtectionParameters if (type.InheritsFrom("System.Configuration.SettingsBase")) { service.SetCanRename(type, false); } + + if (type.HasAttribute("System.Runtime.Serialization.DataContractAttribute")) { + service.SetCanRename(type, false); + } } void Analyze(NameService service, ConfuserContext context, ProtectionParameters parameters, MethodDef method) { @@ -248,6 +252,12 @@ void Analyze(NameService service, ConfuserContext context, ProtectionParameters else if (field.IsLiteral && field.DeclaringType.IsEnum && !parameters.GetParameter(context, field, "renEnum", false)) service.SetCanRename(field, false); + + else if (field.HasAttribute("System.Runtime.Serialization.DataMemberAttribute")) + service.SetCanRename(field, false); + + else if (field.HasAttribute("System.Runtime.Serialization.EnumMemberAttribute")) + service.SetCanRename(field, false); } void Analyze(NameService service, ConfuserContext context, ProtectionParameters parameters, PropertyDef property) { @@ -267,6 +277,9 @@ void Analyze(NameService service, ConfuserContext context, ProtectionParameters else if (property.DeclaringType.Name.String.Contains("AnonymousType")) service.SetCanRename(property, false); + + else if (property.HasAttribute("System.Runtime.Serialization.DataMemberAttribute")) + service.SetCanRename(property, false); } void Analyze(NameService service, ConfuserContext context, ProtectionParameters parameters, EventDef evt) { diff --git a/Confuser.Renamer/Analyzers/WPFAnalyzer.cs b/Confuser.Renamer/Analyzers/WPFAnalyzer.cs index 9cc9993f9..47097bbb3 100644 --- a/Confuser.Renamer/Analyzers/WPFAnalyzer.cs +++ b/Confuser.Renamer/Analyzers/WPFAnalyzer.cs @@ -60,8 +60,18 @@ public void PreRename(ConfuserContext context, INameService service, ProtectionP var decodedName = HttpUtility.UrlDecode(doc.DocumentName); var encodedName = doc.DocumentName; if (bamlRefs.TryGetValue(decodedName, out var references)) { - var decodedDirectory = decodedName.Substring(0, decodedName.LastIndexOf('/') + 1); - var encodedDirectory = encodedName.Substring(0, encodedName.LastIndexOf('/') + 1); + // WPF theme resources must keep their conventional paths + if (decodedName.IndexOf("themes/generic", StringComparison.OrdinalIgnoreCase) >= 0) + continue; + + var decodedLastSlash = decodedName.LastIndexOf('/') + 1; + var encodedLastSlash = encodedName.LastIndexOf('/') + 1; + + var decodedDirectory = decodedName.Substring(0, decodedLastSlash); + var encodedDirectory = encodedName.Substring(0, encodedLastSlash); + + var decodedFileName = decodedName.Substring(decodedLastSlash); + var encodedFileName = encodedName.Substring(encodedLastSlash); var fileName = service.RandomName(renameMode).ToLowerInvariant(); if (decodedName.EndsWith(".BAML", StringComparison.OrdinalIgnoreCase)) @@ -78,8 +88,8 @@ public void PreRename(ConfuserContext context, INameService service, ProtectionP if (renameOk) { foreach (var bamlRef in references) { - bamlRef.Rename(module, decodedName, decodedNewName); - bamlRef.Rename(module, encodedName, encodedNewName); + bamlRef.Rename(module, decodedFileName, fileName); + bamlRef.Rename(module, encodedFileName, fileName); } doc.DocumentName = encodedNewName; } @@ -349,4 +359,4 @@ void AnalyzeResources(ConfuserContext context, INameService service, ModuleDefMD context.Annotations.Set(module, BAMLKey, wpfResInfo); } } -} +} \ No newline at end of file diff --git a/Confuser.Renamer/BAML/PropertyPath/PropertyPathParser.cs b/Confuser.Renamer/BAML/PropertyPath/PropertyPathParser.cs index 832aad768..67542c1ef 100644 --- a/Confuser.Renamer/BAML/PropertyPath/PropertyPathParser.cs +++ b/Confuser.Renamer/BAML/PropertyPath/PropertyPathParser.cs @@ -317,7 +317,7 @@ void StartNewLevel() { ArrayList _al = new ArrayList(); const char NullChar = Char.MinValue; const char EscapeChar = '^'; - static SourceValueInfo[] EmptyInfo = new SourceValueInfo[0]; + static SourceValueInfo[] EmptyInfo = Array.Empty(); static string SpecialChars = @"./[]"; } } diff --git a/Confuser.Renamer/GenericArgumentResolver.cs b/Confuser.Renamer/GenericArgumentResolver.cs index 206a3d28b..d7b5ec730 100644 --- a/Confuser.Renamer/GenericArgumentResolver.cs +++ b/Confuser.Renamer/GenericArgumentResolver.cs @@ -122,7 +122,8 @@ private TypeSig ResolveGenericArgs(TypeSig typeSig) { result = new PinnedSig(ResolveGenericArgs(typeSig.Next)); break; case ElementType.FnPtr: - throw new NotSupportedException("FnPtr is not supported."); + result = typeSig; + break; case ElementType.Array: var arraySig = (ArraySig)typeSig; diff --git a/Confuser.Renamer/NameService.cs b/Confuser.Renamer/NameService.cs index aafebadda..ac60618b7 100644 --- a/Confuser.Renamer/NameService.cs +++ b/Confuser.Renamer/NameService.cs @@ -214,7 +214,7 @@ public string ObfuscateName(IDnlibDef dnlibDef, RenameMode mode) { public string ObfuscateName(string format, string name, RenameMode mode, bool preserveGenericParams = false) { int genericParamsCount = 0; - if (preserveGenericParams) { + if (preserveGenericParams && !string.IsNullOrEmpty(name)) { name = ParseGenericName(name, out genericParamsCount); } diff --git a/Confuser.Renamer/RenamePhase.cs b/Confuser.Renamer/RenamePhase.cs index cb8edd666..91021abcd 100644 --- a/Confuser.Renamer/RenamePhase.cs +++ b/Confuser.Renamer/RenamePhase.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Confuser.Core; @@ -14,8 +15,12 @@ public RenamePhase(NameProtection parent) public override string Name => "Renaming"; + // Tracks overload confusion name assignments: TypeDef -> list of assigned names + readonly Dictionary> _overloadNames = new Dictionary>(); + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { var service = (NameService)context.Registry.GetService(); + bool overloadConfusion = parameters.GetParameter(context, context.CurrentModule, "overload", false); context.Logger.Debug("Renaming..."); foreach (var renamer in service.Renamers) { @@ -36,8 +41,13 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa if (def is MethodDef method) { if ((canRename || method.IsConstructor) && parameters.GetParameter(context, method, "renameArgs", true)) { - foreach (var param in method.ParamDefs) - param.Name = null; + if (method.IsConstructor && method.DeclaringType.Name.String.Contains("AnonymousType")) { + // Anonymous type constructor args map to property names — renaming breaks JSON serialization + } + else { + foreach (var param in method.ParamDefs) + param.Name = null; + } } if (parameters.GetParameter(context, method, "renPdb", false) && method.HasBody) { @@ -79,7 +89,12 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa RenameGenericParameters(typeDef.GenericParameters); } else if (def is MethodDef methodDef) { - methodDef.Name = service.ObfuscateName(methodDef, mode); + if (overloadConfusion && CanApplyOverloadConfusion(methodDef, service)) { + methodDef.Name = GetOverloadName(methodDef, service, mode); + } + else { + methodDef.Name = service.ObfuscateName(methodDef, mode); + } RenameGenericParameters(methodDef.GenericParameters); } else @@ -114,6 +129,48 @@ static void RenameGenericParameters(IList genericParams) param.Name = ((char) (param.Number + 1)).ToString(); } + /// + /// Determines whether overload confusion can safely be applied to the method. + /// Only applies to non-virtual, non-interface, non-special methods. + /// + static bool CanApplyOverloadConfusion(MethodDef method, INameService service) { + if (method.IsVirtual || method.IsAbstract || method.IsConstructor) + return false; + if (method.IsSpecialName || method.IsRuntimeSpecialName) + return false; + // Skip if method has override/sibling references (VTable constrained) + var refs = service.GetReferences(method); + if (refs.Any(r => r.ShouldCancelRename || r.DelayRenaming(service, method))) + return false; + return true; + } + + /// + /// Gets a shared overload name for the method's declaring type. + /// Methods with different signatures in the same type get the same name. + /// + string GetOverloadName(MethodDef method, NameService service, RenameMode mode) { + var declType = method.DeclaringType; + if (!_overloadNames.TryGetValue(declType, out var names)) { + names = new List(); + _overloadNames[declType] = names; + } + + // Check if any existing name can be reused (different signature = safe to share) + foreach (var existingName in names) { + bool signatureConflict = declType.Methods.Any(m => + m.Name == existingName && + new SigComparer().Equals(m.MethodSig, method.MethodSig)); + if (!signatureConflict) + return existingName; + } + + // No reusable name — generate a new one + var newName = service.ObfuscateName(method, mode); + names.Add(newName); + return newName; + } + static IEnumerable GetTargetsWithDelay(IList definitions, ConfuserContext context, INameService service) { var delayedItems = new List(); var currentList = definitions; diff --git a/Confuser.Runtime/Compressor.cs b/Confuser.Runtime/Compressor.cs index abb2e62b3..5e3d3b09b 100644 --- a/Confuser.Runtime/Compressor.cs +++ b/Confuser.Runtime/Compressor.cs @@ -60,7 +60,8 @@ static int Main(string[] args) { AppDomain.CurrentDomain.AssemblyResolve += Resolve; // For some reasons, reflection on Assembly would not discover the types unless GetTypes is called. - m.GetTypes(); + try { m.GetTypes(); } + catch (ReflectionTypeLoadException) { } MethodBase e = m.ResolveMethod(key[0] | (key[1] << 8) | (key[2] << 16) | (key[3] << 24)); var g = new object[e.GetParameters().Length]; diff --git a/ConfuserEx.Common.props b/ConfuserEx.Common.props index a0586f7a4..e597a9e26 100644 --- a/ConfuserEx.Common.props +++ b/ConfuserEx.Common.props @@ -23,7 +23,8 @@ - false + true + latest \ No newline at end of file diff --git a/ConfuserEx.Common.targets b/ConfuserEx.Common.targets index f6083488a..c929f0088 100644 --- a/ConfuserEx.Common.targets +++ b/ConfuserEx.Common.targets @@ -3,8 +3,9 @@ - + + \ No newline at end of file