diff --git a/.gitignore b/.gitignore
index 9ee6a61e2..557af9475 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,4 +39,8 @@ packages/
gh-pages/
.idea/
-**/*out*
\ No newline at end of file
+**/*out*
+
+# Local CI artifacts
+test-results/
+coverage/
\ No newline at end of file
diff --git a/Confuser.CLI/Confuser.CLI.csproj b/Confuser.CLI/Confuser.CLI.csproj
index d087b4871..df0632989 100644
--- a/Confuser.CLI/Confuser.CLI.csproj
+++ b/Confuser.CLI/Confuser.CLI.csproj
@@ -16,6 +16,9 @@
+
+
+
diff --git a/Confuser.CLI/Program.cs b/Confuser.CLI/Program.cs
index aff59c47c..47da3ca8e 100644
--- a/Confuser.CLI/Program.cs
+++ b/Confuser.CLI/Program.cs
@@ -6,7 +6,10 @@
using System.Xml;
using Confuser.Core;
using Confuser.Core.Project;
+using Microsoft.Extensions.Logging;
using NDesk.Options;
+using Serilog;
+using Serilog.Events;
namespace Confuser.CLI {
internal class Program {
@@ -21,6 +24,8 @@ static int Main(string[] args) {
try {
bool noPause = false;
bool debug = false;
+ bool quiet = false;
+ int verbosity = 0;
string outDir = null;
string snKeyPath = null;
string snKeyPass = null;
@@ -48,6 +53,12 @@ static int Main(string[] args) {
}, {
"snkeypass=", "specifies strong name key password.",
value => { snKeyPass = value; }
+ }, {
+ "v|verbose", "increase verbosity (repeat for more: -v, -vv, -vvv).",
+ value => { verbosity++; }
+ }, {
+ "q|quiet", "only show warnings and errors.",
+ value => { quiet = (value != null); }
}
};
@@ -130,7 +141,7 @@ static int Main(string[] args) {
parameters.Project = proj;
}
- int retVal = RunProject(parameters);
+ int retVal = RunProject(parameters, quiet, verbosity);
if (NeedPause() && !noPause) {
Console.WriteLine("Press any key to continue...");
@@ -192,14 +203,34 @@ static void LoadTemplateProject(string templatePath, ConfuserProject proj, List<
templateModules.Add(templateModule);
}
- static int RunProject(ConfuserParameters parameters) {
- var logger = new ConsoleLogger();
- parameters.Logger = logger;
-
- Console.Title = "ConfuserEx - Running...";
+ static int RunProject(ConfuserParameters parameters, bool quiet, int verbosity) {
+ var levelSwitch = quiet
+ ? LogEventLevel.Warning
+ : verbosity >= 3 ? LogEventLevel.Verbose
+ : verbosity >= 2 ? LogEventLevel.Verbose
+ : verbosity >= 1 ? LogEventLevel.Debug
+ : LogEventLevel.Information;
+
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Is(levelSwitch)
+ .WriteTo.Console(
+ outputTemplate: "[{Level:u4}] {Message:lj}{NewLine}{Exception}")
+ .CreateLogger();
+
+ using var loggerFactory = LoggerFactory.Create(builder =>
+ builder.AddSerilog(dispose: false));
+ var melLogger = loggerFactory.CreateLogger("ConfuserEx");
+
+ var progressReporter = new ConsoleProgressReporter();
+ parameters.Logger = melLogger;
+ parameters.ProgressReporter = progressReporter;
+
+ if (OperatingSystem.IsWindows())
+ Console.Title = "ConfuserEx - Running...";
ConfuserEngine.Run(parameters).GetAwaiter().GetResult();
- return logger.ReturnValue;
+ Log.CloseAndFlush();
+ return progressReporter.ReturnValue;
}
static bool NeedPause() {
@@ -217,6 +248,8 @@ static void PrintUsage() {
WriteLine(" -debug : specifies debug symbol generation.");
WriteLine(" -snkey : specifies strong name key file path.");
WriteLine(" -snkeypass : specifies strong name key password.");
+ WriteLine(" -v|verbose : increase verbosity (-v debug, -vv trace).");
+ WriteLine(" -q|quiet : only show warnings and errors.");
}
static void WriteLineWithColor(ConsoleColor color, string txt) {
@@ -234,57 +267,15 @@ static void WriteLine() {
Console.WriteLine();
}
- class ConsoleLogger : ILogger {
+ class ConsoleProgressReporter : IProgressReporter {
readonly DateTime begin;
- public ConsoleLogger() {
+ public ConsoleProgressReporter() {
begin = DateTime.Now;
}
public int ReturnValue { get; private set; }
- public void Debug(string msg) {
- WriteLineWithColor(ConsoleColor.Gray, "[DEBUG] " + msg);
- }
-
- public void DebugFormat(string format, params object[] args) {
- WriteLineWithColor(ConsoleColor.Gray, "[DEBUG] " + string.Format(format, args));
- }
-
- public void Info(string msg) {
- WriteLineWithColor(ConsoleColor.White, " [INFO] " + msg);
- }
-
- public void InfoFormat(string format, params object[] args) {
- WriteLineWithColor(ConsoleColor.White, " [INFO] " + string.Format(format, args));
- }
-
- public void Warn(string msg) {
- WriteLineWithColor(ConsoleColor.Yellow, " [WARN] " + msg);
- }
-
- public void WarnFormat(string format, params object[] args) {
- WriteLineWithColor(ConsoleColor.Yellow, " [WARN] " + string.Format(format, args));
- }
-
- public void WarnException(string msg, Exception ex) {
- WriteLineWithColor(ConsoleColor.Yellow, " [WARN] " + msg);
- WriteLineWithColor(ConsoleColor.Yellow, "Exception: " + ex);
- }
-
- public void Error(string msg) {
- WriteLineWithColor(ConsoleColor.Red, "[ERROR] " + msg);
- }
-
- public void ErrorFormat(string format, params object[] args) {
- WriteLineWithColor(ConsoleColor.Red, "[ERROR] " + string.Format(format, args));
- }
-
- public void ErrorException(string msg, Exception ex) {
- WriteLineWithColor(ConsoleColor.Red, "[ERROR] " + msg);
- WriteLineWithColor(ConsoleColor.Red, "Exception: " + ex);
- }
-
public void Progress(int progress, int overall) { }
public void EndProgress() { }
diff --git a/Confuser.Core/Confuser.Core.csproj b/Confuser.Core/Confuser.Core.csproj
index 0e13ac77c..686e8f95b 100644
--- a/Confuser.Core/Confuser.Core.csproj
+++ b/Confuser.Core/Confuser.Core.csproj
@@ -17,6 +17,7 @@
+
diff --git a/Confuser.Core/ConfuserContext.cs b/Confuser.Core/ConfuserContext.cs
index a8c7a1c76..493643745 100644
--- a/Confuser.Core/ConfuserContext.cs
+++ b/Confuser.Core/ConfuserContext.cs
@@ -4,6 +4,7 @@
using Confuser.Core.Project;
using dnlib.DotNet;
using dnlib.DotNet.Writer;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
///
@@ -18,7 +19,13 @@ public class ConfuserContext {
/// Gets the logger used for logging events.
///
/// The logger.
- public ILogger Logger { get; internal set; }
+ public Microsoft.Extensions.Logging.ILogger Logger { get; internal set; }
+
+ ///
+ /// Gets the progress reporter used for reporting protection progress.
+ ///
+ /// The progress reporter.
+ public IProgressReporter ProgressReporter { get; internal set; }
///
/// Gets the project being processed.
diff --git a/Confuser.Core/ConfuserEngine.cs b/Confuser.Core/ConfuserEngine.cs
index a4e737cce..68ac9c482 100644
--- a/Confuser.Core/ConfuserEngine.cs
+++ b/Confuser.Core/ConfuserEngine.cs
@@ -10,6 +10,7 @@
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Writer;
+using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using CopyrightAttribute = System.Reflection.AssemblyCopyrightAttribute;
using InformationalAttribute = System.Reflection.AssemblyInformationalVersionAttribute;
@@ -80,6 +81,7 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
// 1. Setup context
var context = new ConfuserContext();
context.Logger = parameters.GetLogger();
+ context.ProgressReporter = parameters.GetProgressReporter();
context.Project = parameters.Project.Clone();
context.PackerInitiated = parameters.PackerInitiated;
context.token = token;
@@ -107,7 +109,7 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
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.Logger.LogDebug("Auto-detected .NET runtime path: {0}", runtimePath);
}
}
@@ -116,25 +118,25 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
Marker marker = parameters.GetMarker();
// 2. Discover plugins
- context.Logger.Debug("Discovering plugins...");
+ context.Logger.LogDebug("Discovering plugins...");
IList prots;
IList packers;
IList components;
parameters.GetPluginDiscovery().GetPlugins(context, out prots, out packers, out components);
- context.Logger.InfoFormat("Discovered {0} protections, {1} packers.", prots.Count, packers.Count);
+ context.Logger.LogInformation("Discovered {0} protections, {1} packers.", prots.Count, packers.Count);
context.CheckCancellation();
// 3. Resolve dependency
- context.Logger.Debug("Resolving component dependency...");
+ context.Logger.LogDebug("Resolving component dependency...");
try {
var resolver = new DependencyResolver(prots);
prots = resolver.SortDependency();
}
catch (CircularDependencyException ex) {
- context.Logger.ErrorException("", ex);
+ context.Logger.LogError(ex, "");
throw new ConfuserException(ex);
}
@@ -147,7 +149,7 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
context.CheckCancellation();
// 4. Load modules
- context.Logger.Info("Loading input modules...");
+ context.Logger.LogInformation("Loading input modules...");
marker.Initialize(prots, packers);
MarkerResult markings = marker.MarkProject(context.Project, context);
context.Modules = new ModuleSorter(markings.Modules).Sort().ToList().AsReadOnly();
@@ -162,13 +164,13 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
context.CheckCancellation();
// 5. Initialize components
- context.Logger.Info("Initializing...");
+ context.Logger.LogInformation("Initializing...");
foreach (ConfuserComponent comp in components) {
try {
comp.Initialize(context);
}
catch (Exception ex) {
- context.Logger.ErrorException("Error occurred during initialization of '" + comp.Name + "'.", ex);
+ context.Logger.LogError(ex, "Error occurred during initialization of '" + comp.Name + "'.");
throw new ConfuserException(ex);
}
context.CheckCancellation();
@@ -177,7 +179,7 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
context.CheckCancellation();
// 6. Build pipeline
- context.Logger.Debug("Building pipeline...");
+ context.Logger.LogDebug("Building pipeline...");
var pipeline = new ProtectionPipeline();
context.Pipeline = pipeline;
foreach (ConfuserComponent comp in components) {
@@ -192,33 +194,33 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
ok = true;
}
catch (AssemblyResolveException ex) {
- context.Logger.ErrorException("Failed to resolve an assembly, check if all dependencies are present in the correct version.", ex);
+ context.Logger.LogError(ex, "Failed to resolve an assembly, check if all dependencies are present in the correct version.");
PrintEnvironmentInfo(context);
}
catch (TypeResolveException ex) {
- context.Logger.ErrorException("Failed to resolve a type, check if all dependencies are present in the correct version.", ex);
+ context.Logger.LogError(ex, "Failed to resolve a type, check if all dependencies are present in the correct version.");
PrintEnvironmentInfo(context);
}
catch (MemberRefResolveException ex) {
- context.Logger.ErrorException("Failed to resolve a member, check if all dependencies are present in the correct version.", ex);
+ context.Logger.LogError(ex, "Failed to resolve a member, check if all dependencies are present in the correct version.");
PrintEnvironmentInfo(context);
}
catch (IOException ex) {
- context.Logger.ErrorException("An IO error occurred, check if all input/output locations are readable/writable.", ex);
+ context.Logger.LogError(ex, "An IO error occurred, check if all input/output locations are readable/writable.");
}
catch (OperationCanceledException) {
- context.Logger.Error("Operation cancelled.");
+ context.Logger.LogError("Operation cancelled.");
}
catch (ConfuserException) {
// Exception is already handled/logged, so just ignore and report failure
}
catch (Exception ex) {
- context.Logger.ErrorException("Unknown error occurred.", ex);
+ context.Logger.LogError(ex, "Unknown error occurred.");
}
finally {
if (context.Resolver != null)
context.InternalResolver.Clear();
- context.Logger.Finish(ok);
+ context.ProgressReporter.Finish(ok);
}
}
@@ -268,27 +270,27 @@ static void RunPipeline(ProtectionPipeline pipeline, ConfuserContext context) {
pipeline.ExecuteStage(PipelineStage.SaveModules, SaveModules, () => getAllDefs(), context);
if (!context.PackerInitiated)
- context.Logger.Info("Done.");
+ context.Logger.LogInformation("Done.");
}
static void Inspection(ConfuserContext context) {
- context.Logger.Info("Resolving dependencies...");
+ context.Logger.LogInformation("Resolving dependencies...");
foreach (var dependency in context.Modules
.SelectMany(module => module.GetAssemblyRefs().Select(asmRef => Tuple.Create(asmRef, module)))) {
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.",
+ context.Logger.LogWarning("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...");
+ context.Logger.LogDebug("Checking Strong Name...");
foreach (var module in context.Modules) {
CheckStrongName(context, module);
}
var marker = context.Registry.GetService();
- context.Logger.Debug("Creating global .cctors...");
+ context.Logger.LogDebug("Creating global .cctors...");
foreach (ModuleDefMD module in context.Modules) {
TypeDef modType = module.GlobalType;
if (modType == null) {
@@ -316,12 +318,12 @@ static void CheckStrongName(ConfuserContext context, ModuleDef module) {
bool isKeyProvided = snKey != null || (snDelaySign && snPubKeyBytes != null);
if (!isKeyProvided && moduleIsSignedOrDelayedSigned)
- context.Logger.WarnFormat("[{0}] SN Key or SN public Key is not provided for a signed module, the output may not be working.", module.Name);
+ context.Logger.LogWarning("[{0}] SN Key or SN public Key is not provided for a signed module, the output may not be working.", module.Name);
else if (isKeyProvided && !moduleIsSignedOrDelayedSigned)
- context.Logger.WarnFormat("[{0}] SN Key or SN public Key is provided for an unsigned module, the output may not be working.", module.Name);
+ context.Logger.LogWarning("[{0}] SN Key or SN public Key is provided for an unsigned module, the output may not be working.", module.Name);
else if (snPubKeyBytes != null && moduleIsSignedOrDelayedSigned &&
!module.Assembly.PublicKey.Data.SequenceEqual(snPubKeyBytes))
- context.Logger.WarnFormat("[{0}] Provided SN public Key and signed module's public key do not match, the output may not be working.",
+ context.Logger.LogWarning("[{0}] Provided SN public Key and signed module's public key do not match, the output may not be working.",
module.Name);
}
@@ -338,7 +340,7 @@ static void CopyPEHeaders(PEHeadersOptions writerOptions, ModuleDefMD module) {
}
static void BeginModule(ConfuserContext context) {
- context.Logger.InfoFormat("Processing module '{0}'...", context.CurrentModule.Name);
+ context.Logger.LogInformation("Processing module '{0}'...", context.CurrentModule.Name);
context.CurrentModuleWriterOptions = new ModuleWriterOptions(context.CurrentModule);
CopyPEHeaders(context.CurrentModuleWriterOptions.PEHeadersOptions, context.CurrentModule);
@@ -393,7 +395,7 @@ static void EndModule(ConfuserContext context) {
output = Path.Combine(context.BaseDirectory, output);
string relativeOutput = Utils.GetRelativePath(output, context.BaseDirectory);
if (relativeOutput is null) {
- context.Logger.WarnFormat("Input file is not inside the base directory. Relative path can't be created. Placing file into output root." +
+ context.Logger.LogWarning("Input file is not inside the base directory. Relative path can't be created. Placing file into output root." +
Environment.NewLine + "Responsible file is: {0}", output);
output = Path.GetFileName(output);
}
@@ -408,7 +410,7 @@ static void EndModule(ConfuserContext context) {
}
static void WriteModule(ConfuserContext context) {
- context.Logger.InfoFormat("Writing module '{0}'...", context.CurrentModule.Name);
+ context.Logger.LogInformation("Writing module '{0}'...", context.CurrentModule.Name);
MemoryStream pdb = null, output = new MemoryStream();
@@ -430,7 +432,7 @@ static void WriteModule(ConfuserContext context) {
}
static void Debug(ConfuserContext context) {
- context.Logger.Info("Finalizing...");
+ context.Logger.LogInformation("Finalizing...");
if (!context.Project.Debug)
return;
for (int i = 0; i < context.OutputModules.Count; i++) {
@@ -446,7 +448,7 @@ static void Debug(ConfuserContext context) {
static void Pack(ConfuserContext context) {
if (context.Packer != null) {
- context.Logger.Info("Packing...");
+ context.Logger.LogInformation("Packing...");
context.Packer.Pack(context, new ProtectionParameters(context.Packer, context.Modules.OfType().ToList()));
}
}
@@ -458,7 +460,7 @@ static void SaveModules(ConfuserContext context) {
string dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
- context.Logger.DebugFormat("Saving to '{0}'...", path);
+ context.Logger.LogDebug("Saving to '{0}'...", path);
File.WriteAllBytes(path, context.OutputModules[i]);
}
}
@@ -469,13 +471,13 @@ static void SaveModules(ConfuserContext context) {
/// The working context.
static void PrintInfo(ConfuserContext context) {
if (context.PackerInitiated) {
- context.Logger.Info("Protecting packer stub...");
+ context.Logger.LogInformation("Protecting packer stub...");
}
else {
- context.Logger.InfoFormat("{0} {1}", Version, Copyright);
+ context.Logger.LogInformation("{0} {1}", Version, Copyright);
Type mono = Type.GetType("Mono.Runtime");
- context.Logger.InfoFormat("Running on {0}, {1}, {2} bits",
+ context.Logger.LogInformation("Running on {0}, {1}, {2} bits",
Environment.OSVersion,
mono == null ?
".NET Framework v" + Environment.Version :
@@ -542,27 +544,27 @@ static void PrintEnvironmentInfo(ConfuserContext context) {
if (context.PackerInitiated)
return;
- context.Logger.Error("---BEGIN DEBUG INFO---");
+ context.Logger.LogError("---BEGIN DEBUG INFO---");
- context.Logger.Error("Installed Framework Versions:");
+ context.Logger.LogError("Installed Framework Versions:");
foreach (string ver in GetFrameworkVersions()) {
- context.Logger.ErrorFormat(" {0}", ver.Trim());
+ context.Logger.LogError(" {0}", ver.Trim());
}
- context.Logger.Error("");
+ context.Logger.LogError("");
if (context.Resolver != null) {
- context.Logger.Error("Cached assemblies:");
+ context.Logger.LogError("Cached assemblies:");
foreach (AssemblyDef asm in context.InternalResolver.GetCachedAssemblies()) {
if (string.IsNullOrEmpty(asm.ManifestModule.Location))
- context.Logger.ErrorFormat(" {0}", asm.FullName);
+ context.Logger.LogError(" {0}", asm.FullName);
else
- context.Logger.ErrorFormat(" {0} ({1})", asm.FullName, asm.ManifestModule.Location);
+ context.Logger.LogError(" {0} ({1})", asm.FullName, asm.ManifestModule.Location);
foreach (var reference in asm.Modules.OfType().SelectMany(m => m.GetAssemblyRefs()))
- context.Logger.ErrorFormat(" {0}", reference.FullName);
+ context.Logger.LogError(" {0}", reference.FullName);
}
}
- context.Logger.Error("---END DEBUG INFO---");
+ context.Logger.LogError("---END DEBUG INFO---");
}
}
}
diff --git a/Confuser.Core/ConfuserParameters.cs b/Confuser.Core/ConfuserParameters.cs
index f3e05a2c1..5f9d00ba7 100644
--- a/Confuser.Core/ConfuserParameters.cs
+++ b/Confuser.Core/ConfuserParameters.cs
@@ -1,5 +1,6 @@
using System;
using Confuser.Core.Project;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
///
@@ -16,7 +17,13 @@ public class ConfuserParameters {
/// Gets or sets the logger that used to log the protection process.
///
/// The logger, or null if logging is not needed.
- public ILogger Logger { get; set; }
+ public Microsoft.Extensions.Logging.ILogger Logger { get; set; }
+
+ ///
+ /// Gets or sets the progress reporter used to report protection progress.
+ ///
+ /// The progress reporter, or null if progress reporting is not needed.
+ public IProgressReporter ProgressReporter { get; set; }
internal bool PackerInitiated { get; set; }
@@ -36,8 +43,16 @@ public class ConfuserParameters {
/// Gets the actual non-null logger.
///
/// The logger.
- internal ILogger GetLogger() {
- return Logger ?? NullLogger.Instance;
+ internal Microsoft.Extensions.Logging.ILogger GetLogger() {
+ return Logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance;
+ }
+
+ ///
+ /// Gets the actual non-null progress reporter.
+ ///
+ /// The progress reporter.
+ internal IProgressReporter GetProgressReporter() {
+ return ProgressReporter ?? NullProgressReporter.Instance;
}
///
diff --git a/Confuser.Core/DotNetCorePathResolver.cs b/Confuser.Core/DotNetCorePathResolver.cs
index e056613d6..b35503ce4 100644
--- a/Confuser.Core/DotNetCorePathResolver.cs
+++ b/Confuser.Core/DotNetCorePathResolver.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
///
@@ -13,7 +14,7 @@ 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) {
+ public static IEnumerable ResolveRuntimePaths(string modulePath, Microsoft.Extensions.Logging.ILogger logger) {
// 1. Try runtimeconfig.json next to the module (target framework gets priority)
var runtimeConfigPaths = Enumerable.Empty();
var runtimeConfig = FindRuntimeConfig(modulePath);
@@ -39,36 +40,36 @@ static string FindRuntimeConfig(string modulePath) {
return File.Exists(configPath) ? configPath : null;
}
- static IEnumerable GetPathsFromRuntimeConfig(string configPath, ILogger logger) {
+ static IEnumerable GetPathsFromRuntimeConfig(string configPath, Microsoft.Extensions.Logging.ILogger logger) {
string content;
try {
content = File.ReadAllText(configPath);
}
catch (IOException ex) {
- logger.WarnFormat("Failed to read runtime config '{0}': {1}", configPath, ex.Message);
+ logger.LogWarning("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);
+ logger.LogWarning("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);
+ logger.LogDebug("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.");
+ logger.LogWarning("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);
+ logger.LogDebug("Framework directory not found: {0}", sharedDir);
continue;
}
@@ -88,7 +89,7 @@ static IEnumerable GetPathsFromRuntimeConfig(string configPath, ILogger
if (best != null)
yield return best;
else
- logger.WarnFormat("No installed runtime found matching {0} {1} in {2}", fw.Name, fw.Version, sharedDir);
+ logger.LogWarning("No installed runtime found matching {0} {1} in {2}", fw.Name, fw.Version, sharedDir);
}
}
@@ -137,16 +138,16 @@ static string GetMajorMinor(string version) {
return parts.Length >= 2 ? parts[0] + "." + parts[1] : version;
}
- static IEnumerable ProbeAllInstalledRuntimes(ILogger logger) {
+ static IEnumerable ProbeAllInstalledRuntimes(Microsoft.Extensions.Logging.ILogger logger) {
var dotnetRoot = GetDotNetRoot();
if (dotnetRoot == null) {
- logger.Warn("Could not locate .NET installation directory for runtime probing.");
+ logger.LogWarning("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);
+ logger.LogWarning("Shared framework directory not found: {0}", sharedDir);
yield break;
}
diff --git a/Confuser.Core/ILogger.cs b/Confuser.Core/ILogger.cs
deleted file mode 100644
index ec55db448..000000000
--- a/Confuser.Core/ILogger.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-
-namespace Confuser.Core {
- ///
- /// Defines a logger used to log Confuser events
- ///
- public interface ILogger {
- ///
- /// Logs a message at DEBUG level.
- ///
- /// The message.
- void Debug(string msg);
-
- ///
- /// Logs a message at DEBUG level with specified parameters.
- ///
- /// The format.
- /// The arguments.
- void DebugFormat(string format, params object[] args);
-
- ///
- /// Logs a message at INFO level.
- ///
- /// The message.
- void Info(string msg);
-
- ///
- /// Logs a message at INFO level with specified parameters.
- ///
- /// The format.
- /// The arguments.
- void InfoFormat(string format, params object[] args);
-
- ///
- /// Logs a message at WARN level.
- ///
- /// The message.
- void Warn(string msg);
-
- ///
- /// Logs a message at WARN level with specified parameters.
- ///
- /// The format.
- /// The arguments.
- void WarnFormat(string format, params object[] args);
-
- ///
- /// Logs a message at WARN level with specified exception.
- ///
- /// The message.
- /// The exception.
- void WarnException(string msg, Exception ex);
-
- ///
- /// Logs a message at ERROR level.
- ///
- /// The message.
- void Error(string msg);
-
- ///
- /// Logs a message at ERROR level with specified parameters.
- ///
- /// The format.
- /// The arguments.
- void ErrorFormat(string format, params object[] args);
-
- ///
- /// Logs a message at ERROR level with specified exception.
- ///
- /// The message.
- /// The exception.
- void ErrorException(string msg, Exception ex);
-
- ///
- /// Logs the progress of protection.
- ///
- ///
- /// This method is intended to be used with .
- ///
- ///
- ///
- /// for (int i = 0; i < defs.Length; i++) {
- /// logger.Progress(i + 1, defs.Length);
- /// }
- /// logger.EndProgress();
- ///
- ///
- /// The total work amount .
- /// The amount of work done.
- void Progress(int progress, int overall);
-
- ///
- /// End the progress of protection.
- ///
- ///
- void EndProgress();
-
- ///
- /// Logs the finish of protection.
- ///
- /// Indicated whether the protection process is successful.
- void Finish(bool successful);
- }
-}
diff --git a/Confuser.Core/IProgressReporter.cs b/Confuser.Core/IProgressReporter.cs
new file mode 100644
index 000000000..eb1812e67
--- /dev/null
+++ b/Confuser.Core/IProgressReporter.cs
@@ -0,0 +1,24 @@
+namespace Confuser.Core {
+ ///
+ /// Reports progress and completion of a protection process.
+ ///
+ public interface IProgressReporter {
+ ///
+ /// Reports the progress of protection.
+ ///
+ /// The amount of work done.
+ /// The total work amount.
+ void Progress(int progress, int overall);
+
+ ///
+ /// Signals the end of a progress sequence.
+ ///
+ void EndProgress();
+
+ ///
+ /// Signals the finish of the protection process.
+ ///
+ /// Whether the protection process succeeded.
+ void Finish(bool successful);
+ }
+}
diff --git a/Confuser.Core/Marker.cs b/Confuser.Core/Marker.cs
index 889ae036d..95709a058 100644
--- a/Confuser.Core/Marker.cs
+++ b/Confuser.Core/Marker.cs
@@ -7,6 +7,7 @@
using Confuser.Core.Project;
using Confuser.Core.Project.Patterns;
using dnlib.DotNet;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
using Rules = Dictionary;
@@ -83,7 +84,7 @@ public static StrongNamePublicKey LoadSNPubKey(ConfuserContext context, string p
return new StrongNamePublicKey(path);
}
catch (Exception ex) {
- context.Logger.ErrorException("Cannot load the Strong Name Public Key located at: " + path, ex);
+ context.Logger.LogError(ex, "Cannot load the Strong Name Public Key located at: " + path);
throw new ConfuserException(ex);
}
}
@@ -117,7 +118,7 @@ public static StrongNameKey LoadSNKey(ConfuserContext context, string path, stri
return new StrongNameKey(path);
}
catch (Exception ex) {
- context.Logger.ErrorException("Cannot load the Strong Name Key located at: " + path, ex);
+ context.Logger.LogError(ex, "Cannot load the Strong Name Key located at: " + path);
throw new ConfuserException(ex);
}
}
@@ -134,11 +135,11 @@ protected internal virtual MarkerResult MarkProject(ConfuserProject proj, Confus
if (proj.Packer != null) {
if (!packers.ContainsKey(proj.Packer.Id)) {
- context.Logger.ErrorFormat("Cannot find packer with ID '{0}'.", proj.Packer.Id);
+ context.Logger.LogError("Cannot find packer with ID '{0}'.", proj.Packer.Id);
throw new ConfuserException(null);
}
if (proj.Debug)
- context.Logger.Warn("Generated Debug symbols might not be usable with packers!");
+ context.Logger.LogWarning("Generated Debug symbols might not be usable with packers!");
packer = packers[proj.Packer.Id];
packerParams = new Dictionary(proj.Packer, StringComparer.OrdinalIgnoreCase);
@@ -167,7 +168,7 @@ protected internal virtual MarkerResult MarkProject(ConfuserProject proj, Confus
}
foreach (var module in modules) {
- context.Logger.InfoFormat("Loading '{0}'...", module.Item1.Path);
+ context.Logger.LogInformation("Loading '{0}'...", module.Item1.Path);
Rules rules = ParseRules(proj, module.Item1, context);
context.Annotations.Set(module.Item2, SNKey, LoadSNKey(context, module.Item1.SNKeyPath == null ? null : Path.Combine(proj.BaseDirectory, module.Item1.SNKeyPath), module.Item1.SNKeyPassword));
@@ -219,12 +220,12 @@ protected Rules ParseRules(ConfuserProject proj, ProjectModule module, ConfuserC
ret.Add(rule, parser.Parse(rule.Pattern));
}
catch (InvalidPatternException ex) {
- context.Logger.ErrorFormat("Invalid rule pattern: " + rule.Pattern + ": {0}", ex.Message);
+ context.Logger.LogError("Invalid rule pattern: " + rule.Pattern + ": {0}", ex.Message);
throw new ConfuserException(ex);
}
foreach (var setting in rule) {
if (!protections.ContainsKey(setting.Id)) {
- context.Logger.ErrorFormat("Cannot find protection with ID '{0}'.", setting.Id);
+ context.Logger.LogError("Cannot find protection with ID '{0}'.", setting.Id);
throw new ConfuserException(null);
}
}
diff --git a/Confuser.Core/NullLogger.cs b/Confuser.Core/NullLogger.cs
deleted file mode 100644
index c40382274..000000000
--- a/Confuser.Core/NullLogger.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System;
-using dnlib.DotNet;
-
-namespace Confuser.Core {
- ///
- /// An implementation that doesn't actually do any logging.
- ///
- internal class NullLogger : ILogger {
- ///
- /// The singleton instance of .
- ///
- public static readonly NullLogger Instance = new NullLogger();
-
- ///
- /// Prevents a default instance of the class from being created.
- ///
- NullLogger() { }
-
- ///
- public void Debug(string msg) { }
-
- ///
- public void DebugFormat(string format, params object[] args) { }
-
- ///
- public void Info(string msg) { }
-
- ///
- public void InfoFormat(string format, params object[] args) { }
-
- ///
- public void Warn(string msg) { }
-
- ///
- public void WarnFormat(string format, params object[] args) { }
-
- ///
- public void WarnException(string msg, Exception ex) { }
-
- ///
- public void Error(string msg) { }
-
- ///
- public void ErrorFormat(string format, params object[] args) { }
-
- ///
- public void ErrorException(string msg, Exception ex) { }
-
- ///
- public void Progress(int overall, int progress) { }
-
- ///
- public void EndProgress() { }
-
- ///
- public void Finish(bool successful) { }
-
- ///
- public void BeginModule(ModuleDef module) { }
-
- ///
- public void EndModule(ModuleDef module) { }
- }
-}
diff --git a/Confuser.Core/NullProgressReporter.cs b/Confuser.Core/NullProgressReporter.cs
new file mode 100644
index 000000000..53901f472
--- /dev/null
+++ b/Confuser.Core/NullProgressReporter.cs
@@ -0,0 +1,14 @@
+namespace Confuser.Core {
+ ///
+ /// An implementation that discards all progress reports.
+ ///
+ internal sealed class NullProgressReporter : IProgressReporter {
+ public static readonly NullProgressReporter Instance = new NullProgressReporter();
+
+ NullProgressReporter() { }
+
+ public void Progress(int progress, int overall) { }
+ public void EndProgress() { }
+ public void Finish(bool successful) { }
+ }
+}
diff --git a/Confuser.Core/ObfAttrMarker.cs b/Confuser.Core/ObfAttrMarker.cs
index 1c8cb068c..8dbe5ecca 100644
--- a/Confuser.Core/ObfAttrMarker.cs
+++ b/Confuser.Core/ObfAttrMarker.cs
@@ -8,6 +8,7 @@
using Confuser.Core.Project;
using Confuser.Core.Project.Patterns;
using dnlib.DotNet;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
using Rules = Dictionary;
@@ -221,7 +222,7 @@ bool ToInfo(ObfuscationAttributeInfo attr, out ProtectionSettingsInfo info) {
}
if (!ok) {
- context.Logger.WarnFormat("Ignoring rule '{0}' in {1}.", info.Settings, attr.Owner);
+ context.Logger.LogWarning("Ignoring rule '{0}' in {1}.", info.Settings, attr.Owner);
return false;
}
@@ -299,7 +300,7 @@ protected internal override MarkerResult MarkProject(ConfuserProject proj, Confu
if (proj.Packer != null) {
if (!packers.ContainsKey(proj.Packer.Id)) {
- context.Logger.ErrorFormat("Cannot find packer with ID '{0}'.", proj.Packer.Id);
+ context.Logger.LogError("Cannot find packer with ID '{0}'.", proj.Packer.Id);
throw new ConfuserException(null);
}
@@ -325,20 +326,20 @@ protected internal override MarkerResult MarkProject(ConfuserProject proj, Confu
modules.Add(Tuple.Create(module, modDef));
}
catch (BadImageFormatException ex) {
- context.Logger.ErrorFormat("Failed to load \"{0}\" - Assembly does not appear to be a .NET assembly: \"{1}\".", module.Path, ex.Message);
+ context.Logger.LogError("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);
+ context.Logger.LogError("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).");
+ context.Logger.LogError("Hint: For .NET 6+ apps, obfuscate the .dll file, not the .exe (which is a native host stub).");
}
throw new ConfuserException(ex);
}
}
foreach (var module in modules) {
- context.Logger.InfoFormat("Loading '{0}'...", module.Item1.Path);
+ context.Logger.LogInformation("Loading '{0}'...", module.Item1.Path);
Rules rules = ParseRules(proj, module.Item1, context);
MarkModule(module.Item1, module.Item2, rules, module == modules[0]);
@@ -351,7 +352,7 @@ protected internal override MarkerResult MarkProject(ConfuserProject proj, Confu
}
if (proj.Debug && proj.Packer != null)
- context.Logger.Warn("Generated Debug symbols might not be usable with packers!");
+ context.Logger.LogWarning("Generated Debug symbols might not be usable with packers!");
return new MarkerResult(modules.Select(module => module.Item2).ToList(), packer, extModules);
}
@@ -384,7 +385,7 @@ ProtectionSettingsInfo AddRule(ObfuscationAttributeInfo attr, List
@@ -73,7 +74,8 @@ protected void ProtectStub(ConfuserContext context, string fileName, byte[] modu
ConfuserEngine
.Run(
new ConfuserParameters {
- Logger = new PackerLogger(context.Logger),
+ Logger = context.Logger,
+ ProgressReporter = new PackerProgressReporter(context.ProgressReporter, context.Logger),
PluginDiscovery = discovery,
Marker = new PackerMarker(snKey, snPubKey, snDelaySig, snSigKey, snPubSigKey),
Project = proj,
@@ -81,7 +83,7 @@ protected void ProtectStub(ConfuserContext context, string fileName, byte[] modu
}, context.token).Wait();
}
catch (AggregateException ex) {
- context.Logger.Error("Failed to protect packer stub.");
+ context.Logger.LogError("Failed to protect packer stub.");
throw new ConfuserException(ex);
}
@@ -95,71 +97,33 @@ protected void ProtectStub(ConfuserContext context, string fileName, byte[] modu
}
}
catch (IOException ex) {
- context.Logger.WarnException("Failed to remove temporary files of packer.", ex);
+ context.Logger.LogWarning(ex, "Failed to remove temporary files of packer.");
}
}
}
}
- internal class PackerLogger : ILogger {
- readonly ILogger baseLogger;
+ internal class PackerProgressReporter : IProgressReporter {
+ readonly IProgressReporter baseReporter;
+ readonly Microsoft.Extensions.Logging.ILogger baseLogger;
- public PackerLogger(ILogger baseLogger) {
+ public PackerProgressReporter(IProgressReporter baseReporter, Microsoft.Extensions.Logging.ILogger baseLogger) {
+ this.baseReporter = baseReporter;
this.baseLogger = baseLogger;
}
- public void Debug(string msg) {
- baseLogger.Debug(msg);
- }
-
- public void DebugFormat(string format, params object[] args) {
- baseLogger.DebugFormat(format, args);
- }
-
- public void Info(string msg) {
- baseLogger.Info(msg);
- }
-
- public void InfoFormat(string format, params object[] args) {
- baseLogger.InfoFormat(format, args);
- }
-
- public void Warn(string msg) {
- baseLogger.Warn(msg);
- }
-
- public void WarnFormat(string format, params object[] args) {
- baseLogger.WarnFormat(format, args);
- }
-
- public void WarnException(string msg, Exception ex) {
- baseLogger.WarnException(msg, ex);
- }
-
- public void Error(string msg) {
- baseLogger.Error(msg);
- }
-
- public void ErrorFormat(string format, params object[] args) {
- baseLogger.ErrorFormat(format, args);
- }
-
- public void ErrorException(string msg, Exception ex) {
- baseLogger.ErrorException(msg, ex);
- }
-
public void Progress(int progress, int overall) {
- baseLogger.Progress(progress, overall);
+ baseReporter.Progress(progress, overall);
}
public void EndProgress() {
- baseLogger.EndProgress();
+ baseReporter.EndProgress();
}
public void Finish(bool successful) {
if (!successful)
throw new ConfuserException(null);
- baseLogger.Info("Finish protecting packer stub.");
+ baseLogger.LogInformation("Finish protecting packer stub.");
}
}
diff --git a/Confuser.Core/PluginDiscovery.cs b/Confuser.Core/PluginDiscovery.cs
index 642dafcf8..c42de92d6 100644
--- a/Confuser.Core/PluginDiscovery.cs
+++ b/Confuser.Core/PluginDiscovery.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
///
@@ -64,7 +65,7 @@ protected static void AddPlugins(
protections.Add((Protection)Activator.CreateInstance(i));
}
catch (Exception ex) {
- context.Logger.ErrorException("Failed to instantiate protection '" + i.Name + "'.", ex);
+ context.Logger.LogError(ex, "Failed to instantiate protection '" + i.Name + "'.");
}
}
else if (typeof(Packer).IsAssignableFrom(i)) {
@@ -72,7 +73,7 @@ protected static void AddPlugins(
packers.Add((Packer)Activator.CreateInstance(i));
}
catch (Exception ex) {
- context.Logger.ErrorException("Failed to instantiate packer '" + i.Name + "'.", ex);
+ context.Logger.LogError(ex, "Failed to instantiate packer '" + i.Name + "'.");
}
}
else if (typeof(ConfuserComponent).IsAssignableFrom(i)) {
@@ -80,7 +81,7 @@ protected static void AddPlugins(
components.Add((ConfuserComponent)Activator.CreateInstance(i));
}
catch (Exception ex) {
- context.Logger.ErrorException("Failed to instantiate component '" + i.Name + "'.", ex);
+ context.Logger.LogError(ex, "Failed to instantiate component '" + i.Name + "'.");
}
}
}
@@ -103,7 +104,7 @@ protected virtual void GetPluginsInternal(
AddPlugins(context, protections, packers, components, protAsm);
}
catch (Exception ex) {
- context.Logger.WarnException("Failed to load built-in protections.", ex);
+ context.Logger.LogWarning(ex, "Failed to load built-in protections.");
}
try {
@@ -111,7 +112,7 @@ protected virtual void GetPluginsInternal(
AddPlugins(context, protections, packers, components, renameAsm);
}
catch (Exception ex) {
- context.Logger.WarnException("Failed to load renamer.", ex);
+ context.Logger.LogWarning(ex, "Failed to load renamer.");
}
try {
@@ -119,7 +120,7 @@ protected virtual void GetPluginsInternal(
AddPlugins(context, protections, packers, components, renameAsm);
}
catch (Exception ex) {
- context.Logger.WarnException("Failed to load dynamic cipher library.", ex);
+ context.Logger.LogWarning(ex, "Failed to load dynamic cipher library.");
}
foreach (string pluginPath in context.Project.PluginPaths) {
@@ -129,7 +130,7 @@ protected virtual void GetPluginsInternal(
AddPlugins(context, protections, packers, components, plugin);
}
catch (Exception ex) {
- context.Logger.WarnException("Failed to load plugin '" + pluginPath + "'.", ex);
+ context.Logger.LogWarning(ex, "Failed to load plugin '" + pluginPath + "'.");
}
}
}
diff --git a/Confuser.Core/ProtectionPipeline.cs b/Confuser.Core/ProtectionPipeline.cs
index 79da655dc..ba798f559 100644
--- a/Confuser.Core/ProtectionPipeline.cs
+++ b/Confuser.Core/ProtectionPipeline.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Linq;
using dnlib.DotNet;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
///
@@ -127,14 +128,14 @@ public T FindPhase() where T : ProtectionPhase {
internal void ExecuteStage(PipelineStage stage, Action func, Func> targets, ConfuserContext context) {
foreach (ProtectionPhase pre in preStage[stage]) {
context.CheckCancellation();
- context.Logger.DebugFormat("Executing '{0}' phase...", pre.Name);
+ context.Logger.LogDebug("Executing '{0}' phase...", pre.Name);
pre.Execute(context, new ProtectionParameters(pre.Parent, Filter(context, targets(), pre)));
}
context.CheckCancellation();
func(context);
context.CheckCancellation();
foreach (ProtectionPhase post in postStage[stage]) {
- context.Logger.DebugFormat("Executing '{0}' phase...", post.Name);
+ context.Logger.LogDebug("Executing '{0}' phase...", post.Name);
post.Execute(context, new ProtectionParameters(post.Parent, Filter(context, targets(), post)));
context.CheckCancellation();
}
@@ -170,7 +171,7 @@ static IList Filter(ConfuserContext context, IList targets
ProtectionSettings parameters = ProtectionParameters.GetParameters(context, def);
Debug.Assert(parameters != null);
if (parameters == null) {
- context.Logger.ErrorFormat("'{0}' not marked for obfuscation, possibly a bug.", def);
+ context.Logger.LogError("'{0}' not marked for obfuscation, possibly a bug.", def);
throw new ConfuserException(null);
}
return parameters.ContainsKey(phase.Parent);
diff --git a/Confuser.Core/Utils.cs b/Confuser.Core/Utils.cs
index e941cc1e6..9df5c0d01 100644
--- a/Confuser.Core/Utils.cs
+++ b/Confuser.Core/Utils.cs
@@ -204,33 +204,33 @@ public static void RemoveWhere(this IList self, Predicate match) {
}
///
- /// Returns a that log the progress of iterating the specified list.
+ /// Returns a that reports the progress of iterating the specified list.
///
/// The type of list element
/// The list.
- /// The logger.
+ /// The progress reporter.
/// A wrapper of the list.
- public static IEnumerable WithProgress(this IEnumerable enumerable, ILogger logger) {
+ public static IEnumerable WithProgress(this IEnumerable enumerable, IProgressReporter reporter) {
switch (enumerable) {
case IReadOnlyCollection readOnlyCollection:
- return WithProgress(enumerable, readOnlyCollection.Count, logger);
+ return WithProgress(enumerable, readOnlyCollection.Count, reporter);
case ICollection collection:
- return WithProgress(enumerable, collection.Count, logger);
+ return WithProgress(enumerable, collection.Count, reporter);
default:
var buffered = enumerable.ToList();
- return WithProgress(buffered, buffered.Count, logger);
+ return WithProgress(buffered, buffered.Count, reporter);
}
}
- public static IEnumerable WithProgress(this IEnumerable enumerable, int totalCount, ILogger logger) {
+ public static IEnumerable WithProgress(this IEnumerable enumerable, int totalCount, IProgressReporter reporter) {
var counter = 0;
foreach (var obj in enumerable) {
- logger.Progress(counter, totalCount);
+ reporter.Progress(counter, totalCount);
yield return obj;
counter++;
}
- logger.Progress(totalCount, totalCount);
- logger.EndProgress();
+ reporter.Progress(totalCount, totalCount);
+ reporter.EndProgress();
}
}
}
diff --git a/Confuser.Core/WatermarkingProtection.cs b/Confuser.Core/WatermarkingProtection.cs
index 1b1cdb967..7fff32205 100644
--- a/Confuser.Core/WatermarkingProtection.cs
+++ b/Confuser.Core/WatermarkingProtection.cs
@@ -2,6 +2,7 @@
using Confuser.Core.Services;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
+using Microsoft.Extensions.Logging;
namespace Confuser.Core {
public sealed class WatermarkingProtection : Protection {
@@ -45,7 +46,7 @@ public WatermarkingPhase(ConfuserComponent parent) : base(parent) { }
protected internal override void Execute(ConfuserContext context, ProtectionParameters parameters) {
var marker = context.Registry.GetService();
- context.Logger.Debug("Watermarking...");
+ context.Logger.LogDebug("Watermarking...");
foreach (var module in parameters.Targets.OfType()) {
var attrRef = module.CorLibTypes.GetTypeRef("System", "Attribute");
var attrType = module.FindNormal("ConfusedByAttribute");
diff --git a/Confuser.MSBuild.Tasks/ConfuseTask.cs b/Confuser.MSBuild.Tasks/ConfuseTask.cs
index f4526c910..02986a3f0 100644
--- a/Confuser.MSBuild.Tasks/ConfuseTask.cs
+++ b/Confuser.MSBuild.Tasks/ConfuseTask.cs
@@ -24,17 +24,18 @@ public override bool Execute() {
project.Load(xmlDoc);
project.OutputDirectory = Path.GetDirectoryName(Path.GetFullPath(OutputAssembly.ItemSpec));
- var logger = new MSBuildLogger(Log);
+ var progressReporter = new MSBuildProgressReporter();
var parameters = new ConfuserParameters {
Project = project,
- Logger = logger
+ Logger = new MSBuildMelLogger(Log),
+ ProgressReporter = progressReporter
};
ConfuserEngine.Run(parameters).Wait();
ConfusedFiles = project.Select(m => new TaskItem(Path.Combine(project.OutputDirectory, m.Path))).Cast().ToArray();
- return !logger.HasError;
+ return !progressReporter.HasError;
}
}
}
diff --git a/Confuser.MSBuild.Tasks/MSBuildLogger.cs b/Confuser.MSBuild.Tasks/MSBuildLogger.cs
index fdcb2319b..8f1f7c81f 100644
--- a/Confuser.MSBuild.Tasks/MSBuildLogger.cs
+++ b/Confuser.MSBuild.Tasks/MSBuildLogger.cs
@@ -1,61 +1,54 @@
-using System;
+using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
-using ILogger = Confuser.Core.ILogger;
+using Microsoft.Extensions.Logging;
namespace Confuser.MSBuild.Tasks {
- internal sealed class MSBuildLogger : ILogger {
+ internal sealed class MSBuildMelLogger : Microsoft.Extensions.Logging.ILogger {
private readonly TaskLoggingHelper loggingHelper;
- internal bool HasError { get; private set; }
-
- internal MSBuildLogger(TaskLoggingHelper loggingHelper) =>
+ internal MSBuildMelLogger(TaskLoggingHelper loggingHelper) =>
this.loggingHelper = loggingHelper ?? throw new ArgumentNullException(nameof(loggingHelper));
- void ILogger.Debug(string msg) => loggingHelper.LogMessage(MessageImportance.Low, "[DEBUG] " + msg);
-
- void ILogger.DebugFormat(string format, params object[] args) {
- loggingHelper.LogMessage(MessageImportance.Low, "[DEBUG] " + format, args);
+ public IDisposable BeginScope(TState state) => null;
+
+ public bool IsEnabled(LogLevel logLevel) => true;
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state,
+ Exception exception, Func formatter) {
+ var message = formatter(state, exception);
+ switch (logLevel) {
+ case LogLevel.Trace:
+ case LogLevel.Debug:
+ loggingHelper.LogMessage(MessageImportance.Low, message);
+ break;
+ case LogLevel.Information:
+ loggingHelper.LogMessage(MessageImportance.Normal, message);
+ break;
+ case LogLevel.Warning:
+ loggingHelper.LogWarning(message);
+ if (exception != null) loggingHelper.LogWarningFromException(exception);
+ break;
+ case LogLevel.Error:
+ case LogLevel.Critical:
+ loggingHelper.LogError(message);
+ if (exception != null) loggingHelper.LogErrorFromException(exception);
+ break;
+ }
}
+ }
- void ILogger.EndProgress() { }
+ internal sealed class MSBuildProgressReporter : Confuser.Core.IProgressReporter {
+ internal bool HasError { get; private set; }
- void ILogger.Error(string msg) {
- loggingHelper.LogError(msg);
- HasError = true;
- }
+ void Confuser.Core.IProgressReporter.Progress(int progress, int overall) { }
- void ILogger.ErrorException(string msg, Exception ex) {
- loggingHelper.LogError(msg);
- loggingHelper.LogErrorFromException(ex);
- HasError = true;
- }
+ void Confuser.Core.IProgressReporter.EndProgress() { }
- void ILogger.ErrorFormat(string format, params object[] args) {
- loggingHelper.LogError(format, args);
- HasError = true;
- }
-
- void ILogger.Finish(bool successful) {
+ void Confuser.Core.IProgressReporter.Finish(bool successful) {
if (!successful) {
- HasError = false;
+ HasError = true;
}
}
-
- void ILogger.Info(string msg) => loggingHelper.LogMessage(MessageImportance.Normal, msg);
-
- void ILogger.InfoFormat(string format, params object[] args) =>
- loggingHelper.LogMessage(MessageImportance.Normal, format, args);
-
- void ILogger.Progress(int progress, int overall) { }
-
- void ILogger.Warn(string msg) => loggingHelper.LogWarning(msg);
-
- void ILogger.WarnException(string msg, Exception ex) {
- loggingHelper.LogWarning(msg);
- loggingHelper.LogWarningFromException(ex);
- }
-
- void ILogger.WarnFormat(string format, params object[] args) => loggingHelper.LogWarning(format, args);
}
}
diff --git a/Confuser.Protections/AntiTamper/JITMode.cs b/Confuser.Protections/AntiTamper/JITMode.cs
index 23f41e90f..074c54b40 100644
--- a/Confuser.Protections/AntiTamper/JITMode.cs
+++ b/Confuser.Protections/AntiTamper/JITMode.cs
@@ -12,6 +12,7 @@
using dnlib.DotNet.Emit;
using dnlib.DotNet.MD;
using dnlib.DotNet.Writer;
+using Microsoft.Extensions.Logging;
namespace Confuser.Protections.AntiTamper {
internal class JITMode : IModeHandler {
@@ -154,11 +155,11 @@ public void HandleMD(AntiTamperProtection parent, ConfuserContext context, Prote
void OnWriterEvent(object sender, ModuleWriterEventArgs e) {
var writer = (ModuleWriterBase)sender;
if (e.Event == ModuleWriterEvent.MDBeginWriteMethodBodies) {
- context.Logger.Debug("Extracting method bodies...");
+ context.Logger.LogDebug("Extracting method bodies...");
CreateSection(writer);
}
else if (e.Event == ModuleWriterEvent.BeginStrongNameSign) {
- context.Logger.Debug("Encrypting method section...");
+ context.Logger.LogDebug("Encrypting method section...");
EncryptSection(writer);
}
}
@@ -210,7 +211,7 @@ void CreateSection(ModuleWriterBase writer) {
newSection.Add(bodyIndex, 0x10);
// save methods
- foreach (MethodDef method in methods.WithProgress(context.Logger)) {
+ foreach (MethodDef method in methods.WithProgress(context.ProgressReporter)) {
if (!method.HasBody)
continue;
diff --git a/Confuser.Protections/Compress/Compressor.cs b/Confuser.Protections/Compress/Compressor.cs
index 28e8ec5cd..ca81339db 100644
--- a/Confuser.Protections/Compress/Compressor.cs
+++ b/Confuser.Protections/Compress/Compressor.cs
@@ -15,6 +15,7 @@
using dnlib.DotNet.MD;
using dnlib.DotNet.Writer;
using dnlib.PE;
+using Microsoft.Extensions.Logging;
using FileAttributes = dnlib.DotNet.FileAttributes;
using SR = System.Reflection;
@@ -50,7 +51,7 @@ protected override void PopulatePipeline(ProtectionPipeline pipeline) {
protected override void Pack(ConfuserContext context, ProtectionParameters parameters) {
var ctx = context.Annotations.Get(context, ContextKey);
if (ctx == null) {
- context.Logger.Error("No executable module!");
+ context.Logger.LogError("No executable module!");
throw new ConfuserException(null);
}
@@ -164,7 +165,7 @@ void PackModules(ConfuserContext context, CompressorContext compCtx, ModuleDef s
state = state * 0x5e3f1f + chr;
byte[] encrypted = compCtx.Encrypt(comp, entry.Value, state, progress => {
progress = (progress + moduleIndex) / modules.Count;
- context.Logger.Progress((int)(progress * 10000), 10000);
+ context.ProgressReporter.Progress((int)(progress * 10000), 10000);
});
context.CheckCancellation();
@@ -172,7 +173,7 @@ void PackModules(ConfuserContext context, CompressorContext compCtx, ModuleDef s
stubModule.Resources.Add(resource);
moduleIndex++;
}
- context.Logger.EndProgress();
+ context.ProgressReporter.EndProgress();
}
void InjectData(ConfuserContext context, ModuleDef stubModule, MethodDef method, byte[] data) {
@@ -222,7 +223,7 @@ void InjectStub(ConfuserContext context, CompressorContext compCtx, ProtectionPa
}
compCtx.Deriver.Init(context, random);
- context.Logger.Debug("Encrypting modules...");
+ context.Logger.LogDebug("Encrypting modules...");
// Main
MethodDef entryPoint = defs.OfType().Single(method => method.Name == "Main");
@@ -245,8 +246,8 @@ void InjectStub(ConfuserContext context, CompressorContext compCtx, ProtectionPa
compCtx.OriginModule = context.OutputModules[compCtx.ModuleIndex];
byte[] encryptedModule = compCtx.Encrypt(comp, compCtx.OriginModule, seed,
- progress => context.Logger.Progress((int)(progress * 10000), 10000));
- context.Logger.EndProgress();
+ progress => context.ProgressReporter.Progress((int)(progress * 10000), 10000));
+ context.ProgressReporter.EndProgress();
context.CheckCancellation();
compCtx.EncryptedModule = encryptedModule;
diff --git a/Confuser.Protections/Compress/ExtractPhase.cs b/Confuser.Protections/Compress/ExtractPhase.cs
index 24fd835b0..6fc5e39aa 100644
--- a/Confuser.Protections/Compress/ExtractPhase.cs
+++ b/Confuser.Protections/Compress/ExtractPhase.cs
@@ -7,6 +7,7 @@
using dnlib.DotNet;
using dnlib.DotNet.MD;
using dnlib.DotNet.Writer;
+using Microsoft.Extensions.Logging;
namespace Confuser.Protections.Compress {
internal class ExtractPhase : ProtectionPhase {
@@ -29,7 +30,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
if (context.Annotations.Get(context, Compressor.ContextKey) != null) {
if (isExe) {
- context.Logger.Error("Too many executable modules!");
+ context.Logger.LogError("Too many executable modules!");
throw new ConfuserException(null);
}
return;
diff --git a/Confuser.Protections/Constants/EncodePhase.cs b/Confuser.Protections/Constants/EncodePhase.cs
index c108d14d4..b3db5bd68 100644
--- a/Confuser.Protections/Constants/EncodePhase.cs
+++ b/Confuser.Protections/Constants/EncodePhase.cs
@@ -38,12 +38,12 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
// Encode constants
moduleCtx.ReferenceRepl = new Dictionary>>();
moduleCtx.EncodedBuffer = new List();
- foreach (var entry in ldInit.WithProgress(context.Logger)) // Ensure the array length haven't been encoded yet
+ foreach (var entry in ldInit.WithProgress(context.ProgressReporter)) // Ensure the array length haven't been encoded yet
{
EncodeInitializer(moduleCtx, entry.Key, entry.Value);
context.CheckCancellation();
}
- foreach (var entry in ldc.WithProgress(context.Logger)) {
+ foreach (var entry in ldc.WithProgress(context.ProgressReporter)) {
if (entry.Key is string) {
EncodeString(moduleCtx, (string)entry.Key, entry.Value);
}
@@ -234,7 +234,7 @@ void ExtractConstants(
Dictionary>> ldInit) {
var dataFields = new HashSet();
var fieldRefs = new HashSet();
- foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.Logger)) {
+ foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.ProgressReporter)) {
if (!method.HasBody)
continue;
diff --git a/Confuser.Protections/ControlFlow/ControlFlowPhase.cs b/Confuser.Protections/ControlFlow/ControlFlowPhase.cs
index 3f1b15e28..addeb6257 100644
--- a/Confuser.Protections/ControlFlow/ControlFlowPhase.cs
+++ b/Confuser.Protections/ControlFlow/ControlFlowPhase.cs
@@ -9,6 +9,7 @@
using dnlib.DotNet.MD;
using dnlib.DotNet.Pdb;
using dnlib.DotNet.Writer;
+using Microsoft.Extensions.Logging;
namespace Confuser.Protections.ControlFlow {
internal class ControlFlowPhase : ProtectionPhase {
@@ -74,7 +75,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
bool disabledOpti = DisabledOptimization(context.CurrentModule);
RandomGenerator random = context.Registry.GetService().GetRandomGenerator(ControlFlowProtection._FullId);
- foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.Logger))
+ foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.ProgressReporter))
if (method.HasBody && method.Body.Instructions.Count > 0) {
ProcessMethod(method.Body, ParseParameters(method, context, parameters, random, disabledOpti));
context.CheckCancellation();
@@ -90,7 +91,7 @@ static ManglerBase GetMangler(CFType type) {
void ProcessMethod(CilBody body, CFContext ctx) {
uint maxStack;
if (!MaxStackCalculator.GetMaxStack(body.Instructions, body.ExceptionHandlers, out maxStack)) {
- ctx.Context.Logger.Error("Failed to calcuate maxstack.");
+ ctx.Context.Logger.LogError("Failed to calcuate maxstack.");
throw new ConfuserException(null);
}
body.MaxStack = (ushort)maxStack;
diff --git a/Confuser.Protections/HardeningPhase.cs b/Confuser.Protections/HardeningPhase.cs
index 17dabdfe4..0e5defa8c 100644
--- a/Confuser.Protections/HardeningPhase.cs
+++ b/Confuser.Protections/HardeningPhase.cs
@@ -4,6 +4,7 @@
using Confuser.Core.Services;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
+using Microsoft.Extensions.Logging;
namespace Confuser.Protections {
internal sealed class HardeningPhase : ProtectionPhase {
@@ -31,7 +32,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
private static void HardenMethod(ConfuserContext context, ModuleDef module) {
var cctor = module.GlobalType.FindStaticConstructor();
if (cctor == null) {
- context.Logger.Debug("No .cctor containing protection code found. Nothing to do.");
+ context.Logger.LogDebug("No .cctor containing protection code found. Nothing to do.");
return;
}
diff --git a/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs b/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs
index 3e299f2fa..e08baf3d4 100644
--- a/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs
+++ b/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs
@@ -103,7 +103,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
var store = new RPStore { random = random };
- foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.Logger))
+ foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.ProgressReporter))
if (method.HasBody && method.Body.Instructions.Count > 0) {
ProcessMethod(ParseParameters(method, context, parameters, store));
context.CheckCancellation();
diff --git a/Confuser.Protections/Resources/InjectPhase.cs b/Confuser.Protections/Resources/InjectPhase.cs
index 3d086568b..de803fd58 100644
--- a/Confuser.Protections/Resources/InjectPhase.cs
+++ b/Confuser.Protections/Resources/InjectPhase.cs
@@ -10,6 +10,7 @@
using Confuser.Renamer;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
+using Microsoft.Extensions.Logging;
namespace Confuser.Protections.Resources {
internal class InjectPhase : ProtectionPhase {
@@ -27,7 +28,7 @@ public override string Name {
protected override void Execute(ConfuserContext context, ProtectionParameters parameters) {
if (parameters.Targets.Any()) {
if (!UTF8String.IsNullOrEmpty(context.CurrentModule.Assembly.Culture)) {
- context.Logger.DebugFormat("Skipping resource encryption for satellite assembly '{0}'.",
+ context.Logger.LogDebug("Skipping resource encryption for satellite assembly '{0}'.",
context.CurrentModule.Assembly.FullName);
return;
}
diff --git a/Confuser.Protections/Resources/MDPhase.cs b/Confuser.Protections/Resources/MDPhase.cs
index 0f676cd26..741ebbf32 100644
--- a/Confuser.Protections/Resources/MDPhase.cs
+++ b/Confuser.Protections/Resources/MDPhase.cs
@@ -10,6 +10,7 @@
using dnlib.DotNet;
using dnlib.DotNet.MD;
using dnlib.DotNet.Writer;
+using Microsoft.Extensions.Logging;
namespace Confuser.Protections.Resources {
internal class MDPhase {
@@ -28,7 +29,7 @@ void OnWriterEvent(object sender, ModuleWriterEventArgs e) {
var writer = (ModuleWriterBase)sender;
if (e.Event == ModuleWriterEvent.MDBeginAddResources) {
ctx.Context.CheckCancellation();
- ctx.Context.Logger.Debug("Encrypting resources...");
+ ctx.Context.Logger.LogDebug("Encrypting resources...");
bool hasPacker = ctx.Context.Packer != null;
List resources = ctx.Module.Resources.OfType().ToList();
@@ -68,8 +69,8 @@ void OnWriterEvent(object sender, ModuleWriterEventArgs e) {
// compress
moduleBuff = ctx.Context.Registry.GetService().Compress(
moduleBuff,
- progress => ctx.Context.Logger.Progress((int)(progress * 10000), 10000));
- ctx.Context.Logger.EndProgress();
+ progress => ctx.Context.ProgressReporter.Progress((int)(progress * 10000), 10000));
+ ctx.Context.ProgressReporter.EndProgress();
ctx.Context.CheckCancellation();
uint compressedLen = (uint)(moduleBuff.Length + 3) / 4;
diff --git a/Confuser.Protections/TypeScrambler/AnalyzePhase.cs b/Confuser.Protections/TypeScrambler/AnalyzePhase.cs
index fbec05899..5c8304208 100644
--- a/Confuser.Protections/TypeScrambler/AnalyzePhase.cs
+++ b/Confuser.Protections/TypeScrambler/AnalyzePhase.cs
@@ -19,7 +19,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
var typeService = context.Registry.GetService();
Debug.Assert(typeService != null, $"{nameof(typeService)} != null");
- foreach (var target in parameters.Targets.WithProgress(context.Logger)) {
+ foreach (var target in parameters.Targets.WithProgress(context.ProgressReporter)) {
switch (target) {
case TypeDef typeDef:
typeService.AddScannedItem(new ScannedType(typeDef));
diff --git a/Confuser.Protections/TypeScrambler/ScramblePhase.cs b/Confuser.Protections/TypeScrambler/ScramblePhase.cs
index 6fc86bfd0..1af58491f 100644
--- a/Confuser.Protections/TypeScrambler/ScramblePhase.cs
+++ b/Confuser.Protections/TypeScrambler/ScramblePhase.cs
@@ -27,7 +27,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
// In this stage the references to the scrambled types need to be fixed. This needs to be done for all
// methods in the assembly, because all methods may contain references to the scrambled types and methods.
- foreach (var def in context.CurrentModule.FindDefinitions().WithProgress(context.Logger)) {
+ foreach (var def in context.CurrentModule.FindDefinitions().WithProgress(context.ProgressReporter)) {
switch (def) {
case MethodDef md:
if (md.HasReturnType)
diff --git a/Confuser.Renamer/AnalyzePhase.cs b/Confuser.Renamer/AnalyzePhase.cs
index 4d95e27d1..94e9f7753 100644
--- a/Confuser.Renamer/AnalyzePhase.cs
+++ b/Confuser.Renamer/AnalyzePhase.cs
@@ -6,6 +6,7 @@
using Confuser.Core.Services;
using Confuser.Renamer.Analyzers;
using dnlib.DotNet;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer {
internal class AnalyzePhase : ProtectionPhase {
@@ -34,12 +35,12 @@ void ParseParameters(IDnlibDef def, ConfuserContext context, NameService service
protected override void Execute(ConfuserContext context, ProtectionParameters parameters) {
var service = (NameService)context.Registry.GetService();
- context.Logger.Debug("Building VTables & identifier list...");
+ context.Logger.LogDebug("Building VTables & identifier list...");
foreach (ModuleDef moduleDef in parameters.Targets.OfType())
moduleDef.EnableTypeDefFindCache = true;
- foreach (IDnlibDef def in parameters.Targets.WithProgress(context.Logger)) {
+ foreach (IDnlibDef def in parameters.Targets.WithProgress(context.ProgressReporter)) {
ParseParameters(def, context, service, parameters);
if (def is ModuleDef module) {
@@ -56,10 +57,10 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
context.CheckCancellation();
}
- context.Logger.Debug("Analyzing...");
+ context.Logger.LogDebug("Analyzing...");
RegisterRenamers(context, service);
IList renamers = service.Renamers;
- foreach (IDnlibDef def in parameters.Targets.WithProgress(context.Logger)) {
+ foreach (IDnlibDef def in parameters.Targets.WithProgress(context.ProgressReporter)) {
Analyze(service, context, parameters, def, true);
context.CheckCancellation();
}
@@ -106,35 +107,35 @@ void RegisterRenamers(ConfuserContext context, NameService service) {
if (wpf) {
var wpfAnalyzer = new WPFAnalyzer();
- context.Logger.Debug("WPF found, enabling compatibility.");
+ context.Logger.LogDebug("WPF found, enabling compatibility.");
service.Renamers.Add(wpfAnalyzer);
if (caliburn) {
- context.Logger.Debug("Caliburn.Micro found, enabling compatibility.");
+ context.Logger.LogDebug("Caliburn.Micro found, enabling compatibility.");
service.Renamers.Add(new CaliburnAnalyzer(wpfAnalyzer));
}
}
if (winforms) {
var winformsAnalyzer = new WinFormsAnalyzer();
- context.Logger.Debug("WinForms found, enabling compatibility.");
+ context.Logger.LogDebug("WinForms found, enabling compatibility.");
service.Renamers.Add(winformsAnalyzer);
}
if (json) {
var jsonAnalyzer = new JsonAnalyzer();
- context.Logger.Debug("Newtonsoft.Json found, enabling compatibility.");
+ context.Logger.LogDebug("Newtonsoft.Json found, enabling compatibility.");
service.Renamers.Add(jsonAnalyzer);
}
if (visualBasic) {
var vbAnalyzer = new VisualBasicRuntimeAnalyzer();
- context.Logger.Debug("Visual Basic Embedded Runtime found, enabling compatibility.");
+ context.Logger.LogDebug("Visual Basic Embedded Runtime found, enabling compatibility.");
service.Renamers.Add(vbAnalyzer);
}
if (vsComposition) {
var analyzer = new VsCompositionAnalyzer();
- context.Logger.Debug("Visual Studio Composition found, enabling compatibility.");
+ context.Logger.LogDebug("Visual Studio Composition found, enabling compatibility.");
service.Renamers.Add(analyzer);
}
}
diff --git a/Confuser.Renamer/Analyzers/CallSiteAnalyzer.cs b/Confuser.Renamer/Analyzers/CallSiteAnalyzer.cs
index b4cf6f007..0a7cca3fa 100644
--- a/Confuser.Renamer/Analyzers/CallSiteAnalyzer.cs
+++ b/Confuser.Renamer/Analyzers/CallSiteAnalyzer.cs
@@ -4,6 +4,7 @@
using Confuser.Renamer.References;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.Analyzers {
internal sealed class CallSiteAnalyzer : IRenamer {
@@ -61,7 +62,7 @@ private static void HandleBinderInvokeMember(ConfuserContext context, MethodDef
BuildMemberReferences(context, typeDefOrRef, boundMemberName, nameInstruction);
}
else {
- context.Logger.WarnFormat(
+ context.Logger.LogWarning(
"Failed to resolve type for dynamic invoke member in {0} - blocking all members with name {1} from renaming.",
method, boundMemberName);
diff --git a/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs b/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs
index 5deeeaa4a..f6bbc267c 100644
--- a/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs
+++ b/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs
@@ -7,7 +7,7 @@
using Confuser.Renamer.References;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
-using ILogger = Confuser.Core.ILogger;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.Analyzers {
///
@@ -20,7 +20,7 @@ void IRenamer.Analyze(ConfuserContext context, INameService service, ProtectionP
Analyze(service, context.Registry.GetService(), context.Modules.Cast().ToArray(), context.Logger, method);
}
- public void Analyze(INameService nameService, ITraceService traceService, IReadOnlyList moduleDefs, ILogger logger, MethodDef method) {
+ public void Analyze(INameService nameService, ITraceService traceService, IReadOnlyList moduleDefs, Microsoft.Extensions.Logging.ILogger logger, MethodDef method) {
if (!method.HasBody) return;
MethodTrace methodTrace = null;
@@ -49,7 +49,7 @@ MethodTrace GetMethodTrace() {
var trace = GetMethodTrace();
var arguments = trace.TraceArguments(instr);
if (arguments == null) {
- logger.WarnFormat(Resources.ReflectionAnalyzer_Analyze_TracingArgumentsFailed, calledMethod.FullName, method.FullName);
+ logger.LogWarning(Resources.ReflectionAnalyzer_Analyze_TracingArgumentsFailed, calledMethod.FullName, method.FullName);
}
else if (arguments.Length >= 2) {
var types = GetReferencedTypes(method.Body.Instructions[arguments[0]], method, trace);
diff --git a/Confuser.Renamer/Analyzers/ResourceAnalyzer.cs b/Confuser.Renamer/Analyzers/ResourceAnalyzer.cs
index 9f58d66ce..fccfc9d40 100644
--- a/Confuser.Renamer/Analyzers/ResourceAnalyzer.cs
+++ b/Confuser.Renamer/Analyzers/ResourceAnalyzer.cs
@@ -6,6 +6,7 @@
using Confuser.Renamer.References;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.Analyzers {
internal class ResourceAnalyzer : IRenamer {
@@ -23,7 +24,7 @@ public void Analyze(ConfuserContext context, INameService service, ProtectionPar
string nameAsmName = asmName.Substring(0, asmName.Length - ".resources".Length);
ModuleDef mainModule = context.Modules.SingleOrDefault(mod => mod.Assembly.Name == nameAsmName);
if (mainModule == null) {
- context.Logger.ErrorFormat("Could not find main assembly of satellite assembly '{0}'.", module.Assembly.FullName);
+ context.Logger.LogError("Could not find main assembly of satellite assembly '{0}'.", module.Assembly.FullName);
throw new ConfuserException(null);
}
@@ -35,7 +36,7 @@ public void Analyze(ConfuserContext context, INameService service, ProtectionPar
TypeDef type = mainModule.FindReflection(typeName);
if (type == null) {
- context.Logger.WarnFormat(Resources.ResourceAnalyzer_Analyze_CouldNotFindResourceType, typeName);
+ context.Logger.LogWarning(Resources.ResourceAnalyzer_Analyze_CouldNotFindResourceType, typeName);
continue;
}
string format = $"{{0}}.{culture}.resources";
@@ -68,7 +69,7 @@ public void Analyze(ConfuserContext context, INameService service, ProtectionPar
}
if (type == null) {
- context.Logger.WarnFormat(Resources.ResourceAnalyzer_Analyze_CouldNotFindResourceType, typeName);
+ context.Logger.LogWarning(Resources.ResourceAnalyzer_Analyze_CouldNotFindResourceType, typeName);
continue;
}
service.ReduceRenameMode(type, RenameMode.Reflection);
diff --git a/Confuser.Renamer/Analyzers/TypeBlobAnalyzer.cs b/Confuser.Renamer/Analyzers/TypeBlobAnalyzer.cs
index 15a3e94f1..5b755ab59 100644
--- a/Confuser.Renamer/Analyzers/TypeBlobAnalyzer.cs
+++ b/Confuser.Renamer/Analyzers/TypeBlobAnalyzer.cs
@@ -7,6 +7,7 @@
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.DotNet.MD;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.Analyzers {
public sealed class TypeBlobAnalyzer : IRenamer {
@@ -16,7 +17,7 @@ void IRenamer.Analyze(ConfuserContext context, INameService service, ProtectionP
Analyze(service, context.Modules, context.Logger, moduleDef);
}
- public static void Analyze(INameService service, ICollection modules, Core.ILogger logger, ModuleDefMD module) {
+ public static void Analyze(INameService service, ICollection modules, Microsoft.Extensions.Logging.ILogger logger, ModuleDefMD module) {
// MemberRef
var table = module.TablesStream.Get(Table.Method);
var len = table.Rows;
@@ -75,7 +76,7 @@ public static void Analyze(INameService service, ICollection module
foreach (var arg in attr.NamedArguments) {
var memberDef = FindArgumentMemberDef(arg, attrType);
if (memberDef == null)
- logger.WarnFormat(
+ logger.LogWarning(
arg.IsField ? "Failed to resolve CA field '{0}::{1} : {2}'." : "Failed to resolve CA property '{0}::{1} : {2}'.",
attrType, arg.Name, arg.Type);
else
diff --git a/Confuser.Renamer/Analyzers/WPFAnalyzer.cs b/Confuser.Renamer/Analyzers/WPFAnalyzer.cs
index ebf49dceb..51840bfd9 100644
--- a/Confuser.Renamer/Analyzers/WPFAnalyzer.cs
+++ b/Confuser.Renamer/Analyzers/WPFAnalyzer.cs
@@ -14,6 +14,7 @@
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.IO;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.Analyzers {
internal class WPFAnalyzer : IRenamer {
@@ -47,7 +48,7 @@ public void PreRename(ConfuserContext context, INameService service, ProtectionP
var renameMode = parameters.GetParameter(context, def, "renXamlMode", RenameMode.Letters);
if (renameMode < RenameMode.Letters) {
var illegalValues = Enum.GetValues(typeof(RenameMode)).Cast().Where(m => m < RenameMode.Letters);
- context.Logger.Warn("The renaming modes " + String.Join(", ", illegalValues) + " are not allowed for XAML resources. Letters mode will be used.");
+ context.Logger.LogWarning("The renaming modes " + String.Join(", ", illegalValues) + " are not allowed for XAML resources. Letters mode will be used.");
renameMode = RenameMode.Letters;
}
@@ -82,7 +83,7 @@ public void PreRename(ConfuserContext context, INameService service, ProtectionP
string decodedNewName = decodedDirectory + fileName;
string encodedNewName = encodedDirectory + fileName;
- context.Logger.Debug(String.Format("Preserving virtual paths. Replaced {0} with {1}", decodedName, decodedNewName));
+ context.Logger.LogDebug(String.Format("Preserving virtual paths. Replaced {0} with {1}", decodedName, decodedNewName));
bool renameOk = references.All(r => r.CanRename(module, decodedName, decodedNewName) || r.CanRename(module, encodedName, encodedNewName));
@@ -199,7 +200,7 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
operand = match.Groups[2].Value;
}
else if (operand.Contains("/"))
- context.Logger.WarnFormat("Fail to extract XAML name from '{0}'.", instr.Operand);
+ context.Logger.LogWarning("Fail to extract XAML name from '{0}'.", instr.Operand);
var reference = new BAMLStringReference(refModule, instr);
operand = WebUtility.UrlDecode(operand.TrimStart('/'));
@@ -222,14 +223,14 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
int[] args = trace.TraceArguments(instrInfo.Item2);
if (args == null) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract dependency property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract dependency property name in '{0}'.", method.FullName);
erred = true;
continue;
}
Instruction ldstr = method.Body.Instructions[args[0]];
if (ldstr.OpCode.Code != Code.Ldstr) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract dependency property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract dependency property name in '{0}'.", method.FullName);
erred = true;
continue;
}
@@ -270,10 +271,10 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
}
if (!found) {
if (instrInfo.Item1)
- context.Logger.WarnFormat("Failed to find the accessors of attached dependency property '{0}' in type '{1}'.",
+ context.Logger.LogWarning("Failed to find the accessors of attached dependency property '{0}' in type '{1}'.",
name, declType.FullName);
else
- context.Logger.WarnFormat("Failed to find the CLR property of normal dependency property '{0}' in type '{1}'.",
+ context.Logger.LogWarning("Failed to find the CLR property of normal dependency property '{0}' in type '{1}'.",
name, declType.FullName);
}
}
@@ -283,14 +284,14 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
int[] args = trace.TraceArguments(instr);
if (args == null) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract routed event name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract routed event name in '{0}'.", method.FullName);
erred = true;
continue;
}
Instruction ldstr = method.Body.Instructions[args[0]];
if (ldstr.OpCode.Code != Code.Ldstr) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract routed event name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract routed event name in '{0}'.", method.FullName);
erred = true;
continue;
}
@@ -300,7 +301,7 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
EventDef eventDef = null;
if ((eventDef = declType.FindEvent(name)) == null) {
- context.Logger.WarnFormat("Failed to find the CLR event of routed event '{0}' in type '{1}'.",
+ context.Logger.LogWarning("Failed to find the CLR event of routed event '{0}' in type '{1}'.",
name, declType.FullName);
continue;
}
diff --git a/Confuser.Renamer/Analyzers/WinFormsAnalyzer.cs b/Confuser.Renamer/Analyzers/WinFormsAnalyzer.cs
index c8f78e92c..9a33714c7 100644
--- a/Confuser.Renamer/Analyzers/WinFormsAnalyzer.cs
+++ b/Confuser.Renamer/Analyzers/WinFormsAnalyzer.cs
@@ -6,6 +6,7 @@
using Confuser.Core.Services;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.Analyzers {
public class WinFormsAnalyzer : IRenamer {
@@ -66,7 +67,7 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
int[] args = trace.TraceArguments(instrInfo.Item2);
if (args == null) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract binding property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract binding property name in '{0}'.", method.FullName);
erred = true;
continue;
}
@@ -75,14 +76,14 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
var propertyName = ResolveNameInstruction(method, args, ref argumentIndex);
if (propertyName.OpCode.Code != Code.Ldstr) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract binding property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract binding property name in '{0}'.", method.FullName);
erred = true;
}
else {
List props;
if (!properties.TryGetValue((string)propertyName.Operand, out props)) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract target property in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract target property in '{0}'.", method.FullName);
erred = true;
}
else {
@@ -95,14 +96,14 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
var dataMember = ResolveNameInstruction(method, args, ref argumentIndex);
if (dataMember.OpCode.Code != Code.Ldstr) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract binding property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract binding property name in '{0}'.", method.FullName);
erred = true;
}
else {
List props;
if (!properties.TryGetValue((string)dataMember.Operand, out props)) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract target property in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract target property in '{0}'.", method.FullName);
erred = true;
}
else {
@@ -116,7 +117,7 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
int[] args = trace.TraceArguments(instrInfo);
if (args == null) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract binding property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract binding property name in '{0}'.", method.FullName);
erred = true;
continue;
}
@@ -125,13 +126,13 @@ void AnalyzeMethod(ConfuserContext context, INameService service, MethodDef meth
var propertyName = ResolveNameInstruction(method, args, ref argumentIndex);
if (propertyName.OpCode.Code != Code.Ldstr) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract binding property name in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract binding property name in '{0}'.", method.FullName);
erred = true;
}
else {
if (!properties.TryGetValue((string)propertyName.Operand, out var props)) {
if (!erred)
- context.Logger.WarnFormat("Failed to extract target property in '{0}'.", method.FullName);
+ context.Logger.LogWarning("Failed to extract target property in '{0}'.", method.FullName);
erred = true;
}
else {
diff --git a/Confuser.Renamer/BAML/BAMLAnalyzer.cs b/Confuser.Renamer/BAML/BAMLAnalyzer.cs
index dec64a2e7..f4569a595 100644
--- a/Confuser.Renamer/BAML/BAMLAnalyzer.cs
+++ b/Confuser.Renamer/BAML/BAMLAnalyzer.cs
@@ -9,6 +9,7 @@
using Confuser.Renamer.Analyzers;
using Confuser.Renamer.References;
using dnlib.DotNet;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer.BAML {
internal class BAMLAnalyzer {
@@ -338,7 +339,7 @@ void ProcessElementBody(BamlElement root, BamlElement elem) {
if (attrInfo.Item1 is EventDef) {
MethodDef method = root.Type.FindMethod(propRec.Value);
if (method == null)
- context.Logger.WarnFormat("Cannot resolve method '{0}' in '{1}'.", root.Type.FullName, propRec.Value);
+ context.Logger.LogWarning("Cannot resolve method '{0}' in '{1}'.", root.Type.FullName, propRec.Value);
else {
var reference = new BAMLAttributeReference(method, propRec);
service.AddReference(method, reference);
@@ -472,7 +473,7 @@ void ProcessConverter(PropertyWithConverterRecord rec, TypeDef type) {
AddDefReference(field, reference);
}
if (property == null && field == null)
- context.Logger.WarnFormat("Could not resolve command '{0}' in '{1}'.", cmd, CurrentBAMLName);
+ context.Logger.LogWarning("Could not resolve command '{0}' in '{1}'.", cmd, CurrentBAMLName);
}
}
}
@@ -540,7 +541,7 @@ void ProcessConverter(PropertyWithConverterRecord rec, TypeDef type) {
src = match.Groups[2].Value;
}
else if (rec.Value.Contains("/"))
- context.Logger.WarnFormat("Fail to extract XAML name from '{0}'.", rec.Value);
+ context.Logger.LogWarning("Fail to extract XAML name from '{0}'.", rec.Value);
if (!src.StartsWith(packScheme, StringComparison.OrdinalIgnoreCase)) {
var rel = new Uri(new Uri(packScheme + "application:,,,/" + CurrentBAMLName), src);
diff --git a/Confuser.Renamer/RenamePhase.cs b/Confuser.Renamer/RenamePhase.cs
index a73630deb..2302ddd74 100644
--- a/Confuser.Renamer/RenamePhase.cs
+++ b/Confuser.Renamer/RenamePhase.cs
@@ -5,6 +5,7 @@
using Confuser.Core;
using dnlib.DotNet;
using dnlib.DotNet.Pdb;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer {
class RenamePhase : ProtectionPhase {
@@ -22,7 +23,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
var service = (NameService)context.Registry.GetService();
bool overloadConfusion = parameters.GetParameter(context, context.CurrentModule, "overload", false);
- context.Logger.Debug("Renaming...");
+ context.Logger.LogDebug("Renaming...");
foreach (var renamer in service.Renamers) {
foreach (var def in parameters.Targets)
renamer.PreRename(context, service, parameters, def);
@@ -32,7 +33,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
var targets = parameters.Targets.ToList();
service.GetRandom().Shuffle(targets);
var pdbDocs = new HashSet();
- foreach (var def in GetTargetsWithDelay(targets, context, service).WithProgress(targets.Count, context.Logger)) {
+ foreach (var def in GetTargetsWithDelay(targets, context, service).WithProgress(targets.Count, context.ProgressReporter)) {
if (def is ModuleDef moduleDef && parameters.GetParameter(context, moduleDef, "rickroll", false))
RickRoller.CommenceRickroll(context, moduleDef);
@@ -115,7 +116,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa
foreach (var reference in updatedReferenceList) {
errorBuilder.Append(" - ").AppendLine(reference.ToString(service));
}
- context.Logger.Error(errorBuilder.ToString().Trim());
+ context.Logger.LogError(errorBuilder.ToString().Trim());
throw new ConfuserException();
}
context.CheckCancellation();
@@ -189,7 +190,7 @@ static IEnumerable GetTargetsWithDelay(IList definitions,
foreach (var def in delayedItems) {
errorBuilder.Append("• ").AppendDescription(def, service).AppendLine();
}
- context.Logger.Warn(errorBuilder.ToString().Trim());
+ context.Logger.LogWarning(errorBuilder.ToString().Trim());
yield break;
}
lastCount = delayedItems.Count;
diff --git a/Confuser.Renamer/VTable.cs b/Confuser.Renamer/VTable.cs
index cca4effa4..7a62802c7 100644
--- a/Confuser.Renamer/VTable.cs
+++ b/Confuser.Renamer/VTable.cs
@@ -4,7 +4,7 @@
using System.Linq;
using Confuser.Core;
using dnlib.DotNet;
-using ILogger = Confuser.Core.ILogger;
+using Microsoft.Extensions.Logging;
namespace Confuser.Renamer {
public class VTableSignature {
@@ -314,31 +314,31 @@ static void Inherits(VTableConstruction vTbl, VTable baseVTbl) {
[Conditional("DEBUG")]
static void CheckKeyExist(VTableStorage storage, IDictionary dictionary, TKey key, string name) {
if (!dictionary.ContainsKey(key)) {
- storage.GetLogger().ErrorFormat("{0} not found: {1}", name, key);
+ storage.GetLogger().LogError("{0} not found: {1}", name, key);
foreach (var k in dictionary.Values)
- storage.GetLogger().ErrorFormat(" {0}", k);
+ storage.GetLogger().LogError(" {0}", k);
}
}
[Conditional("DEBUG")]
static void CheckKeyExist(VTableStorage storage, ILookup lookup, TKey key, string name) {
if (!lookup.Contains(key)) {
- storage.GetLogger().ErrorFormat("{0} not found: {1}", name, key);
+ storage.GetLogger().LogError("{0} not found: {1}", name, key);
foreach (var k in lookup.Select(g => g.Key))
- storage.GetLogger().ErrorFormat(" {0}", k);
+ storage.GetLogger().LogError(" {0}", k);
}
}
}
public class VTableStorage {
Dictionary storage = new Dictionary();
- ILogger logger;
+ Microsoft.Extensions.Logging.ILogger logger;
- public VTableStorage(ILogger logger) {
+ public VTableStorage(Microsoft.Extensions.Logging.ILogger logger) {
this.logger = logger;
}
- public ILogger GetLogger() {
+ public Microsoft.Extensions.Logging.ILogger GetLogger() {
return logger;
}
diff --git a/ConfuserEx/ConfuserEx.csproj b/ConfuserEx/ConfuserEx.csproj
index 5f11173c4..09231a04e 100644
--- a/ConfuserEx/ConfuserEx.csproj
+++ b/ConfuserEx/ConfuserEx.csproj
@@ -19,6 +19,8 @@
+
+
diff --git a/ConfuserEx/FlowDocumentSink.cs b/ConfuserEx/FlowDocumentSink.cs
new file mode 100644
index 000000000..46a5dc70d
--- /dev/null
+++ b/ConfuserEx/FlowDocumentSink.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace ConfuserEx {
+ ///
+ /// A Serilog sink that writes log events to a WPF
+ /// with color-coded output matching the original ConfuserEx console style.
+ ///
+ internal sealed class FlowDocumentSink : ILogEventSink {
+ readonly Paragraph paragraph;
+
+ public FlowDocumentSink(Paragraph paragraph) {
+ this.paragraph = paragraph ?? throw new ArgumentNullException(nameof(paragraph));
+ }
+
+ public void Emit(LogEvent logEvent) {
+ var brush = GetBrush(logEvent.Level);
+ var prefix = GetPrefix(logEvent.Level);
+ var message = logEvent.RenderMessage();
+
+ Application.Current.Dispatcher.BeginInvoke(new Action(() => {
+ paragraph.Inlines.Add(new Run(prefix + message) { Foreground = brush });
+ paragraph.Inlines.Add(new LineBreak());
+
+ if (logEvent.Exception != null) {
+ paragraph.Inlines.Add(new Run("Exception: " + logEvent.Exception) { Foreground = brush });
+ paragraph.Inlines.Add(new LineBreak());
+ }
+ }));
+ }
+
+ static Brush GetBrush(LogEventLevel level) {
+ switch (level) {
+ case LogEventLevel.Verbose:
+ case LogEventLevel.Debug:
+ return Brushes.Gray;
+ case LogEventLevel.Information:
+ return Brushes.White;
+ case LogEventLevel.Warning:
+ return Brushes.Yellow;
+ case LogEventLevel.Error:
+ case LogEventLevel.Fatal:
+ return Brushes.Red;
+ default:
+ return Brushes.White;
+ }
+ }
+
+ static string GetPrefix(LogEventLevel level) {
+ switch (level) {
+ case LogEventLevel.Verbose:
+ case LogEventLevel.Debug:
+ return "[DEBUG] ";
+ case LogEventLevel.Information:
+ return " [INFO] ";
+ case LogEventLevel.Warning:
+ return " [WARN] ";
+ case LogEventLevel.Error:
+ case LogEventLevel.Fatal:
+ return "[ERROR] ";
+ default:
+ return "";
+ }
+ }
+ }
+}
diff --git a/ConfuserEx/ViewModel/UI/ProtectTabVM.cs b/ConfuserEx/ViewModel/UI/ProtectTabVM.cs
index a9f7088cc..3f7936c41 100644
--- a/ConfuserEx/ViewModel/UI/ProtectTabVM.cs
+++ b/ConfuserEx/ViewModel/UI/ProtectTabVM.cs
@@ -8,9 +8,11 @@
using CommunityToolkit.Mvvm.Input;
using Confuser.Core;
using Confuser.Core.Project;
+using Microsoft.Extensions.Logging;
+using Serilog;
namespace ConfuserEx.ViewModel {
- internal class ProtectTabVM : TabViewModel, ILogger {
+ internal class ProtectTabVM : TabViewModel, IProgressReporter {
readonly Paragraph documentContent;
CancellationTokenSource cancelSrc;
double? progress = 0;
@@ -48,9 +50,24 @@ void DoProtect() {
parameters.Project = ((IViewModel)App.Project).Model;
if (File.Exists(App.FileName))
Environment.CurrentDirectory = Path.GetDirectoryName(App.FileName);
- parameters.Logger = this;
documentContent.Inlines.Clear();
+
+ var serilogLogger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Sink(new FlowDocumentSink(documentContent))
+ .CreateLogger();
+
+ // The logger factory must outlive the async protection run — ConfuserEngine.Run
+ // executes on a background thread, so we dispose it in the continuation below
+ // rather than with a method-scoped 'using' (which would dispose it too early).
+ var loggerFactory = LoggerFactory.Create(builder =>
+ builder.AddSerilog(serilogLogger, dispose: true));
+ var melLogger = loggerFactory.CreateLogger("ConfuserEx");
+
+ parameters.Logger = melLogger;
+ parameters.ProgressReporter = this;
+
cancelSrc = new CancellationTokenSource();
Result = null;
Progress = null;
@@ -58,90 +75,50 @@ void DoProtect() {
App.NavigationDisabled = true;
ConfuserEngine.Run(parameters, cancelSrc.Token)
- .ContinueWith(_ =>
- Application.Current.Dispatcher.BeginInvoke(new Action(() => {
- Progress = 0;
- App.NavigationDisabled = false;
- CommandManager.InvalidateRequerySuggested();
- })));
+ .ContinueWith(_ => {
+ loggerFactory.Dispose();
+ Application.Current.Dispatcher.BeginInvoke(new Action(() => {
+ Progress = 0;
+ App.NavigationDisabled = false;
+ CommandManager.InvalidateRequerySuggested();
+ }));
+ });
}
void DoCancel() {
cancelSrc.Cancel();
}
- void AppendLine(string format, Brush foreground, params object[] args) {
- Application.Current.Dispatcher.BeginInvoke(new Action(() => {
- documentContent.Inlines.Add(new Run(string.Format(format, args)) { Foreground = foreground });
- documentContent.Inlines.Add(new LineBreak());
- }));
- }
-
- #region Logger Impl
+ #region IProgressReporter
DateTime begin;
- void ILogger.Debug(string msg) {
- AppendLine("[DEBUG] {0}", Brushes.Gray, msg);
- }
-
- void ILogger.DebugFormat(string format, params object[] args) {
- AppendLine("[DEBUG] {0}", Brushes.Gray, string.Format(format, args));
- }
-
- void ILogger.Info(string msg) {
- AppendLine(" [INFO] {0}", Brushes.White, msg);
- }
-
- void ILogger.InfoFormat(string format, params object[] args) {
- AppendLine(" [INFO] {0}", Brushes.White, string.Format(format, args));
- }
-
- void ILogger.Warn(string msg) {
- AppendLine(" [WARN] {0}", Brushes.Yellow, msg);
- }
-
- void ILogger.WarnFormat(string format, params object[] args) {
- AppendLine(" [WARN] {0}", Brushes.Yellow, string.Format(format, args));
- }
-
- void ILogger.WarnException(string msg, Exception ex) {
- AppendLine(" [WARN] {0}", Brushes.Yellow, msg);
- AppendLine("Exception: {0}", Brushes.Yellow, ex);
- }
-
- void ILogger.Error(string msg) {
- AppendLine("[ERROR] {0}", Brushes.Red, msg);
- }
-
- void ILogger.ErrorFormat(string format, params object[] args) {
- AppendLine("[ERROR] {0}", Brushes.Red, string.Format(format, args));
- }
-
- void ILogger.ErrorException(string msg, Exception ex) {
- AppendLine("[ERROR] {0}", Brushes.Red, msg);
- AppendLine("Exception: {0}", Brushes.Red, ex);
- }
-
- void ILogger.Progress(int progress, int overall) {
+ void IProgressReporter.Progress(int progress, int overall) {
Progress = (double)progress / overall;
}
- void ILogger.EndProgress() {
+ void IProgressReporter.EndProgress() {
Progress = null;
}
- void ILogger.Finish(bool successful) {
+ void IProgressReporter.Finish(bool successful) {
DateTime now = DateTime.Now;
string timeString = string.Format(
"at {0}, {1}:{2:d2} elapsed.",
now.ToShortTimeString(),
(int)now.Subtract(begin).TotalMinutes,
now.Subtract(begin).Seconds);
- if (successful)
- AppendLine("Finished {0}", Brushes.Lime, timeString);
- else
- AppendLine("Failed {0}", Brushes.Red, timeString);
+
+ Application.Current.Dispatcher.BeginInvoke(new Action(() => {
+ if (successful) {
+ documentContent.Inlines.Add(new Run("Finished " + timeString) { Foreground = Brushes.Lime });
+ }
+ else {
+ documentContent.Inlines.Add(new Run("Failed " + timeString) { Foreground = Brushes.Red });
+ }
+ documentContent.Inlines.Add(new LineBreak());
+ }));
+
Result = successful;
}
diff --git a/Tests/Confuser.GUI.Test/GuiSmokeTest.cs b/Tests/Confuser.GUI.Test/GuiSmokeTest.cs
index ac6f04a64..50e3e85e1 100644
--- a/Tests/Confuser.GUI.Test/GuiSmokeTest.cs
+++ b/Tests/Confuser.GUI.Test/GuiSmokeTest.cs
@@ -150,15 +150,28 @@ public void Gui_ProtectSampleApp_ShowsSuccess() {
LaunchGui($"\"{crprojPath}\"");
var mainWindow = WaitForMainWindow(app);
- // Navigate to the Protect! tab
+ // Navigate to the Protect! tab. Match by TabItem control type + name —
+ // NOT ByText, which ambiguously matches both the tab header and the
+ // Protect! button (they share the caption "Protect!").
var protectTab = Retry.WhileNull(
- () => mainWindow.FindFirstDescendant(cf => cf.ByText("Protect!")),
- TimeSpan.FromSeconds(5),
- TimeSpan.FromMilliseconds(500)).Result;
+ () => mainWindow.FindFirstDescendant(cf =>
+ cf.ByControlType(FlaUI.Core.Definitions.ControlType.TabItem)
+ .And(cf.ByName("Protect!"))),
+ TimeSpan.FromSeconds(10),
+ TimeSpan.FromMilliseconds(300)).Result;
Assert.NotNull(protectTab);
- protectTab.Click();
- // Find and click the Protect! button
+ // Select the tab and wait until it is actually selected. WPF virtualizes
+ // inactive tab content, so the Protect! button does not enter the UIA tree
+ // until the tab is selected and its content has rendered.
+ var tabItem = protectTab.AsTabItem();
+ tabItem.Select();
+ Retry.WhileFalse(
+ () => tabItem.IsSelected,
+ TimeSpan.FromSeconds(5),
+ TimeSpan.FromMilliseconds(200));
+
+ // Find and click the Protect! button (only present once the tab is rendered).
var protectButton = Retry.WhileNull(
() => {
var buttons = mainWindow.FindAllDescendants(cf => cf.ByControlType(FlaUI.Core.Definitions.ControlType.Button));
@@ -167,8 +180,8 @@ public void Gui_ProtectSampleApp_ShowsSuccess() {
}
return null;
},
- TimeSpan.FromSeconds(5),
- TimeSpan.FromMilliseconds(500)).Result;
+ TimeSpan.FromSeconds(15),
+ TimeSpan.FromMilliseconds(300)).Result;
Assert.NotNull(protectButton);
output.WriteLine("Clicking Protect! button...");
diff --git a/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs b/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs
index fe2ff1be0..c1ff62411 100644
--- a/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs
+++ b/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs
@@ -6,10 +6,10 @@
using Confuser.Renamer.Analyzers;
using Confuser.UnitTest;
using dnlib.DotNet;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using Xunit.Abstractions;
-using ILogger = Confuser.Core.ILogger;
namespace Confuser.Renamer.Test.Analyzers {
public sealed class ReflectionAnalyzerTest {
@@ -21,7 +21,7 @@ public sealed class ReflectionAnalyzerTest {
public ReflectionAnalyzerTest(ITestOutputHelper outputHelper) =>
_outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
- private ILogger CreateLogger() => new XunitLogger(_outputHelper);
+ private Microsoft.Extensions.Logging.ILogger CreateLogger() => new XunitLogger(_outputHelper);
private string ReferenceProperty { get; }
diff --git a/Tests/Confuser.UnitTest/TestBase.cs b/Tests/Confuser.UnitTest/TestBase.cs
index c23372ebc..006b8ae64 100644
--- a/Tests/Confuser.UnitTest/TestBase.cs
+++ b/Tests/Confuser.UnitTest/TestBase.cs
@@ -80,9 +80,11 @@ protected async Task Run(string[] inputFileNames, string[] expectedOutput, IEnum
if (rule.Count > 0)
proj.Rules.Add(rule);
+ var xunitLogger = new XunitLogger(outputHelper, outputAction);
var parameters = new ConfuserParameters {
Project = proj,
- Logger = new XunitLogger(outputHelper, outputAction)
+ Logger = xunitLogger,
+ ProgressReporter = xunitLogger
};
await ConfuserEngine.Run(parameters);
diff --git a/Tests/Confuser.UnitTest/XUnitLogger.cs b/Tests/Confuser.UnitTest/XUnitLogger.cs
index aa627b036..a7d8cddd3 100644
--- a/Tests/Confuser.UnitTest/XUnitLogger.cs
+++ b/Tests/Confuser.UnitTest/XUnitLogger.cs
@@ -1,9 +1,10 @@
-using System;
+using System;
using Confuser.Core;
+using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
namespace Confuser.UnitTest {
- public sealed class XunitLogger : ILogger {
+ public sealed class XunitLogger : Microsoft.Extensions.Logging.ILogger, IProgressReporter {
private readonly ITestOutputHelper _outputHelper;
private readonly Action _outputAction;
@@ -14,45 +15,25 @@ public XunitLogger(ITestOutputHelper outputHelper, Action outputAction)
_outputAction = outputAction;
}
- void ILogger.Debug(string msg) =>
- ProcessOutput("[DEBUG] " + msg);
+ public IDisposable BeginScope(TState state) => null;
- void ILogger.DebugFormat(string format, params object[] args) =>
- ProcessOutput("[DEBUG] " + format, args);
+ public bool IsEnabled(LogLevel logLevel) => true;
- void ILogger.EndProgress() { }
+ public void Log(LogLevel logLevel, EventId eventId, TState state,
+ Exception exception, Func formatter) {
+ var message = formatter(state, exception);
- void ILogger.Error(string msg) =>
- throw new Exception(msg);
+ if (logLevel >= LogLevel.Error)
+ throw new Exception(message, exception);
- void ILogger.ErrorException(string msg, Exception ex) =>
- throw new Exception(msg, ex);
-
- void ILogger.ErrorFormat(string format, params object[] args) =>
- throw new Exception(string.Format(format, args));
-
- void ILogger.Finish(bool successful) =>
- ProcessOutput("[DONE]");
-
- void ILogger.Info(string msg) =>
- ProcessOutput("[INFO] " + msg);
-
- void ILogger.InfoFormat(string format, params object[] args) =>
- ProcessOutput("[INFO] " + format, args);
-
- void ILogger.Progress(int progress, int overall) { }
-
- void ILogger.Warn(string msg) =>
- ProcessOutput("[WARN] " + msg);
+ ProcessOutput(message);
+ }
- void ILogger.WarnException(string msg, Exception ex) =>
- ProcessOutput("[WARN] " + msg + Environment.NewLine + ex.ToString());
+ void IProgressReporter.Progress(int progress, int overall) { }
- void ILogger.WarnFormat(string format, params object[] args) =>
- ProcessOutput("[WARN] " + format, args);
+ void IProgressReporter.EndProgress() { }
- private void ProcessOutput(string format, params object[] args) =>
- ProcessOutput(string.Format(format, args));
+ void IProgressReporter.Finish(bool successful) => ProcessOutput("[DONE]");
private void ProcessOutput(string message) {
_outputAction?.Invoke(message);
diff --git a/scripts/local-ci.sh b/scripts/local-ci.sh
new file mode 100644
index 000000000..cde77b79c
--- /dev/null
+++ b/scripts/local-ci.sh
@@ -0,0 +1,361 @@
+#!/usr/bin/env bash
+# =============================================================================
+# local-ci.sh — Run the full CI pipeline locally (mirrors GitHub Actions)
+#
+# Replicates: lint.yml, ci.yml (build + package), test.yml (test + coverage)
+#
+# Usage:
+# ./scripts/local-ci.sh # run everything
+# ./scripts/local-ci.sh lint # lint only
+# ./scripts/local-ci.sh build # restore + build only
+# ./scripts/local-ci.sh test # build + test only
+# ./scripts/local-ci.sh package # build + package only
+# ./scripts/local-ci.sh all # everything (default)
+# =============================================================================
+
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$REPO_ROOT"
+
+CONFIGURATION="Release"
+SLN="Confuser2.sln"
+RESULTS_DIR="test-results"
+COVERAGE_DIR="coverage"
+
+# ---------------------------------------------------------------------------
+# Colors
+# ---------------------------------------------------------------------------
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+step() { echo -e "\n${CYAN}${BOLD}==> $1${NC}"; }
+success() { echo -e "${GREEN}✓ $1${NC}"; }
+warn() { echo -e "${YELLOW}⚠ $1${NC}"; }
+fail() { echo -e "${RED}✗ $1${NC}"; }
+
+# ---------------------------------------------------------------------------
+# Find MSBuild via vswhere (CI uses microsoft/setup-msbuild@v2)
+# ---------------------------------------------------------------------------
+find_msbuild() {
+ # vswhere -latest finds the newest VS (2025 > 2022 > 2019).
+ # CI uses windows-2025 runners with VS 2025 / MSBuild 18.
+ local vswhere="/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
+ if [ -f "$vswhere" ]; then
+ # Get installation path first, then construct MSBuild path
+ local vs_path
+ vs_path=$("$vswhere" -latest -requires Microsoft.Component.MSBuild \
+ -property installationPath 2>/dev/null | head -1)
+ if [ -n "$vs_path" ]; then
+ # Convert Windows path to unix-style for bash
+ local unix_path
+ unix_path=$(cygpath -u "$vs_path" 2>/dev/null || echo "$vs_path")
+ MSBUILD="$unix_path/MSBuild/Current/Bin/MSBuild.exe"
+ if [ ! -f "$MSBUILD" ]; then
+ MSBUILD=""
+ fi
+ fi
+ fi
+
+ if [ -z "${MSBUILD:-}" ]; then
+ warn "MSBuild not found — will use 'dotnet build' (C++/CLI project will be skipped)"
+ return 1
+ fi
+
+ local ver
+ ver=$("$MSBUILD" -version 2>/dev/null | tail -1 || echo "unknown")
+ echo " MSBuild: $MSBUILD (v$ver)"
+ return 0
+}
+
+# ---------------------------------------------------------------------------
+# Phase: Lint (mirrors lint.yml)
+# ---------------------------------------------------------------------------
+do_lint() {
+ step "LINT — whitespace, style, analyzers"
+
+ local any_failed=false
+
+ echo " Restoring for lint..."
+ dotnet restore "$SLN" --verbosity quiet 2>/dev/null
+
+ echo " Checking whitespace..."
+ if dotnet format whitespace "$SLN" --verify-no-changes --verbosity minimal 2>&1 | tail -5; then
+ success "Whitespace OK"
+ else
+ warn "Whitespace issues found"
+ any_failed=true
+ fi
+
+ echo " Checking style..."
+ if dotnet format style "$SLN" --verify-no-changes --severity warn 2>&1 | tail -5; then
+ success "Style OK"
+ else
+ warn "Style issues found"
+ any_failed=true
+ fi
+
+ echo " Checking analyzers..."
+ if dotnet format analyzers "$SLN" --verify-no-changes --severity warn 2>&1 | tail -5; then
+ success "Analyzers OK"
+ else
+ warn "Analyzer issues found"
+ any_failed=true
+ fi
+
+ if [ "$any_failed" = true ]; then
+ warn "Lint issues found — run 'dotnet format $SLN' to fix"
+ return 1
+ else
+ success "All lint checks passed"
+ fi
+}
+
+# ---------------------------------------------------------------------------
+# Phase: Restore + Build (mirrors ci.yml)
+# ---------------------------------------------------------------------------
+do_build() {
+ step "BUILD — restore + compile ($CONFIGURATION)"
+
+ local has_msbuild=false
+ find_msbuild && has_msbuild=true
+
+ # Use 'dotnet build' for all SDK-style projects (handles .NET SDK resolution).
+ # Then use MSBuild.exe for the C++/CLI project if available.
+ echo " Restoring..."
+ dotnet restore "$SLN" --verbosity minimal
+
+ echo " Building (dotnet)..."
+ dotnet build "$SLN" -c "$CONFIGURATION" --no-restore 2>&1 \
+ | grep -v "244_ClrProtection" || true
+
+ # C++/CLI project requires MSBuild.exe (not dotnet build)
+ if [ "$has_msbuild" = true ]; then
+ local vcxproj="Tests/244_ClrProtection/244_ClrProtection.vcxproj"
+ if [ -f "$vcxproj" ]; then
+ echo " Building C++/CLI test project (msbuild)..."
+ "$MSBUILD" "$vcxproj" -p:Configuration="$CONFIGURATION" -verbosity:minimal 2>&1 \
+ | tail -3 || warn "C++/CLI project build failed (non-critical)"
+ fi
+ else
+ warn "Skipping C++/CLI project (no MSBuild.exe found)"
+ fi
+
+ # Verify key outputs exist
+ local cli_dll="Confuser.CLI/bin/$CONFIGURATION/net10.0/Confuser.CLI.dll"
+ local gui_dll="ConfuserEx/bin/$CONFIGURATION/net10.0-windows/ConfuserEx.dll"
+ local core48="Confuser.Core/bin/$CONFIGURATION/net48/Confuser.Core.dll"
+ local corenstd="Confuser.Core/bin/$CONFIGURATION/netstandard2.0/Confuser.Core.dll"
+
+ local all_ok=true
+ for f in "$cli_dll" "$gui_dll" "$core48" "$corenstd"; do
+ if [ -f "$f" ]; then
+ success "$(basename "$f") ($(dirname "$f" | sed "s|.*/bin/$CONFIGURATION/||"))"
+ else
+ fail "Missing: $f"
+ all_ok=false
+ fi
+ done
+
+ if [ "$all_ok" = false ]; then
+ fail "Build produced missing outputs"
+ return 1
+ fi
+
+ success "Build complete"
+}
+
+# ---------------------------------------------------------------------------
+# Phase: Test (mirrors test.yml)
+# ---------------------------------------------------------------------------
+do_test() {
+ step "TEST — run all test projects with coverage"
+
+ rm -rf "$RESULTS_DIR" 2>/dev/null || true
+ mkdir -p "$RESULTS_DIR"
+
+ local total_passed=0
+ local total_failed=0
+ local total_skipped=0
+ local any_failed=false
+
+ # Find all *.Test.csproj (same as CI: Get-ChildItem -Filter '*.Test.csproj' -Recurse)
+ while IFS= read -r proj; do
+ local name
+ name=$(basename "$proj" .csproj)
+ echo -e "\n ${CYAN}Testing $name...${NC}"
+
+ local output
+ output=$(dotnet test "$proj" -c "$CONFIGURATION" --no-build --verbosity minimal \
+ --collect:"XPlat Code Coverage" \
+ --logger "trx;LogFileName=$name.trx" \
+ --results-directory "$RESULTS_DIR/$name" 2>&1) || true
+
+ # Parse summary line: "Passed! - Failed: 0, Passed: 3, Skipped: 0, Total: 3"
+ local summary
+ summary=$(echo "$output" | grep -E "^(Passed!|Failed!)" | tail -1)
+
+ if [ -n "$summary" ]; then
+ local p f s
+ p=$(echo "$summary" | grep -oP 'Passed:\s+\K\d+' || echo 0)
+ f=$(echo "$summary" | grep -oP 'Failed:\s+\K\d+' || echo 0)
+ s=$(echo "$summary" | grep -oP 'Skipped:\s+\K\d+' || echo 0)
+ total_passed=$((total_passed + p))
+ total_failed=$((total_failed + f))
+ total_skipped=$((total_skipped + s))
+
+ if echo "$summary" | grep -q "^Failed!"; then
+ fail "$name — $summary"
+ any_failed=true
+ else
+ success "$name — Passed: $p, Failed: $f, Skipped: $s"
+ fi
+ else
+ # No tests discovered
+ local no_test
+ no_test=$(echo "$output" | grep -c "No test is available" || true)
+ if [ "$no_test" -gt 0 ]; then
+ warn "$name — no tests discovered (missing Microsoft.NET.Test.Sdk?)"
+ else
+ warn "$name — no test output"
+ fi
+ fi
+ done < <(find Tests -name "*.Test.csproj" -type f | sort)
+
+ echo ""
+ echo " ─────────────────────────────────────"
+ echo -e " ${BOLD}Total: Passed=$total_passed Failed=$total_failed Skipped=$total_skipped${NC}"
+ echo " ─────────────────────────────────────"
+
+ # Generate coverage report if reportgenerator is available
+ local reports
+ reports=$(find "$RESULTS_DIR" -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';')
+ if [ -n "$reports" ] && command -v reportgenerator &>/dev/null; then
+ step "COVERAGE — generating report"
+ mkdir -p "$COVERAGE_DIR/report"
+ reportgenerator "-reports:$reports" \
+ "-targetdir:$COVERAGE_DIR/report" \
+ "-reporttypes:TextSummary" 2>/dev/null
+ cat "$COVERAGE_DIR/report/Summary.txt" 2>/dev/null || true
+ elif [ -n "$reports" ]; then
+ warn "Install reportgenerator for coverage reports: dotnet tool install -g dotnet-reportgenerator-globaltool"
+ fi
+
+ if [ "$any_failed" = true ]; then
+ fail "Some tests failed"
+ return 1
+ fi
+
+ success "All tests passed"
+}
+
+# ---------------------------------------------------------------------------
+# Phase: Package (mirrors ci.yml packaging steps)
+# ---------------------------------------------------------------------------
+do_package() {
+ step "PACKAGE — create release archives"
+
+ local cli_dir="Confuser.CLI/bin/$CONFIGURATION/net10.0"
+ local gui_dir="ConfuserEx/bin/$CONFIGURATION/net10.0-windows"
+
+ # CLI zip
+ if [ -d "$cli_dir" ]; then
+ rm -f ConfuserEx-CLI.zip 2>/dev/null || true
+ (cd "$cli_dir" && find . -not -name '*.pdb' -not -name '*.xml' -not -path './runtimes/*/native/*' \
+ -type f | sort | zip -q "$REPO_ROOT/ConfuserEx-CLI.zip" -@)
+ local size
+ size=$(du -h ConfuserEx-CLI.zip | cut -f1)
+ success "ConfuserEx-CLI.zip ($size)"
+ else
+ fail "CLI output not found at $cli_dir — run build first"
+ fi
+
+ # GUI zip
+ if [ -d "$gui_dir" ]; then
+ rm -f ConfuserEx-GUI.zip 2>/dev/null || true
+ (cd "$gui_dir" && find . -not -name '*.pdb' -not -name '*.xml' \
+ -type f | sort | zip -q "$REPO_ROOT/ConfuserEx-GUI.zip" -@)
+ size=$(du -h ConfuserEx-GUI.zip | cut -f1)
+ success "ConfuserEx-GUI.zip ($size)"
+ else
+ fail "GUI output not found at $gui_dir — run build first"
+ fi
+
+ # Combined zip
+ rm -rf combined 2>/dev/null || true
+ mkdir -p combined
+ cp "$cli_dir"/* combined/ 2>/dev/null || true
+ cp "$gui_dir"/* combined/ 2>/dev/null || true
+ rm -f combined/*.pdb combined/*.xml 2>/dev/null || true
+ if [ "$(ls -A combined 2>/dev/null)" ]; then
+ rm -f ConfuserEx.zip 2>/dev/null || true
+ (cd combined && find . -type f | sort | zip -q "$REPO_ROOT/ConfuserEx.zip" -@)
+ size=$(du -h ConfuserEx.zip | cut -f1)
+ success "ConfuserEx.zip ($size)"
+ fi
+ rm -rf combined
+
+ # NuGet package
+ local nupkg
+ nupkg=$(find Confuser.MSBuild.Tasks/bin/$CONFIGURATION -name "*.nupkg" 2>/dev/null | head -1)
+ if [ -n "$nupkg" ]; then
+ success "$(basename "$nupkg")"
+ else
+ warn "No .nupkg found (MSBuild-only build may be required)"
+ fi
+
+ success "Packaging complete"
+}
+
+# ---------------------------------------------------------------------------
+# Main
+# ---------------------------------------------------------------------------
+MODE="${1:-all}"
+
+echo -e "${BOLD}╔══════════════════════════════════════╗${NC}"
+echo -e "${BOLD}║ ConfuserEx Local CI Pipeline ║${NC}"
+echo -e "${BOLD}╚══════════════════════════════════════╝${NC}"
+echo " Mode: $MODE"
+echo " Config: $CONFIGURATION"
+echo " Root: $REPO_ROOT"
+
+ERRORS=0
+
+case "$MODE" in
+ lint)
+ do_lint || ERRORS=$((ERRORS + 1))
+ ;;
+ build)
+ do_build || ERRORS=$((ERRORS + 1))
+ ;;
+ test)
+ do_build || ERRORS=$((ERRORS + 1))
+ do_test || ERRORS=$((ERRORS + 1))
+ ;;
+ package)
+ do_build || ERRORS=$((ERRORS + 1))
+ do_package || ERRORS=$((ERRORS + 1))
+ ;;
+ all)
+ do_lint || ERRORS=$((ERRORS + 1))
+ do_build || ERRORS=$((ERRORS + 1))
+ do_test || ERRORS=$((ERRORS + 1))
+ do_package || ERRORS=$((ERRORS + 1))
+ ;;
+ *)
+ echo "Usage: $0 [lint|build|test|package|all]"
+ exit 1
+ ;;
+esac
+
+echo ""
+if [ "$ERRORS" -gt 0 ]; then
+ fail "Pipeline finished with $ERRORS failed phase(s)"
+ exit 1
+else
+ success "Pipeline complete — all phases passed"
+fi