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