diff --git a/.gitignore b/.gitignore index c65f226a6..840641ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ obj/ _ReSharper*/ [Tt]est[Rr]esult* -/docs !packages/*/build/ packages/ /Confuser.Test diff --git a/Confuser.CLI/Program.cs b/Confuser.CLI/Program.cs index c20818875..0e258f672 100644 --- a/Confuser.CLI/Program.cs +++ b/Confuser.CLI/Program.cs @@ -128,9 +128,9 @@ public void ErrorException(string msg, Exception ex) { WriteLineWithColor(ConsoleColor.Red, "Exception: " + ex); } - public void Progress(int overall, int progress) { - WriteLineWithColor(ConsoleColor.Gray, string.Format("{0}/{1}", progress, overall)); - } + public void Progress(int progress, int overall) { } + + public void EndProgress() { } public void Finish(bool successful) { DateTime now = DateTime.Now; diff --git a/Confuser.Core/ConfuserEngine.cs b/Confuser.Core/ConfuserEngine.cs index ad1bdc662..e5c31ebb1 100644 --- a/Confuser.Core/ConfuserEngine.cs +++ b/Confuser.Core/ConfuserEngine.cs @@ -67,20 +67,21 @@ private static void RunInternal(ConfuserParameters parameters, CancellationToken context.PackerInitiated = parameters.PackerInitiated; context.token = token; - - var asmResolver = new AssemblyResolver(); - asmResolver.EnableTypeDefCache = true; - asmResolver.DefaultModuleContext = new ModuleContext(asmResolver); - context.Resolver = asmResolver; - context.BaseDirectory = Path.Combine(Environment.CurrentDirectory, parameters.Project.BaseDirectory + "\\"); - context.OutputDirectory = Path.Combine(parameters.Project.BaseDirectory, parameters.Project.OutputDirectory + "\\"); - foreach (string probePath in parameters.Project.ProbePaths) - asmResolver.PostSearchPaths.Add(Path.Combine(context.BaseDirectory, probePath)); - PrintInfo(context); bool ok = false; try { + var asmResolver = new AssemblyResolver(); + asmResolver.EnableTypeDefCache = true; + asmResolver.DefaultModuleContext = new ModuleContext(asmResolver); + context.Resolver = asmResolver; + context.BaseDirectory = Path.Combine(Environment.CurrentDirectory, parameters.Project.BaseDirectory + "\\"); + context.OutputDirectory = Path.Combine(parameters.Project.BaseDirectory, parameters.Project.OutputDirectory + "\\"); + foreach (string probePath in parameters.Project.ProbePaths) + asmResolver.PostSearchPaths.Add(Path.Combine(context.BaseDirectory, probePath)); + + context.CheckCancellation(); + Marker marker = parameters.GetMarker(); // 2. Discover plugins @@ -93,13 +94,14 @@ private static void RunInternal(ConfuserParameters parameters, CancellationToken context.Logger.InfoFormat("Discovered {0} protections, {1} packers.", prots.Count, packers.Count); + context.CheckCancellation(); + // 3. Resolve dependency context.Logger.Debug("Resolving component dependency..."); try { var resolver = new DependencyResolver(prots); prots = resolver.SortDependency(); - } - catch (CircularDependencyException ex) { + } catch (CircularDependencyException ex) { context.Logger.ErrorException("", ex); throw new ConfuserException(ex); } @@ -110,6 +112,8 @@ private static void RunInternal(ConfuserParameters parameters, CancellationToken foreach (Packer packer in packers) components.Add(packer); + context.CheckCancellation(); + // 4. Load modules context.Logger.Info("Loading input modules..."); marker.Initalize(prots, packers); @@ -122,19 +126,22 @@ private static void RunInternal(ConfuserParameters parameters, CancellationToken asmResolver.AddToCache(module); context.Packer = markings.Packer; + context.CheckCancellation(); + // 5. Initialize components context.Logger.Info("Initializing..."); foreach (ConfuserComponent comp in components) { try { comp.Initialize(context); - } - catch (Exception ex) { + } catch (Exception ex) { context.Logger.ErrorException("Error occured during initialization of '" + comp.Name + "'.", ex); throw new ConfuserException(ex); } context.CheckCancellation(); } + context.CheckCancellation(); + // 6. Build pipeline context.Logger.Debug("Building pipeline..."); var pipeline = new ProtectionPipeline(); @@ -142,35 +149,28 @@ private static void RunInternal(ConfuserParameters parameters, CancellationToken foreach (ConfuserComponent comp in components) { comp.PopulatePipeline(pipeline); } + context.CheckCancellation(); //7. Run pipeline RunPipeline(pipeline, context); ok = true; - } - catch (AssemblyResolveException ex) { + } catch (AssemblyResolveException ex) { context.Logger.ErrorException("Failed to resolve a assembly, check if all dependencies are of correct version.", ex); - } - catch (TypeResolveException ex) { + } catch (TypeResolveException ex) { context.Logger.ErrorException("Failed to resolve a type, check if all dependencies are of correct version.", ex); - } - catch (MemberRefResolveException ex) { + } catch (MemberRefResolveException ex) { context.Logger.ErrorException("Failed to resolve a member, check if all dependencies are of correct version.", ex); - } - catch (IOException ex) { + } catch (IOException ex) { context.Logger.ErrorException("An IO error occured, check if all input/output locations are read/writable.", ex); - } - catch (OperationCanceledException) { + } catch (OperationCanceledException) { context.Logger.Error("Operation is canceled."); - } - catch (ConfuserException) { + } catch (ConfuserException) { // Exception is already handled/logged, so just ignore and report failure - } - catch (Exception ex) { + } catch (Exception ex) { context.Logger.ErrorException("Unknown error occured.", ex); - } - finally { + } finally { context.Logger.Finish(ok); } } @@ -234,8 +234,7 @@ private static void Inspection(ConfuserContext context) { .SelectMany(module => module.GetAssemblyRefs().Select(asmRef => Tuple.Create(asmRef, module)))) { try { AssemblyDef assembly = context.Resolver.ResolveThrow(dependency.Item1, dependency.Item2); - } - catch (AssemblyResolveException ex) { + } catch (AssemblyResolveException ex) { context.Logger.ErrorException("Failed to resolve dependency of '" + dependency.Item2.Name + "'.", ex); throw new ConfuserException(ex); } @@ -300,6 +299,7 @@ private static void BeginModule(ConfuserContext context) { context.Logger.InfoFormat("Processing module '{0}'...", context.CurrentModule.Name); context.CurrentModuleWriterListener = new ModuleWriterListener(); + context.CurrentModuleWriterListener.OnWriterEvent += (sender, e) => context.CheckCancellation(); context.CurrentModuleWriterOptions = new ModuleWriterOptions(context.CurrentModule, context.CurrentModuleWriterListener); var snKey = context.Annotations.Get(context.CurrentModule, Marker.SNKey); context.CurrentModuleWriterOptions.InitializeStrongNameSigning(context.CurrentModule, snKey); @@ -388,8 +388,7 @@ private static void SaveModules(ConfuserContext context) { private static void PrintInfo(ConfuserContext context) { if (context.PackerInitiated) { context.Logger.Info("Protecting packer stub..."); - } - else { + } else { context.Logger.InfoFormat("{0} {1}", Version, Copyright); Type mono = Type.GetType("Mono.Runtime"); diff --git a/Confuser.Core/ILogger.cs b/Confuser.Core/ILogger.cs index e2c028e4c..171691226 100644 --- a/Confuser.Core/ILogger.cs +++ b/Confuser.Core/ILogger.cs @@ -74,9 +74,26 @@ public interface ILogger { /// /// 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 overall, int progress); + void Progress(int progress, int overall); + + /// + /// End the progress of protection. + /// + /// + void EndProgress(); /// /// Logs the finish of protection. diff --git a/Confuser.Core/Marker.cs b/Confuser.Core/Marker.cs index 2555a7790..a7083e460 100644 --- a/Confuser.Core/Marker.cs +++ b/Confuser.Core/Marker.cs @@ -110,6 +110,8 @@ protected internal virtual MarkerResult MarkProject(ConfuserProject proj, Confus context.Logger.InfoFormat("Loading '{0}'...", module.Path); ModuleDefMD modDef = module.Resolve(proj.BaseDirectory, context.Resolver.DefaultModuleContext); + context.CheckCancellation(); + if (proj.Debug) modDef.LoadPdb(); @@ -118,8 +120,10 @@ protected internal virtual MarkerResult MarkProject(ConfuserProject proj, Confus context.Annotations.Set(modDef, SNKey, LoadSNKey(context, module.SNKeyPath == null ? null : Path.Combine(proj.BaseDirectory, module.SNKeyPath), module.SNKeyPassword)); context.Annotations.Set(modDef, RulesKey, rules); - foreach (IDnlibDef def in modDef.FindDefinitions()) + foreach (IDnlibDef def in modDef.FindDefinitions()) { ApplyRules(context, def, rules); + context.CheckCancellation(); + } // Packer parameters are stored in modules if (packerParams != null) diff --git a/Confuser.Core/NullLogger.cs b/Confuser.Core/NullLogger.cs index e791cfa1a..2fd1a2f50 100644 --- a/Confuser.Core/NullLogger.cs +++ b/Confuser.Core/NullLogger.cs @@ -49,6 +49,9 @@ public void ErrorException(string msg, Exception ex) { } /// public void Progress(int overall, int progress) { } + /// + public void EndProgress() { } + /// public void Finish(bool successful) { } diff --git a/Confuser.Core/Packer.cs b/Confuser.Core/Packer.cs index 13b0b02b8..57fdb1c08 100644 --- a/Confuser.Core/Packer.cs +++ b/Confuser.Core/Packer.cs @@ -124,8 +124,12 @@ public void ErrorException(string msg, Exception ex) { baseLogger.ErrorException(msg, ex); } - public void Progress(int overall, int progress) { - baseLogger.Progress(overall, progress); + public void Progress(int progress, int overall) { + baseLogger.Progress(progress, overall); + } + + public void EndProgress() { + baseLogger.EndProgress(); } public void Finish(bool successful) { diff --git a/Confuser.Core/PluginDiscovery.cs b/Confuser.Core/PluginDiscovery.cs index cff399241..667363201 100644 --- a/Confuser.Core/PluginDiscovery.cs +++ b/Confuser.Core/PluginDiscovery.cs @@ -32,7 +32,12 @@ public void GetPlugins(ConfuserContext context, out IList protection GetPluginsInternal(context, protections, packers, components); } - private static bool HasAccessibleDefConstructor(Type type) { + /// + /// Determines whether the specified type has an accessible default constructor. + /// + /// The type. + /// true if the specified type has an accessible default constructor; otherwise, false. + public static bool HasAccessibleDefConstructor(Type type) { ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes); if (ctor == null) return false; return ctor.IsPublic; diff --git a/Confuser.Core/Project/ConfuserPrj.xsd b/Confuser.Core/Project/ConfuserPrj.xsd index ed4c2d134..0d5ba27f3 100644 --- a/Confuser.Core/Project/ConfuserPrj.xsd +++ b/Confuser.Core/Project/ConfuserPrj.xsd @@ -61,7 +61,7 @@ - + diff --git a/Confuser.Core/ProtectionPhase.cs b/Confuser.Core/ProtectionPhase.cs index 9c89e3ce0..d35848128 100644 --- a/Confuser.Core/ProtectionPhase.cs +++ b/Confuser.Core/ProtectionPhase.cs @@ -23,6 +23,12 @@ public ProtectionPhase(ConfuserComponent parent) { /// The protection targets. public abstract ProtectionTargets Targets { get; } + /// + /// Gets the name of the phase. + /// + /// The name of phase. + public abstract string Name { get; } + /// /// Gets a value indicating whether this phase process all targets, not just the targets that requires the component. /// diff --git a/Confuser.Core/ProtectionPipeline.cs b/Confuser.Core/ProtectionPipeline.cs index f77ba2ec7..e5cb78e9b 100644 --- a/Confuser.Core/ProtectionPipeline.cs +++ b/Confuser.Core/ProtectionPipeline.cs @@ -120,12 +120,15 @@ public T FindPhase() where T : ProtectionPhase { /// The working context. internal void ExecuteStage(PipelineStage stage, Action func, Func> targets, ConfuserContext context) { foreach (ProtectionPhase pre in preStage[stage]) { - pre.Execute(context, new ProtectionParameters(pre.Parent, Filter(context, targets(), pre))); context.CheckCancellation(); + context.Logger.DebugFormat("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); post.Execute(context, new ProtectionParameters(post.Parent, Filter(context, targets(), post))); context.CheckCancellation(); } diff --git a/Confuser.Core/Services/CompressionService.cs b/Confuser.Core/Services/CompressionService.cs index be853b61a..8a9340f43 100644 --- a/Confuser.Core/Services/CompressionService.cs +++ b/Confuser.Core/Services/CompressionService.cs @@ -24,42 +24,41 @@ public CompressionService(ConfuserContext context) { /// public MethodDef GetRuntimeDecompressor(ModuleDef module, Action init) { Tuple> decompressor = context.Annotations.GetOrCreate(module, Decompressor, m => { - var rt = context.Registry.GetService(); + var rt = context.Registry.GetService(); - List members = InjectHelper.Inject(rt.GetRuntimeType("Confuser.Runtime.Lzma"), module.GlobalType, module).ToList(); - MethodDef decomp = null; - foreach (IDnlibDef member in members) { - if (member is MethodDef) { - var method = (MethodDef)member; - if (method.Access == MethodAttributes.Public) - method.Access = MethodAttributes.Assembly; - if (!method.IsConstructor) - method.IsSpecialName = false; + List members = InjectHelper.Inject(rt.GetRuntimeType("Confuser.Runtime.Lzma"), module.GlobalType, module).ToList(); + MethodDef decomp = null; + foreach (IDnlibDef member in members) { + if (member is MethodDef) { + var method = (MethodDef)member; + if (method.Access == MethodAttributes.Public) + method.Access = MethodAttributes.Assembly; + if (!method.IsConstructor) + method.IsSpecialName = false; - if (method.Name == "Decompress") - decomp = method; - } - else if (member is FieldDef) { - var field = (FieldDef)member; - if (field.Access == FieldAttributes.Public) - field.Access = FieldAttributes.Assembly; - if (field.IsLiteral) { - field.DeclaringType.Fields.Remove(field); - } - } - } - members.RemoveWhere(def => def is FieldDef && ((FieldDef)def).IsLiteral); + if (method.Name == "Decompress") + decomp = method; + } else if (member is FieldDef) { + var field = (FieldDef)member; + if (field.Access == FieldAttributes.Public) + field.Access = FieldAttributes.Assembly; + if (field.IsLiteral) { + field.DeclaringType.Fields.Remove(field); + } + } + } + members.RemoveWhere(def => def is FieldDef && ((FieldDef)def).IsLiteral); - Debug.Assert(decomp != null); - return Tuple.Create(decomp, members); - }); + Debug.Assert(decomp != null); + return Tuple.Create(decomp, members); + }); foreach (IDnlibDef member in decompressor.Item2) init(member); return decompressor.Item1; } /// - public byte[] Compress(byte[] data) { + public byte[] Compress(byte[] data, Action progressFunc = null) { CoderPropID[] propIDs = { CoderPropID.DictionarySize, CoderPropID.PosStateBits, @@ -89,9 +88,29 @@ public byte[] Compress(byte[] data) { fileSize = data.Length; for (int i = 0; i < 8; i++) x.WriteByte((Byte)(fileSize >> (8 * i))); - encoder.Code(new MemoryStream(data), x, -1, -1, null); + + ICodeProgress progress = null; + if (progressFunc != null) + progress = new CompressionLogger(progressFunc, data.Length); + encoder.Code(new MemoryStream(data), x, -1, -1, progress); + return x.ToArray(); } + + private class CompressionLogger : ICodeProgress { + private readonly Action progressFunc; + private readonly int size; + + public CompressionLogger(Action progressFunc, int size) { + this.progressFunc = progressFunc; + this.size = size; + } + + public void SetProgress(long inSize, long outSize) { + double precentage = (double)inSize / size; + progressFunc(precentage); + } + } } /// @@ -110,7 +129,8 @@ public interface ICompressionService { /// Compresses the specified data. /// /// The buffer storing the data. + /// The function that receive the progress of compression. /// The compressed data. - byte[] Compress(byte[] data); + byte[] Compress(byte[] data, Action progressFunc = null); } } \ No newline at end of file diff --git a/Confuser.Core/Utils.cs b/Confuser.Core/Utils.cs index 900ea1b91..2a4fe4ec9 100644 --- a/Confuser.Core/Utils.cs +++ b/Confuser.Core/Utils.cs @@ -68,7 +68,7 @@ public static void AddListEntry(this Dictionary } /// - /// OBtains the relative path from the specified base path. + /// Obtains the relative path from the specified base path. /// /// The file path. /// The base path. @@ -181,5 +181,23 @@ public static IList RemoveWhere(this IList self, Predicate match) { } return self; } + + /// + /// Returns a that log the progress of iterating the specified list. + /// + /// The type of list element + /// The list. + /// The logger. + /// A wrapper of the list. + public static IEnumerable WithProgress(this IEnumerable enumerable, ILogger logger) { + var list = new List(enumerable); + int i; + for (i = 0; i < list.Count; i++) { + logger.Progress(i, list.Count); + yield return list[i]; + } + logger.Progress(i, list.Count); + logger.EndProgress(); + } } } \ No newline at end of file diff --git a/Confuser.Protections/AntiDebugProtection.cs b/Confuser.Protections/AntiDebugProtection.cs index a3b1c50dc..5becf455c 100644 --- a/Confuser.Protections/AntiDebugProtection.cs +++ b/Confuser.Protections/AntiDebugProtection.cs @@ -49,6 +49,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Modules; } } + public override string Name { + get { return "Anti-debug injection"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { var rt = context.Registry.GetService(); var marker = context.Registry.GetService(); diff --git a/Confuser.Protections/AntiDumpProtection.cs b/Confuser.Protections/AntiDumpProtection.cs index 9c828b0a0..eaf3710c2 100644 --- a/Confuser.Protections/AntiDumpProtection.cs +++ b/Confuser.Protections/AntiDumpProtection.cs @@ -49,6 +49,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Modules; } } + public override string Name { + get { return "Anti-dump injection"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { TypeDef rtType = context.Registry.GetService().GetRuntimeType("Confuser.Runtime.AntiDump"); diff --git a/Confuser.Protections/AntiILDasmProtection.cs b/Confuser.Protections/AntiILDasmProtection.cs index 2a9b33f31..dd270c44b 100644 --- a/Confuser.Protections/AntiILDasmProtection.cs +++ b/Confuser.Protections/AntiILDasmProtection.cs @@ -43,6 +43,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Modules; } } + public override string Name { + get { return "Anti-ILDasm marking"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { foreach (ModuleDef module in parameters.Targets.OfType()) { TypeRef attrRef = module.CorLibTypes.GetTypeRef("System.Runtime.CompilerServices", "SuppressIldasmAttribute"); diff --git a/Confuser.Protections/AntiTamper/AntiTamperProtection.cs b/Confuser.Protections/AntiTamper/AntiTamperProtection.cs index 207c01935..f731831b6 100644 --- a/Confuser.Protections/AntiTamper/AntiTamperProtection.cs +++ b/Confuser.Protections/AntiTamper/AntiTamperProtection.cs @@ -53,6 +53,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Anti-tamper helpers injection"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { if (!parameters.Targets.Any()) return; @@ -82,6 +86,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Anti-tamper metadata preparation"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { if (!parameters.Targets.Any()) return; diff --git a/Confuser.Protections/AntiTamper/JITMode.cs b/Confuser.Protections/AntiTamper/JITMode.cs index eef0c4dda..31f0ca778 100644 --- a/Confuser.Protections/AntiTamper/JITMode.cs +++ b/Confuser.Protections/AntiTamper/JITMode.cs @@ -24,6 +24,7 @@ internal class JITMode : IModeHandler { private uint c; private MethodDef cctor; private MethodDef cctorRepl; + private ConfuserContext context; private IKeyDeriver deriver; private byte[] fieldLayout; @@ -37,6 +38,7 @@ internal class JITMode : IModeHandler { private uint z; public void HandleInject(AntiTamperProtection parent, ConfuserContext context, ProtectionParameters parameters) { + this.context = context; random = context.Registry.GetService().GetRandomGenerator(parent.FullId); z = random.NextUInt32(); x = random.NextUInt32(); @@ -77,8 +79,7 @@ public void HandleInject(AntiTamperProtection parent, ConfuserContext context, P Instruction instr = instrs[i]; if (instr.OpCode == OpCodes.Ldtoken) { instr.Operand = context.CurrentModule.GlobalType; - } - else if (instr.OpCode == OpCodes.Call) { + } else if (instr.OpCode == OpCodes.Call) { var method = (IMethod)instr.Operand; if (method.DeclaringType.Name == "Mutation" && method.Name == "Crypt") { @@ -144,9 +145,10 @@ public void HandleMD(AntiTamperProtection parent, ConfuserContext context, Prote private void OnWriterEvent(object sender, ModuleWriterListenerEventArgs e) { var writer = (ModuleWriter)sender; if (e.WriterEvent == ModuleWriterEvent.MDBeginWriteMethodBodies) { + context.Logger.Debug("Extracting method bodies..."); CreateSection(writer); - } - else if (e.WriterEvent == ModuleWriterEvent.BeginStrongNameSign) { + } else if (e.WriterEvent == ModuleWriterEvent.BeginStrongNameSign) { + context.Logger.Debug("Encrypting method section..."); EncryptSection(writer); } } @@ -202,7 +204,7 @@ private void CreateSection(ModuleWriter writer) { cctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); // save methods - foreach (MethodDef method in methods) { + foreach (MethodDef method in methods.WithProgress(context.Logger)) { if (!method.HasBody) continue; @@ -216,6 +218,7 @@ private void CreateSection(ModuleWriter writer) { method.Body = NopBody; writer.MetaData.TablesHeap.MethodTable[token.Rid].ImplFlags |= (ushort)MethodImplAttributes.NoInlining; + context.CheckCancellation(); } bodyIndex.PopulateSection(newSection); @@ -242,8 +245,7 @@ private void EncryptSection(ModuleWriter writer) { if (nameHash == name1 * name2) { encSize = reader.ReadUInt32(); encLoc = reader.ReadUInt32(); - } - else if (nameHash != 0) { + } else if (nameHash != 0) { uint sectSize = reader.ReadUInt32(); uint sectLoc = reader.ReadUInt32(); Hash(stream, reader, sectLoc, sectSize); diff --git a/Confuser.Protections/Compress/Compressor.cs b/Confuser.Protections/Compress/Compressor.cs index 0b872efc8..0ac115760 100644 --- a/Confuser.Protections/Compress/Compressor.cs +++ b/Confuser.Protections/Compress/Compressor.cs @@ -50,7 +50,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 main module specified!"); + context.Logger.Error("No executable module!"); throw new ConfuserException(null); } @@ -77,6 +77,7 @@ protected override void Pack(ConfuserContext context, ProtectionParameters param stubModule.Write(ms, new ModuleWriterOptions(stubModule, new KeyInjector(ctx)) { StrongNameKey = snKey }); + context.CheckCancellation(); base.ProtectStub(context, context.OutputPaths[ctx.ModuleIndex], ms.ToArray(), snKey, new StubProtection(ctx)); } } @@ -105,6 +106,7 @@ private void PackModules(ConfuserContext context, CompressorContext compCtx, Mod key[i] |= 1; compCtx.KeySig = key; + int moduleIndex = 0; foreach (var entry in modules) { byte[] name = Encoding.UTF8.GetBytes(entry.Key); for (int i = 0; i < name.Length; i++) @@ -113,11 +115,17 @@ private void PackModules(ConfuserContext context, CompressorContext compCtx, Mod uint state = 0x6fff61; foreach (byte chr in name) state = state * 0x5e3f1f + chr; - byte[] encrypted = compCtx.Encrypt(comp, entry.Value, state); + byte[] encrypted = compCtx.Encrypt(comp, entry.Value, state, progress => { + progress = (progress + moduleIndex) / modules.Count; + context.Logger.Progress((int)(progress * 10000), 10000); + }); + context.CheckCancellation(); var resource = new EmbeddedResource(Convert.ToBase64String(name), encrypted, ManifestResourceAttributes.Private); stubModule.Resources.Add(resource); + moduleIndex++; } + context.Logger.EndProgress(); } private void InjectData(ModuleDef stubModule, MethodDef method, byte[] data) { @@ -137,14 +145,14 @@ private void InjectData(ModuleDef stubModule, MethodDef method, byte[] data) { stubModule.GlobalType.Fields.Add(dataField); MutationHelper.ReplacePlaceholder(method, arg => { - var repl = new List(); - repl.AddRange(arg); - repl.Add(Instruction.Create(OpCodes.Dup)); - repl.Add(Instruction.Create(OpCodes.Ldtoken, dataField)); - repl.Add(Instruction.Create(OpCodes.Call, stubModule.Import( - typeof (RuntimeHelpers).GetMethod("InitializeArray")))); - return repl.ToArray(); - }); + var repl = new List(); + repl.AddRange(arg); + repl.Add(Instruction.Create(OpCodes.Dup)); + repl.Add(Instruction.Create(OpCodes.Ldtoken, dataField)); + repl.Add(Instruction.Create(OpCodes.Call, stubModule.Import( + typeof (RuntimeHelpers).GetMethod("InitializeArray")))); + return repl.ToArray(); + }); } private void InjectStub(ConfuserContext context, CompressorContext compCtx, ProtectionParameters parameters, ModuleDef stubModule) { @@ -165,13 +173,20 @@ private void InjectStub(ConfuserContext context, CompressorContext compCtx, Prot } compCtx.Deriver.Init(context, random); + context.Logger.Debug("Encrypting modules..."); + // Main MethodDef entryPoint = defs.OfType().Single(method => method.Name == "Main"); stubModule.EntryPoint = entryPoint; uint seed = random.NextUInt32(); compCtx.OriginModule = context.OutputModules[compCtx.ModuleIndex]; - byte[] encryptedModule = compCtx.Encrypt(comp, compCtx.OriginModule, seed); + + byte[] encryptedModule = compCtx.Encrypt(comp, compCtx.OriginModule, seed, + progress => context.Logger.Progress((int)(progress * 10000), 10000)); + context.Logger.EndProgress(); + context.CheckCancellation(); + compCtx.EncryptedModule = encryptedModule; MutationHelper.InjectKeys(entryPoint, @@ -196,9 +211,8 @@ private void InjectStub(ConfuserContext context, CompressorContext compCtx, Prot instrs.RemoveAt(i - 1); instrs.RemoveAt(i - 2); instrs.InsertRange(i - 2, compCtx.Deriver.EmitDerivation(decrypter, context, (Local)ldDst.Operand, (Local)ldSrc.Operand)); - } - else if (method.DeclaringType.Name == "Lzma" && - method.Name == "Decompress") { + } else if (method.DeclaringType.Name == "Lzma" && + method.Name == "Decompress") { MethodDef decomp = comp.GetRuntimeDecompressor(stubModule, member => { }); instr.Operand = decomp; } @@ -228,8 +242,7 @@ public void OnWriterEvent(ModuleWriterBase writer, ModuleWriterEvent evt) { uint sigToken = 0x11000000 | sigRid; ctx.KeyToken = sigToken; MutationHelper.InjectKey(writer.Module.EntryPoint, 2, (int)sigToken); - } - else if (evt == ModuleWriterEvent.MDBeginAddResources) { + } else if (evt == ModuleWriterEvent.MDBeginAddResources) { // Compute hash byte[] hash = SHA1.Create().ComputeHash(ctx.OriginModule); uint hashBlob = writer.MetaData.BlobHeap.Add(hash); diff --git a/Confuser.Protections/Compress/CompressorContext.cs b/Confuser.Protections/Compress/CompressorContext.cs index ae435e61d..9d859bcfe 100644 --- a/Confuser.Protections/Compress/CompressorContext.cs +++ b/Confuser.Protections/Compress/CompressorContext.cs @@ -19,7 +19,7 @@ internal class CompressorContext { public string ModuleName; public byte[] OriginModule; - public byte[] Encrypt(ICompressionService compress, byte[] data, uint seed) { + public byte[] Encrypt(ICompressionService compress, byte[] data, uint seed, Action progressFunc) { data = (byte[])data.Clone(); var dst = new uint[0x10]; var src = new uint[0x10]; @@ -37,7 +37,7 @@ public byte[] Encrypt(ICompressionService compress, byte[] data, uint seed) { if ((i & 0xff) == 0) state = (state * state) % 0x8a5cb7; } - data = compress.Compress(data); + data = compress.Compress(data, progressFunc); Array.Resize(ref data, (data.Length + 3) & ~3); var encryptedData = new byte[data.Length]; diff --git a/Confuser.Protections/Compress/ExtractPhase.cs b/Confuser.Protections/Compress/ExtractPhase.cs index 03a9c597f..948d99c09 100644 --- a/Confuser.Protections/Compress/ExtractPhase.cs +++ b/Confuser.Protections/Compress/ExtractPhase.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Confuser.Core; @@ -14,15 +15,26 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Modules; } } + public override string Name { + get { return "Packer info extraction"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { if (context.Packer == null) return; - if (context.Annotations.Get(context, Compressor.ContextKey) != null) + bool isExe = context.CurrentModule.Kind == ModuleKind.Windows || + context.CurrentModule.Kind == ModuleKind.Console; + + if (context.Annotations.Get(context, Compressor.ContextKey) != null) { + if (isExe) { + context.Logger.Error("Too many executable modules!"); + throw new ConfuserException(null); + } return; + } - var mainModule = parameters.GetParameter(context, null, "main"); - if (context.CurrentModule.Name == mainModule) { + if (isExe) { var ctx = new CompressorContext { ModuleIndex = context.CurrentModuleIndex, Assembly = context.CurrentModule.Assembly diff --git a/Confuser.Protections/Compress/StubProtection.cs b/Confuser.Protections/Compress/StubProtection.cs index aa1e23e32..cdaaeb1f4 100644 --- a/Confuser.Protections/Compress/StubProtection.cs +++ b/Confuser.Protections/Compress/StubProtection.cs @@ -49,6 +49,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Modules; } } + public override string Name { + get { return "Packer info encoding"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { context.CurrentModuleWriterListener.OnWriterEvent += (sender, e) => { if (e.WriterEvent == ModuleWriterEvent.MDBeginCreateTables) { diff --git a/Confuser.Protections/Constants/EncodePhase.cs b/Confuser.Protections/Constants/EncodePhase.cs index 204b10cca..ca704fe29 100644 --- a/Confuser.Protections/Constants/EncodePhase.cs +++ b/Confuser.Protections/Constants/EncodePhase.cs @@ -20,6 +20,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Constants encoding"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { var moduleCtx = context.Annotations.Get(context.CurrentModule, ConstantProtection.ContextKey); if (!parameters.Targets.Any() || moduleCtx == null) @@ -33,11 +37,12 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa // encode moduleCtx.EncodedBuffer = new List(); - foreach (var entry in ldInit) // Ensure the array length haven't been encoded yet + foreach (var entry in ldInit.WithProgress(context.Logger)) // Ensure the array length haven't been encoded yet { EncodeInitializer(moduleCtx, entry.Key, entry.Value); + context.CheckCancellation(); } - foreach (var entry in ldc) { + foreach (var entry in ldc.WithProgress(context.Logger)) { if (entry.Key is string) { EncodeString(moduleCtx, (string)entry.Key, entry.Value); } @@ -59,6 +64,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa } else throw new UnreachableException(); + context.CheckCancellation(); } // compress @@ -72,6 +78,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa } Debug.Assert(buffIndex == encodedBuff.Length); encodedBuff = context.Registry.GetService().Compress(encodedBuff); + context.CheckCancellation(); uint compressedLen = (uint)(encodedBuff.Length + 3) / 4; compressedLen = (compressedLen + 0xfu) & ~0xfu; @@ -214,7 +221,7 @@ private void ExtractConstants( ConfuserContext context, ProtectionParameters parameters, CEContext moduleCtx, Dictionary>> ldc, Dictionary>> ldInit) { - foreach (MethodDef method in parameters.Targets.OfType()) { + foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.Logger)) { if (!method.HasBody) continue; @@ -319,6 +326,8 @@ private void ExtractConstants( if (eligible) ldc.AddListEntry(instr.Operand, Tuple.Create(method, instr)); } + + context.CheckCancellation(); } } diff --git a/Confuser.Protections/Constants/InjectPhase.cs b/Confuser.Protections/Constants/InjectPhase.cs index 2abdf46aa..11b0fada3 100644 --- a/Confuser.Protections/Constants/InjectPhase.cs +++ b/Confuser.Protections/Constants/InjectPhase.cs @@ -18,6 +18,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Constant encryption helpers injection"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { if (parameters.Targets.Any()) { var compression = context.Registry.GetService(); diff --git a/Confuser.Protections/ControlFlow/ControlFlowPhase.cs b/Confuser.Protections/ControlFlow/ControlFlowPhase.cs index d82355d13..141f79a88 100644 --- a/Confuser.Protections/ControlFlow/ControlFlowPhase.cs +++ b/Confuser.Protections/ControlFlow/ControlFlowPhase.cs @@ -20,6 +20,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Control flow mangling"; } + } + private static CFContext ParseParameters(MethodDef method, ConfuserContext context, ProtectionParameters parameters, RandomGenerator random, bool disableOpti) { var ret = new CFContext(); ret.Type = parameters.GetParameter(context, method, "type", CFType.Switch); @@ -67,9 +71,10 @@ 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()) + foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.Logger)) if (method.HasBody && method.Body.Instructions.Count > 0) { ProcessMethod(method.Body, ParseParameters(method, context, parameters, random, disabledOpti)); + context.CheckCancellation(); } } diff --git a/Confuser.Protections/InvalidMetadataProtection.cs b/Confuser.Protections/InvalidMetadataProtection.cs index 3c2fe60ae..5182ab31e 100644 --- a/Confuser.Protections/InvalidMetadataProtection.cs +++ b/Confuser.Protections/InvalidMetadataProtection.cs @@ -49,6 +49,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Modules; } } + public override string Name { + get { return "Invalid metadata addition"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { if (parameters.Targets.Contains(context.CurrentModule)) { random = context.Registry.GetService().GetRandomGenerator(_FullId); diff --git a/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs b/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs index 48013bbff..0c13f3980 100644 --- a/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs +++ b/Confuser.Protections/ReferenceProxy/ReferenceProxyPhase.cs @@ -16,6 +16,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Encoding reference proxies"; } + } + private static RPContext ParseParameters(MethodDef method, ConfuserContext context, ProtectionParameters parameters, RPStore store) { var ret = new RPContext(); ret.Mode = parameters.GetParameter(context, method, "mode", Mode.Mild); @@ -96,9 +100,10 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa var store = new RPStore { random = random }; - foreach (MethodDef method in parameters.Targets.OfType()) + foreach (MethodDef method in parameters.Targets.OfType().WithProgress(context.Logger)) if (method.HasBody && method.Body.Instructions.Count > 0) { ProcessMethod(ParseParameters(method, context, parameters, store)); + context.CheckCancellation(); } RPContext ctx = ParseParameters(context.CurrentModule, context, parameters, store); diff --git a/Confuser.Protections/Resources/InjectPhase.cs b/Confuser.Protections/Resources/InjectPhase.cs index 03d262a7b..f4e1bc788 100644 --- a/Confuser.Protections/Resources/InjectPhase.cs +++ b/Confuser.Protections/Resources/InjectPhase.cs @@ -19,6 +19,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.Methods; } } + public override string Name { + get { return "Resource encryption helpers injection"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { if (parameters.Targets.Any()) { var compression = context.Registry.GetService(); diff --git a/Confuser.Protections/Resources/MDPhase.cs b/Confuser.Protections/Resources/MDPhase.cs index 9c9d5ee85..bfe77f5c8 100644 --- a/Confuser.Protections/Resources/MDPhase.cs +++ b/Confuser.Protections/Resources/MDPhase.cs @@ -26,6 +26,9 @@ public void Hook() { private void OnWriterEvent(object sender, ModuleWriterListenerEventArgs e) { var writer = (ModuleWriter)sender; if (e.WriterEvent == ModuleWriterEvent.MDBeginAddResources) { + ctx.Context.CheckCancellation(); + ctx.Context.Logger.Debug("Encrypting resources..."); + List resources = ctx.Module.Resources.OfType().ToList(); ctx.Module.Resources.RemoveWhere(res => res is EmbeddedResource); @@ -51,7 +54,11 @@ private void OnWriterEvent(object sender, ModuleWriterListenerEventArgs e) { } // compress - moduleBuff = ctx.Context.Registry.GetService().Compress(moduleBuff); + moduleBuff = ctx.Context.Registry.GetService().Compress( + moduleBuff, + progress => ctx.Context.Logger.Progress((int)(progress * 10000), 10000)); + ctx.Context.Logger.EndProgress(); + ctx.Context.CheckCancellation(); uint compressedLen = (uint)(moduleBuff.Length + 3) / 4; compressedLen = (compressedLen + 0xfu) & ~0xfu; @@ -91,8 +98,7 @@ private void OnWriterEvent(object sender, ModuleWriterListenerEventArgs e) { MutationHelper.InjectKeys(ctx.InitMethod, new[] { 0, 1 }, new[] { (int)(size / 4), (int)(keySeed) }); - } - else if (e.WriterEvent == ModuleWriterEvent.EndCalculateRvasAndFileOffsets) { + } else if (e.WriterEvent == ModuleWriterEvent.EndCalculateRvasAndFileOffsets) { TablesHeap tblHeap = writer.MetaData.TablesHeap; tblHeap.FieldRVATable[writer.MetaData.GetFieldRVARid(ctx.DataField)].RVA = (uint)encryptedResource.RVA; } diff --git a/Confuser.Renamer/AnalyzePhase.cs b/Confuser.Renamer/AnalyzePhase.cs index bb2786676..f2c396403 100644 --- a/Confuser.Renamer/AnalyzePhase.cs +++ b/Confuser.Renamer/AnalyzePhase.cs @@ -15,6 +15,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.AllDefinitions; } } + public override string Name { + get { return "Name analysis"; } + } + private void ParseParameters(IDnlibDef def, ConfuserContext context, NameService service, ProtectionParameters parameters) { var mode = parameters.GetParameter(context, def, "mode", null); if (mode != null) @@ -24,7 +28,7 @@ private void ParseParameters(IDnlibDef def, ConfuserContext context, NameService protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { var service = (NameService)context.Registry.GetService(); context.Logger.Debug("Building VTables & identifier list..."); - foreach (IDnlibDef def in parameters.Targets) { + foreach (IDnlibDef def in parameters.Targets.WithProgress(context.Logger)) { ParseParameters(def, context, service, parameters); if (def is ModuleDef) { @@ -39,12 +43,14 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa service.GetVTables().GetVTable((TypeDef)def); service.SetOriginalNamespace(def, ((TypeDef)def).Namespace); } + context.CheckCancellation(); } context.Logger.Debug("Analyzing..."); IList renamers = service.Renamers; - foreach (IDnlibDef def in parameters.Targets) { + foreach (IDnlibDef def in parameters.Targets.WithProgress(context.Logger)) { Analyze(service, context, parameters, def, true); + context.CheckCancellation(); } } diff --git a/Confuser.Renamer/PostRenamePhase.cs b/Confuser.Renamer/PostRenamePhase.cs index d26d09f44..e72fb9653 100644 --- a/Confuser.Renamer/PostRenamePhase.cs +++ b/Confuser.Renamer/PostRenamePhase.cs @@ -14,12 +14,17 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.AllDefinitions; } } + public override string Name { + get { return "Post-renaming"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { var service = (NameService)context.Registry.GetService(); foreach (IRenamer renamer in service.Renamers) { foreach (IDnlibDef def in parameters.Targets) renamer.PostRename(context, service, def); + context.CheckCancellation(); } } } diff --git a/Confuser.Renamer/RenamePhase.cs b/Confuser.Renamer/RenamePhase.cs index 0455236fd..7066f7a81 100644 --- a/Confuser.Renamer/RenamePhase.cs +++ b/Confuser.Renamer/RenamePhase.cs @@ -12,6 +12,10 @@ public override ProtectionTargets Targets { get { return ProtectionTargets.AllDefinitions; } } + public override string Name { + get { return "Renaming"; } + } + protected override void Execute(ConfuserContext context, ProtectionParameters parameters) { var service = (NameService)context.Registry.GetService(); @@ -19,9 +23,10 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa foreach (IRenamer renamer in service.Renamers) { foreach (IDnlibDef def in parameters.Targets) renamer.PreRename(context, service, def); + context.CheckCancellation(); } - foreach (IDnlibDef def in parameters.Targets) { + foreach (IDnlibDef def in parameters.Targets.WithProgress(context.Logger)) { if (def is MethodDef) if (parameters.GetParameter(context, def, "renameArgs", true)) { foreach (ParamDef param in ((MethodDef)def).ParamDefs) @@ -62,6 +67,7 @@ protected override void Execute(ConfuserContext context, ProtectionParameters pa throw new ConfuserException(null); } } + context.CheckCancellation(); } } } diff --git a/Confuser2.sln b/Confuser2.sln index 4e449d087..c9d645fc5 100644 --- a/Confuser2.sln +++ b/Confuser2.sln @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Confuser.Runtime", "Confuse {BEB67A6E-4C54-4DE5-8C6B-2C12F44A7B92} = {BEB67A6E-4C54-4DE5-8C6B-2C12F44A7B92} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfuserEx", "ConfuserEx\ConfuserEx.csproj", "{B5205EBA-EC32-4C53-86A0-FAEEE7393EC0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +60,10 @@ Global {A45C184F-F98F-4258-A928-BFF437034791}.Debug|Any CPU.Build.0 = Release|Any CPU {A45C184F-F98F-4258-A928-BFF437034791}.Release|Any CPU.ActiveCfg = Release|Any CPU {A45C184F-F98F-4258-A928-BFF437034791}.Release|Any CPU.Build.0 = Release|Any CPU + {B5205EBA-EC32-4C53-86A0-FAEEE7393EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5205EBA-EC32-4C53-86A0-FAEEE7393EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5205EBA-EC32-4C53-86A0-FAEEE7393EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5205EBA-EC32-4C53-86A0-FAEEE7393EC0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ConfuserEx/App.xaml b/ConfuserEx/App.xaml new file mode 100644 index 000000000..8c66cf03a --- /dev/null +++ b/ConfuserEx/App.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + Resources/#FontAwesome + + + \ No newline at end of file diff --git a/ConfuserEx/App.xaml.cs b/ConfuserEx/App.xaml.cs new file mode 100644 index 000000000..df9c8accd --- /dev/null +++ b/ConfuserEx/App.xaml.cs @@ -0,0 +1,6 @@ +using System; +using System.Windows; + +namespace ConfuserEx { + public partial class App : Application { } +} \ No newline at end of file diff --git a/ConfuserEx/BrushToColorConverter.cs b/ConfuserEx/BrushToColorConverter.cs new file mode 100644 index 000000000..6ed30a062 --- /dev/null +++ b/ConfuserEx/BrushToColorConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace ConfuserEx { + public class BrushToColorConverter : IValueConverter { + public static readonly BrushToColorConverter Instance = new BrushToColorConverter(); + private BrushToColorConverter() { } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + var brush = value as SolidColorBrush; + if (brush != null) + return brush.Color; + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/CompComboBox.xaml b/ConfuserEx/CompComboBox.xaml new file mode 100644 index 000000000..b316505fe --- /dev/null +++ b/ConfuserEx/CompComboBox.xaml @@ -0,0 +1,43 @@ + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/CompComboBox.xaml.cs b/ConfuserEx/CompComboBox.xaml.cs new file mode 100644 index 000000000..f3766ce2a --- /dev/null +++ b/ConfuserEx/CompComboBox.xaml.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using Confuser.Core; + +namespace ConfuserEx { + public partial class CompComboBox : UserControl { + public static readonly DependencyProperty ComponentsProperty = DependencyProperty.Register("Components", typeof (IEnumerable), typeof (CompComboBox), new UIPropertyMetadata(null)); + public static readonly DependencyProperty SelectedComponentProperty = DependencyProperty.Register("SelectedComponent", typeof (ConfuserComponent), typeof (CompComboBox), new UIPropertyMetadata(null)); + public static readonly DependencyProperty ArgumentsProperty = DependencyProperty.Register("Arguments", typeof (Dictionary), typeof (CompComboBox), new UIPropertyMetadata(null)); + + public CompComboBox() { + InitializeComponent(); + } + + public IEnumerable Components { + get { return (IEnumerable)GetValue(ComponentsProperty); } + set { SetValue(ComponentsProperty, value); } + } + + public ConfuserComponent SelectedComponent { + get { return (ConfuserComponent)GetValue(SelectedComponentProperty); } + set { SetValue(SelectedComponentProperty, value); } + } + + public Dictionary Arguments { + get { return (Dictionary)GetValue(ArgumentsProperty); } + set { SetValue(ArgumentsProperty, value); } + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ComponentConverter.cs b/ConfuserEx/ComponentConverter.cs new file mode 100644 index 000000000..e665fb141 --- /dev/null +++ b/ConfuserEx/ComponentConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Data; +using Confuser.Core; + +namespace ConfuserEx { + internal class ComponentConverter : Freezable, IValueConverter { + public static readonly DependencyProperty ComponentsProperty = DependencyProperty.Register("Components", typeof (IList), typeof (ComponentConverter), new UIPropertyMetadata(null)); + + public IList Components { + get { return (IList)GetValue(ComponentsProperty); } + set { SetValue(ComponentsProperty, value); } + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + Debug.Assert(value is string || value == null); + Debug.Assert(targetType == typeof (ConfuserComponent)); + Debug.Assert(Components != null); + + if (value == null) return null; + return Components.Single(comp => comp.Id == (string)value); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { + Debug.Assert(value is ConfuserComponent || value == null); + Debug.Assert(targetType == typeof (string)); + + if (value == null) return null; + return ((ConfuserComponent)value).Id; + } + + protected override Freezable CreateInstanceCore() { + return new ComponentConverter(); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ComponentDiscovery.cs b/ConfuserEx/ComponentDiscovery.cs new file mode 100644 index 000000000..cf08f7a7d --- /dev/null +++ b/ConfuserEx/ComponentDiscovery.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Confuser.Core; + +namespace ConfuserEx { + internal class ComponentDiscovery { + private static void CrossDomainLoadComponents() { + var ctx = (CrossDomainContext)AppDomain.CurrentDomain.GetData("ctx"); + Assembly assembly = Assembly.LoadFile(ctx.PluginPath); + foreach (Type i in assembly.GetTypes()) { + if (i.IsAbstract || !PluginDiscovery.HasAccessibleDefConstructor(i)) + continue; + + if (typeof (Protection).IsAssignableFrom(i)) { + var prot = (Protection)Activator.CreateInstance(i); + ctx.AddProtection(Info.FromComponent(prot, ctx.PluginPath)); + } else if (typeof (Packer).IsAssignableFrom(i)) { + var packer = (Packer)Activator.CreateInstance(i); + ctx.AddPacker(Info.FromComponent(packer, ctx.PluginPath)); + } + } + } + + public static void LoadComponents(IList protections, IList packers, string pluginPath) { + var ctx = new CrossDomainContext(protections, packers, pluginPath); + AppDomain appDomain = AppDomain.CreateDomain(""); + appDomain.SetData("ctx", ctx); + appDomain.DoCallBack(CrossDomainLoadComponents); + AppDomain.Unload(appDomain); + } + + public static void RemoveComponents(IList protections, IList packers, string pluginPath) { + protections.RemoveWhere(comp => comp is InfoComponent && ((InfoComponent)comp).info.path == pluginPath); + packers.RemoveWhere(comp => comp is InfoComponent && ((InfoComponent)comp).info.path == pluginPath); + } + + private class CrossDomainContext : MarshalByRefObject { + private readonly IList packers; + private readonly string pluginPath; + private readonly IList protections; + + public CrossDomainContext(IList protections, IList packers, string pluginPath) { + this.protections = protections; + this.packers = packers; + this.pluginPath = pluginPath; + } + + public string PluginPath { + get { return pluginPath; } + } + + public void AddProtection(Info info) { + protections.Add(new InfoComponent(info)); + } + + public void AddPacker(Info info) { + packers.Add(new InfoComponent(info)); + } + } + + [Serializable] + private class Info { + public string desc; + public string fullId; + public string id; + public string name; + public string path; + + public static Info FromComponent(ConfuserComponent component, string pluginPath) { + var ret = new Info(); + ret.name = component.Name; + ret.desc = component.Description; + ret.id = component.Id; + ret.fullId = component.FullId; + ret.path = pluginPath; + return ret; + } + } + + private class InfoComponent : ConfuserComponent { + public readonly Info info; + + public InfoComponent(Info info) { + this.info = info; + } + + public override string Name { + get { return info.name; } + } + + public override string Description { + get { return info.desc; } + } + + public override string Id { + get { return info.id; } + } + + public override string FullId { + get { return info.fullId; } + } + + protected override void Initialize(ConfuserContext context) { + throw new NotSupportedException(); + } + + protected override void PopulatePipeline(ProtectionPipeline pipeline) { + throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ConfuserEx.csproj b/ConfuserEx/ConfuserEx.csproj new file mode 100644 index 000000000..96288e2fc --- /dev/null +++ b/ConfuserEx/ConfuserEx.csproj @@ -0,0 +1,247 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {B5205EBA-EC32-4C53-86A0-FAEEE7393EC0} + WinExe + Properties + ConfuserEx + ConfuserEx + v4.0 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + ..\ + true + + + true + ..\Debug\bin\ + DEBUG;TRACE + full + AnyCPU + prompt + + + ..\Release\bin\ + TRACE + true + pdbonly + AnyCPU + prompt + + + ConfuserEx.ico + + + true + + + ..\ConfuserEx.snk + + + + ..\packages\MvvmLightLibs.4.3.31.1\lib\net40\GalaSoft.MvvmLight.Extras.WPF4.dll + + + ..\packages\MvvmLightLibs.4.3.31.1\lib\net40\GalaSoft.MvvmLight.WPF4.dll + + + ..\packages\CommonServiceLocator.1.2\lib\portable-windows8+net40+sl5+windowsphone8\Microsoft.Practices.ServiceLocation.dll + + + ..\deps\Ookii.Dialogs.Wpf.dll + + + + True + ..\packages\TaskParallelLibrary.1.0.2856.0\lib\Net35\System.Threading.dll + + + ..\packages\MvvmLightLibs.4.3.31.1\lib\net40\System.Windows.Interactivity.dll + + + + + + + + + + + + MSBuild:Compile + Designer + + + Properties\GlobalAssemblyInfo.cs + + + + CompComboBox.xaml + + + + + + + + + + + + + + + + + + + + + + + ProjectModuleView.xaml + + + ProjectRuleView.xaml + + + ProjectTabAdvancedView.xaml + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + + + + + + + Properties\ConfuserEx.snk + + + + + + + + + {BEB67A6E-4C54-4DE5-8C6B-2C12F44A7B92} + Confuser.Core + + + {E832E9B8-2158-4FC0-89A1-56C6ECC10F6B} + Confuser.DynCipher + + + {3EAB01B5-9B49-48D8-BFA1-5493B26CCB71} + Confuser.Protections + + + {862DA0DA-52E1-47CD-B9C2-46B106031B28} + Confuser.Renamer + + + {A45C184F-F98F-4258-A928-BFF437034791} + Confuser.Runtime + + + {FDFC1237-143F-4919-8318-4926901F4639} + dnlib + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + + PTL + + + + + solveAliasProblem;$(PrepareResourcesDependsOn) + + + \ No newline at end of file diff --git a/ConfuserEx/ConfuserEx.ico b/ConfuserEx/ConfuserEx.ico new file mode 100644 index 000000000..7bcfaf714 Binary files /dev/null and b/ConfuserEx/ConfuserEx.ico differ diff --git a/ConfuserEx/EnumValuesExtension.cs b/ConfuserEx/EnumValuesExtension.cs new file mode 100644 index 000000000..5056ce774 --- /dev/null +++ b/ConfuserEx/EnumValuesExtension.cs @@ -0,0 +1,16 @@ +using System; +using System.Windows.Markup; + +namespace ConfuserEx { + public class EnumValuesExtension : MarkupExtension { + private readonly Type enumType; + + public EnumValuesExtension(Type enumType) { + this.enumType = enumType; + } + + public override object ProvideValue(IServiceProvider serviceProvider) { + return Enum.GetValues(enumType); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/FileDragDrop.cs b/ConfuserEx/FileDragDrop.cs new file mode 100644 index 000000000..2e1955f2c --- /dev/null +++ b/ConfuserEx/FileDragDrop.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using ConfuserEx.ViewModel; +using GalaSoft.MvvmLight.Command; + +namespace ConfuserEx { + public class FileDragDrop { + public static readonly DependencyProperty CommandProperty = + DependencyProperty.RegisterAttached("Command", typeof (ICommand), typeof (FileDragDrop), new UIPropertyMetadata(null, OnCommandChanged)); + + public static ICommand FileCmd = new DragDropCommand( + data => { + Debug.Assert(data.Item2.GetDataPresent(DataFormats.FileDrop)); + if (data.Item1 is TextBox) { + string file = ((string[])data.Item2.GetData(DataFormats.FileDrop))[0]; + Debug.Assert(File.Exists(file)); + ((TextBox)data.Item1).Text = file; + } else if (data.Item1 is ListBox) { + var files = (string[])data.Item2.GetData(DataFormats.FileDrop); + Debug.Assert(files.All(file => File.Exists(file))); + var list = (IList)((ListBox)data.Item1).ItemsSource; + foreach (string file in files) + list.Add(new StringItem(file)); + } else + throw new NotSupportedException(); + }, data => { + if (!data.Item2.GetDataPresent(DataFormats.FileDrop)) + return false; + return ((string[])data.Item2.GetData(DataFormats.FileDrop)).All(file => File.Exists(file)); + }); + + + public static ICommand DirectoryCmd = new DragDropCommand( + data => { + Debug.Assert(data.Item2.GetDataPresent(DataFormats.FileDrop)); + if (data.Item1 is TextBox) { + string dir = ((string[])data.Item2.GetData(DataFormats.FileDrop))[0]; + Debug.Assert(Directory.Exists(dir)); + ((TextBox)data.Item1).Text = dir; + } else if (data.Item1 is ListBox) { + var dirs = (string[])data.Item2.GetData(DataFormats.FileDrop); + Debug.Assert(dirs.All(dir => Directory.Exists(dir))); + var list = (IList)((ListBox)data.Item1).ItemsSource; + foreach (string dir in dirs) + list.Add(new StringItem(dir)); + } else + throw new NotSupportedException(); + }, data => { + if (!data.Item2.GetDataPresent(DataFormats.FileDrop)) + return false; + return ((string[])data.Item2.GetData(DataFormats.FileDrop)).All(dir => Directory.Exists(dir)); + }); + + public static ICommand GetCommand(DependencyObject obj) { + return (ICommand)obj.GetValue(CommandProperty); + } + + public static void SetCommand(DependencyObject obj, ICommand value) { + obj.SetValue(CommandProperty, value); + } + + private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var elem = (UIElement)d; + if (e.NewValue != null) { + elem.AllowDrop = true; + elem.PreviewDragOver += OnDragOver; + elem.PreviewDrop += OnDrop; + } else { + elem.AllowDrop = false; + elem.PreviewDragOver -= OnDragOver; + elem.PreviewDrop -= OnDrop; + } + } + + private static void OnDragOver(object sender, DragEventArgs e) { + ICommand cmd = GetCommand((DependencyObject)sender); + e.Effects = DragDropEffects.None; + if (cmd is DragDropCommand) { + if (cmd.CanExecute(Tuple.Create((UIElement)sender, e.Data))) + e.Effects = DragDropEffects.Link; + } else { + if (cmd.CanExecute(e.Data)) + e.Effects = DragDropEffects.Link; + } + e.Handled = true; + } + + private static void OnDrop(object sender, DragEventArgs e) { + ICommand cmd = GetCommand((DependencyObject)sender); + if (cmd is DragDropCommand) { + if (cmd.CanExecute(Tuple.Create((UIElement)sender, e.Data))) + cmd.Execute(Tuple.Create((UIElement)sender, e.Data)); + } else { + if (cmd.CanExecute(e.Data)) + cmd.Execute(e.Data); + } + e.Handled = true; + } + + + private class DragDropCommand : RelayCommand> { + public DragDropCommand(Action> execute, Func, bool> canExecute) + : base(execute, canExecute) { } + } + } +} \ No newline at end of file diff --git a/ConfuserEx/InvertBoolConverter.cs b/ConfuserEx/InvertBoolConverter.cs new file mode 100644 index 000000000..c1088a124 --- /dev/null +++ b/ConfuserEx/InvertBoolConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Windows.Data; + +namespace ConfuserEx { + internal class InvertBoolConverter : IValueConverter { + public static readonly InvertBoolConverter Instance = new InvertBoolConverter(); + private InvertBoolConverter() { } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + Debug.Assert(value is bool); + Debug.Assert(targetType == typeof (bool)); + return !(bool)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/MainWindow.xaml b/ConfuserEx/MainWindow.xaml new file mode 100644 index 000000000..204d5b451 --- /dev/null +++ b/ConfuserEx/MainWindow.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/MainWindow.xaml.cs b/ConfuserEx/MainWindow.xaml.cs new file mode 100644 index 000000000..d3b713e0c --- /dev/null +++ b/ConfuserEx/MainWindow.xaml.cs @@ -0,0 +1,38 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using Confuser.Core.Project; +using ConfuserEx.ViewModel; + +namespace ConfuserEx { + public partial class MainWindow : Window { + public MainWindow() { + InitializeComponent(); + + var app = new AppVM(); + app.Project = new ProjectVM(new ConfuserProject()); + app.FileName = "Unnamed.crproj"; + + app.Tabs.Add(new ProjectTabVM(app)); + app.Tabs.Add(new SettingsTabVM(app)); + app.Tabs.Add(new ProtectTabVM(app)); + + DataContext = app; + } + + private void OpenMenu(object sender, RoutedEventArgs e) { + var btn = (Button)sender; + ContextMenu menu = btn.ContextMenu; + menu.PlacementTarget = btn; + menu.Placement = PlacementMode.MousePoint; + menu.IsOpen = true; + } + + protected override void OnClosing(CancelEventArgs e) { + base.OnClosing(e); + e.Cancel = !((AppVM)DataContext).OnWindowClosing(); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/Properties/AssemblyInfo.cs b/ConfuserEx/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..739f72280 --- /dev/null +++ b/ConfuserEx/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: AssemblyTitle("ConfuserEx")] +[assembly: AssemblyDescription("ConfuserEx GUI")] \ No newline at end of file diff --git a/ConfuserEx/Resources/CREDITS b/ConfuserEx/Resources/CREDITS new file mode 100644 index 000000000..ca84f085e --- /dev/null +++ b/ConfuserEx/Resources/CREDITS @@ -0,0 +1 @@ +Icons adapted from http://flaticons.net \ No newline at end of file diff --git a/ConfuserEx/Resources/Decode.png b/ConfuserEx/Resources/Decode.png new file mode 100644 index 000000000..4d020f6ff Binary files /dev/null and b/ConfuserEx/Resources/Decode.png differ diff --git a/ConfuserEx/Resources/Error.png b/ConfuserEx/Resources/Error.png new file mode 100644 index 000000000..369bf7b81 Binary files /dev/null and b/ConfuserEx/Resources/Error.png differ diff --git a/ConfuserEx/Resources/FontAwesome.otf b/ConfuserEx/Resources/FontAwesome.otf new file mode 100644 index 000000000..3461e3fce Binary files /dev/null and b/ConfuserEx/Resources/FontAwesome.otf differ diff --git a/ConfuserEx/Resources/New.png b/ConfuserEx/Resources/New.png new file mode 100644 index 000000000..ac61f0328 Binary files /dev/null and b/ConfuserEx/Resources/New.png differ diff --git a/ConfuserEx/Resources/Open.png b/ConfuserEx/Resources/Open.png new file mode 100644 index 000000000..506d4b6da Binary files /dev/null and b/ConfuserEx/Resources/Open.png differ diff --git a/ConfuserEx/Resources/Save.png b/ConfuserEx/Resources/Save.png new file mode 100644 index 000000000..66a9cf95f Binary files /dev/null and b/ConfuserEx/Resources/Save.png differ diff --git a/ConfuserEx/Resources/Tools.png b/ConfuserEx/Resources/Tools.png new file mode 100644 index 000000000..984532575 Binary files /dev/null and b/ConfuserEx/Resources/Tools.png differ diff --git a/ConfuserEx/Skin.cs b/ConfuserEx/Skin.cs new file mode 100644 index 000000000..8729f0726 --- /dev/null +++ b/ConfuserEx/Skin.cs @@ -0,0 +1,51 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; + +namespace ConfuserEx { + public class Skin { + public static readonly DependencyProperty EmptyPromptProperty = + DependencyProperty.RegisterAttached("EmptyPrompt", typeof (string), typeof (Skin), new UIPropertyMetadata(null)); + + public static readonly DependencyProperty TabsDisabledProperty = + DependencyProperty.RegisterAttached("TabsDisabled", typeof (bool), typeof (Skin), new UIPropertyMetadata(false)); + + public static readonly DependencyProperty RTBDocumentProperty = + DependencyProperty.RegisterAttached("RTBDocument", typeof (FlowDocument), typeof (Skin), new FrameworkPropertyMetadata(null, OnRTBDocumentChanged)); + + public static string GetEmptyPrompt(DependencyObject obj) { + return (string)obj.GetValue(EmptyPromptProperty); + } + + public static void SetEmptyPrompt(DependencyObject obj, string value) { + obj.SetValue(EmptyPromptProperty, value); + } + + public static bool GetTabsDisabled(DependencyObject obj) { + return (bool)obj.GetValue(TabsDisabledProperty); + } + + public static void SetTabsDisabled(DependencyObject obj, bool value) { + obj.SetValue(TabsDisabledProperty, value); + } + + public static void OnRTBDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs dpe) { + var rtb = (RichTextBox)d; + if (dpe.NewValue != null) { + rtb.Document = (FlowDocument)dpe.NewValue; + rtb.TextChanged += (sender, e) => rtb.ScrollToEnd(); + } + else + rtb.Document = new FlowDocument(); + } + + public static FlowDocument GetRTBDocument(DependencyObject obj) { + return (FlowDocument)obj.GetValue(RTBDocumentProperty); + } + + public static void SetRTBDocument(DependencyObject obj, FlowDocument value) { + obj.SetValue(RTBDocumentProperty, value); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/Skin.xaml b/ConfuserEx/Skin.xaml new file mode 100644 index 000000000..85dc51996 --- /dev/null +++ b/ConfuserEx/Skin.xaml @@ -0,0 +1,1501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + None + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + None + + M 0 0 L 3.5 4 L 7 0 Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/ViewModel/IViewModel.cs b/ConfuserEx/ViewModel/IViewModel.cs new file mode 100644 index 000000000..dcfa0d430 --- /dev/null +++ b/ConfuserEx/ViewModel/IViewModel.cs @@ -0,0 +1,7 @@ +using System; + +namespace ConfuserEx.ViewModel { + public interface IViewModel { + TModel Model { get; } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/Project/ProjectModuleVM.cs b/ConfuserEx/ViewModel/Project/ProjectModuleVM.cs new file mode 100644 index 000000000..47adb0881 --- /dev/null +++ b/ConfuserEx/ViewModel/Project/ProjectModuleVM.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; +using System.Threading; +using Confuser.Core.Project; + +namespace ConfuserEx.ViewModel { + public class ProjectModuleVM : ViewModelBase, IViewModel, IRuleContainer { + private readonly ProjectModule module; + private readonly ProjectVM parent; + private string asmName = "Unknown"; + private string simpleName; + + public ProjectModuleVM(ProjectVM parent, ProjectModule module) { + this.parent = parent; + this.module = module; + + ObservableCollection rules = Utils.Wrap(module.Rules, rule => new ProjectRuleVM(parent, rule)); + rules.CollectionChanged += (sender, e) => parent.IsModified = true; + Rules = rules; + + SimpleName = System.IO.Path.GetFileName(module.Path); + LoadAssemblyName(); + } + + public ProjectModule Module { + get { return module; } + } + + public string Path { + get { return module.Path; } + set { + if (SetProperty(module.Path != value, val => module.Path = val, value, "Path")) { + parent.IsModified = true; + SimpleName = System.IO.Path.GetFileName(module.Path); + LoadAssemblyName(); + } + } + } + + public string SimpleName { + get { return simpleName; } + private set { SetProperty(ref simpleName, value, "SimpleName"); } + } + + public string AssemblyName { + get { return asmName; } + private set { SetProperty(ref asmName, value, "AssemblyName"); } + } + + public string SNKeyPath { + get { return module.SNKeyPath; } + set { + if (SetProperty(module.SNKeyPath != value, val => module.SNKeyPath = val, value, "SNKeyPath")) + parent.IsModified = true; + } + } + + public string SNKeyPassword { + get { return module.SNKeyPassword; } + set { + if (SetProperty(module.SNKeyPassword != value, val => module.SNKeyPassword = val, value, "SNKeyPassword")) + parent.IsModified = true; + } + } + + public IList Rules { get; private set; } + + ProjectModule IViewModel.Model { + get { return module; } + } + + private void LoadAssemblyName() { + AssemblyName = "Loading..."; + ThreadPool.QueueUserWorkItem(_ => { + try { + string path = System.IO.Path.Combine(parent.BaseDirectory, Path); + AssemblyName name = System.Reflection.AssemblyName.GetAssemblyName(path); + AssemblyName = name.FullName; + } catch { + AssemblyName = "Unknown"; + } + }); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/Project/ProjectRuleVM.cs b/ConfuserEx/ViewModel/Project/ProjectRuleVM.cs new file mode 100644 index 000000000..cc179b3d6 --- /dev/null +++ b/ConfuserEx/ViewModel/Project/ProjectRuleVM.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Confuser.Core; +using Confuser.Core.Project; +using Confuser.Core.Project.Patterns; + +namespace ConfuserEx.ViewModel { + internal interface IRuleContainer { + IList Rules { get; } + } + + public class ProjectRuleVM : ViewModelBase, IViewModel { + private readonly ProjectVM parent; + private readonly Rule rule; + private string error; + private PatternExpression exp; + + public ProjectRuleVM(ProjectVM parent, Rule rule) { + this.parent = parent; + this.rule = rule; + + ObservableCollection> protections = Utils.Wrap(rule, setting => new ProjectSettingVM(parent, setting)); + protections.CollectionChanged += (sender, e) => parent.IsModified = true; + Protections = protections; + + ParseExpression(); + } + + public ProjectVM Project { + get { return parent; } + } + + public string Pattern { + get { return rule.Pattern; } + set { + if (SetProperty(rule.Pattern != value, val => rule.Pattern = val, value, "Pattern")) { + parent.IsModified = true; + ParseExpression(); + } + } + } + + public PatternExpression Expression { + get { return exp; } + set { SetProperty(ref exp, value, "Expression"); } + } + + public string ExpressionError { + get { return error; } + set { SetProperty(ref error, value, "ExpressionError"); } + } + + public ProtectionPreset Preset { + get { return rule.Preset; } + set { + if (SetProperty(rule.Preset != value, val => rule.Preset = val, value, "Preset")) + parent.IsModified = true; + } + } + + public bool Inherit { + get { return rule.Inherit; } + set { + if (SetProperty(rule.Inherit != value, val => rule.Inherit = val, value, "Inherit")) + parent.IsModified = true; + } + } + + public IList> Protections { get; private set; } + + Rule IViewModel.Model { + get { return rule; } + } + + private void ParseExpression() { + PatternExpression expression; + try { + expression = new PatternParser().Parse(Pattern); + ExpressionError = null; + } catch (Exception e) { + ExpressionError = e.Message; + expression = null; + } + Expression = expression; + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/Project/ProjectSettingVM.cs b/ConfuserEx/ViewModel/Project/ProjectSettingVM.cs new file mode 100644 index 000000000..4fbae9a5c --- /dev/null +++ b/ConfuserEx/ViewModel/Project/ProjectSettingVM.cs @@ -0,0 +1,34 @@ +using System; +using Confuser.Core.Project; + +namespace ConfuserEx.ViewModel { + public class ProjectSettingVM : ViewModelBase, IViewModel> { + private readonly ProjectVM parent; + private readonly SettingItem setting; + + public ProjectSettingVM(ProjectVM parent, SettingItem setting) { + this.parent = parent; + this.setting = setting; + } + + public string Id { + get { return setting.Id; } + set { + if (SetProperty(setting.Id != value, val => setting.Id = val, value, "Id")) + parent.IsModified = true; + } + } + + public SettingItemAction Action { + get { return setting.Action; } + set { + if (SetProperty(setting.Action != value, val => setting.Action = val, value, "Action")) + parent.IsModified = true; + } + } + + SettingItem IViewModel>.Model { + get { return setting; } + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/Project/ProjectVM.cs b/ConfuserEx/ViewModel/Project/ProjectVM.cs new file mode 100644 index 000000000..21c1f5209 --- /dev/null +++ b/ConfuserEx/ViewModel/Project/ProjectVM.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; +using Confuser.Core; +using Confuser.Core.Project; + +namespace ConfuserEx.ViewModel { + public class ProjectVM : ViewModelBase, IViewModel, IRuleContainer { + private readonly ConfuserProject proj; + private bool modified; + private ProjectSettingVM packer; + + public ProjectVM(ConfuserProject proj) { + this.proj = proj; + + ObservableCollection modules = Utils.Wrap(proj, module => new ProjectModuleVM(this, module)); + modules.CollectionChanged += (sender, e) => IsModified = true; + Modules = modules; + + ObservableCollection plugins = Utils.Wrap(proj.PluginPaths, path => new StringItem(path)); + plugins.CollectionChanged += (sender, e) => IsModified = true; + Plugins = plugins; + + ObservableCollection probePaths = Utils.Wrap(proj.ProbePaths, path => new StringItem(path)); + probePaths.CollectionChanged += (sender, e) => IsModified = true; + ProbePaths = probePaths; + + ObservableCollection rules = Utils.Wrap(proj.Rules, rule => new ProjectRuleVM(this, rule)); + rules.CollectionChanged += (sender, e) => IsModified = true; + Rules = rules; + + Protections = new ObservableCollection(); + Packers = new ObservableCollection(); + ComponentDiscovery.LoadComponents(Protections, Packers, Assembly.Load("Confuser.Protections").Location); + ComponentDiscovery.LoadComponents(Protections, Packers, Assembly.Load("Confuser.Renamer").Location); + } + + public ConfuserProject Project { + get { return proj; } + } + + public bool IsModified { + get { return modified; } + set { SetProperty(ref modified, value, "IsModified"); } + } + + public string Seed { + get { return proj.Seed; } + set { SetProperty(proj.Seed != value, val => proj.Seed = val, value, "Seed"); } + } + + public bool Debug { + get { return proj.Debug; } + set { SetProperty(proj.Debug != value, val => proj.Debug = val, value, "Debug"); } + } + + public string BaseDirectory { + get { return proj.BaseDirectory; } + set { SetProperty(proj.BaseDirectory != value, val => proj.BaseDirectory = val, value, "BaseDirectory"); } + } + + public string OutputDirectory { + get { return proj.OutputDirectory; } + set { SetProperty(proj.OutputDirectory != value, val => proj.OutputDirectory = val, value, "OutputDirectory"); } + } + + public ProjectSettingVM Packer { + get { + if (proj.Packer == null) + packer = null; + else + packer = new ProjectSettingVM(this, proj.Packer); + return packer; + } + set { + var vm = (IViewModel>)value; + bool changed = (vm == null && proj.Packer != null) || (vm != null && proj.Packer != vm.Model); + SetProperty(changed, val => proj.Packer = val == null ? null : val.Model, vm, "Packer"); + } + } + + public IList Modules { get; private set; } + public IList Plugins { get; private set; } + public IList ProbePaths { get; private set; } + + public ObservableCollection Protections { get; private set; } + public ObservableCollection Packers { get; private set; } + public IList Rules { get; private set; } + + ConfuserProject IViewModel.Model { + get { return proj; } + } + + protected override void OnPropertyChanged(string property) { + base.OnPropertyChanged(property); + if (property != "IsModified") + IsModified = true; + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/StringItem.cs b/ConfuserEx/ViewModel/StringItem.cs new file mode 100644 index 000000000..b81f292ee --- /dev/null +++ b/ConfuserEx/ViewModel/StringItem.cs @@ -0,0 +1,19 @@ +using System; + +namespace ConfuserEx.ViewModel { + public class StringItem : IViewModel { + public StringItem(string item) { + Item = item; + } + + public string Item { get; private set; } + + string IViewModel.Model { + get { return Item; } + } + + public override string ToString() { + return Item; + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/UI/AppVM.cs b/ConfuserEx/ViewModel/UI/AppVM.cs new file mode 100644 index 000000000..95b0f05f9 --- /dev/null +++ b/ConfuserEx/ViewModel/UI/AppVM.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Windows; +using System.Windows.Input; +using System.Xml; +using Confuser.Core; +using Confuser.Core.Project; +using GalaSoft.MvvmLight.Command; +using Ookii.Dialogs.Wpf; + +namespace ConfuserEx.ViewModel { + public class AppVM : ViewModelBase { + private readonly IList tabs = new ObservableCollection(); + private string fileName; + private bool navDisabled; + + private ProjectVM proj; + + public bool NavigationDisabled { + get { return navDisabled; } + set { SetProperty(ref navDisabled, value, "NavigationDisabled"); } + } + + public ProjectVM Project { + get { return proj; } + set { + if (proj != null) + proj.PropertyChanged -= OnProjectPropertyChanged; + + SetProperty(ref proj, value, "Project"); + + if (proj != null) + proj.PropertyChanged += OnProjectPropertyChanged; + } + } + + public string FileName { + get { return fileName; } + set { + SetProperty(ref fileName, value, "Project"); + OnPropertyChanged("Title"); + } + } + + public string Title { + get { + return string.Format("{0}{1} - {2}", + Path.GetFileName(fileName), + (proj.IsModified ? "*" : ""), + ConfuserEngine.Version); + } + } + + public IList Tabs { + get { return tabs; } + } + + public ICommand NewProject { + get { return new RelayCommand(NewProj, () => !NavigationDisabled); } + } + + public ICommand OpenProject { + get { return new RelayCommand(OpenProj, () => !NavigationDisabled); } + } + + public ICommand SaveProject { + get { return new RelayCommand(() => SaveProj(), () => !NavigationDisabled); } + } + + public ICommand Decode { + get { return new RelayCommand(() => { MessageBox.Show("Not yet implemented!", "ConfuserEx", MessageBoxButton.OK, MessageBoxImage.Information); }, () => !NavigationDisabled); } + } + + public bool OnWindowClosing() { + return PromptSave(); + } + + private bool SaveProj() { + if (File.Exists(FileName)) { + var sfd = new VistaSaveFileDialog(); + sfd.FileName = FileName; + sfd.Filter = "ConfuserEx Projects (*.crproj)|*.crproj|All Files (*.*)|*.*"; + if (!(sfd.ShowDialog(Application.Current.MainWindow) ?? false) || sfd.FileName == null) + return false; + FileName = sfd.FileName; + } + ConfuserProject proj = ((IViewModel)Project).Model; + proj.Save().Save(FileName); + Project.IsModified = false; + return true; + } + + private bool PromptSave() { + if (!Project.IsModified) + return true; + switch (MessageBox.Show("The current project has unsaved changes. Do you want to save them?", "ConfuserEx", MessageBoxButton.YesNoCancel, MessageBoxImage.Question)) { + case MessageBoxResult.Yes: + return SaveProj(); + case MessageBoxResult.No: + return true; + case MessageBoxResult.Cancel: + return false; + } + return false; + } + + private void NewProj() { + if (!PromptSave()) + return; + + Project = new ProjectVM(new ConfuserProject()); + FileName = "Unnamed.crproj"; + } + + private void OpenProj() { + if (!PromptSave()) + return; + + var ofd = new VistaOpenFileDialog(); + ofd.Filter = "ConfuserEx Projects (*.crproj)|*.crproj|All Files (*.*)|*.*"; + if ((ofd.ShowDialog(Application.Current.MainWindow) ?? false) && ofd.FileName != null) { + string fileName = ofd.FileName; + try { + var xmlDoc = new XmlDocument(); + xmlDoc.Load(fileName); + var proj = new ConfuserProject(); + proj.Load(xmlDoc); + Project = new ProjectVM(proj); + FileName = fileName; + } catch { + MessageBox.Show("Invalid project!", "ConfuserEx", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + } + + private void OnProjectPropertyChanged(object sender, PropertyChangedEventArgs e) { + if (e.PropertyName == "IsModified" && proj.IsModified) + OnPropertyChanged("Title"); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/UI/ProjectTabVM.cs b/ConfuserEx/ViewModel/UI/ProjectTabVM.cs new file mode 100644 index 000000000..3c61ee8a4 --- /dev/null +++ b/ConfuserEx/ViewModel/UI/ProjectTabVM.cs @@ -0,0 +1,131 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using Confuser.Core.Project; +using ConfuserEx.Views; +using GalaSoft.MvvmLight.Command; +using Ookii.Dialogs.Wpf; + +namespace ConfuserEx.ViewModel { + public class ProjectTabVM : TabViewModel { + private int selIndex = -1; + + public ProjectTabVM(AppVM app) + : base(app, "Project") { } + + public ICommand DragDrop { + get { + return new RelayCommand(data => { + foreach (string file in (string[])data.GetData(DataFormats.FileDrop)) + AddModule(file); + }, data => { + if (!data.GetDataPresent(DataFormats.FileDrop)) + return false; + var files = (string[])data.GetData(DataFormats.FileDrop); + bool ret = files.All(file => File.Exists(file)); + return ret; + }); + } + } + + public int SelectedIndex { + get { return selIndex; } + set { SetProperty(ref selIndex, value, "SelectedIndex"); } + } + + public ICommand ChooseBaseDir { + get { + return new RelayCommand(() => { + var fbd = new VistaFolderBrowserDialog(); + fbd.SelectedPath = App.Project.BaseDirectory; + if (fbd.ShowDialog() ?? false) { + App.Project.BaseDirectory = fbd.SelectedPath; + App.Project.OutputDirectory = Path.Combine(App.Project.BaseDirectory, "Confused"); + } + }); + } + } + + public ICommand ChooseOutputDir { + get { + return new RelayCommand(() => { + var fbd = new VistaFolderBrowserDialog(); + fbd.SelectedPath = App.Project.OutputDirectory; + if (fbd.ShowDialog() ?? false) { + App.Project.OutputDirectory = fbd.SelectedPath; + } + }); + } + } + + public ICommand Add { + get { + return new RelayCommand(() => { + var ofd = new VistaOpenFileDialog(); + ofd.Filter = ".NET assemblies (*.exe, *.dll)|*.exe;*.dll|All Files (*.*)|*.*"; + if (ofd.ShowDialog() ?? false) { + AddModule(ofd.FileName); + } + }); + } + } + + public ICommand Remove { + get { + return new RelayCommand(() => { + int selIndex = SelectedIndex; + Debug.Assert(selIndex != -1); + ProjectModuleVM module = App.Project.Modules[selIndex]; + string msg = string.Format("Are you sure to remove module '{0}'?\r\nAll settings specific to it would be lost!", module.Path); + if (MessageBox.Show(msg, "ConfuserEx", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { + App.Project.Modules.RemoveAt(selIndex); + SelectedIndex = selIndex >= App.Project.Modules.Count ? App.Project.Modules.Count - 1 : selIndex; + } + }, () => SelectedIndex != -1); + } + } + + public ICommand Edit { + get { + return new RelayCommand(() => { + Debug.Assert(SelectedIndex != -1); + var dialog = new ProjectModuleView(App.Project.Modules[SelectedIndex]); + dialog.Owner = Application.Current.MainWindow; + dialog.ShowDialog(); + }, () => SelectedIndex != -1); + } + } + + public ICommand Advanced { + get { + return new RelayCommand(() => { + var dialog = new ProjectTabAdvancedView(App.Project); + dialog.Owner = Application.Current.MainWindow; + dialog.ShowDialog(); + }); + } + } + + private void AddModule(string file) { + if (!File.Exists(file)) { + MessageBox.Show(string.Format("File '{0}' does not exists!", file), "ConfuserEx", MessageBoxButton.OK, MessageBoxImage.Error); + return; + } + if (string.IsNullOrEmpty(App.Project.BaseDirectory)) { + string directory = Path.GetDirectoryName(file); + App.Project.BaseDirectory = directory; + App.Project.OutputDirectory = Path.Combine(directory, "Confused"); + } + var module = new ProjectModuleVM(App.Project, new ProjectModule()); + try { + module.Path = Confuser.Core.Utils.GetRelativePath(file, App.Project.BaseDirectory); + } catch { + module.Path = file; + } + App.Project.Modules.Add(module); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/UI/ProtectTabVM.cs b/ConfuserEx/ViewModel/UI/ProtectTabVM.cs new file mode 100644 index 000000000..6feaff6da --- /dev/null +++ b/ConfuserEx/ViewModel/UI/ProtectTabVM.cs @@ -0,0 +1,151 @@ +extern alias PTL; + +using System; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using Confuser.Core; +using Confuser.Core.Project; +using GalaSoft.MvvmLight.Command; +using PTL::System.Threading; + +// http://connect.microsoft.com/VisualStudio/feedback/details/615953/ + +namespace ConfuserEx.ViewModel { + internal class ProtectTabVM : TabViewModel, ILogger { + private readonly Paragraph documentContent; + private double? progress = 0; + private bool? result; + + public ProtectTabVM(AppVM app) + : base(app, "Protect!") { + documentContent = new Paragraph(); + LogDocument = new FlowDocument(); + LogDocument.Blocks.Add(documentContent); + } + + public ICommand ProtectCmd { + get { return new RelayCommand(DoProtect, () => !App.NavigationDisabled); } + } + + public ICommand CancelCmd { + get { return new RelayCommand(DoCancel, () => App.NavigationDisabled); } + } + + public double? Progress { + get { return progress; } + set { SetProperty(ref progress, value, "Progress"); } + } + + public FlowDocument LogDocument { get; private set; } + + public bool? Result { + get { return result; } + set { SetProperty(ref result, value, "Result"); } + } + + CancellationTokenSource cancelSrc; + private void DoProtect() { + var parameters = new ConfuserParameters(); + parameters.Project = ((IViewModel)App.Project).Model; + parameters.Logger = this; + + documentContent.Inlines.Clear(); + cancelSrc = new CancellationTokenSource(); + Result = null; + Progress = null; + begin = DateTime.Now; + App.NavigationDisabled = true; + + ConfuserEngine.Run(parameters, cancelSrc.Token) + .ContinueWith(_ => + Application.Current.Dispatcher.BeginInvoke(new Action(() => { + Progress = 0; + App.NavigationDisabled = false; + CommandManager.InvalidateRequerySuggested(); + }))); + } + + private void DoCancel() { + cancelSrc.Cancel(); + } + + private 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 + + private 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) { + Progress = (double)progress / overall; + } + + void ILogger.EndProgress() { + Progress = null; + } + + void ILogger.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); + Result = successful; + } + + #endregion + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/UI/SettingsTabVM.cs b/ConfuserEx/ViewModel/UI/SettingsTabVM.cs new file mode 100644 index 000000000..cdbc2ae8d --- /dev/null +++ b/ConfuserEx/ViewModel/UI/SettingsTabVM.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Windows; +using System.Windows.Data; +using System.Windows.Input; +using Confuser.Core; +using Confuser.Core.Project; +using ConfuserEx.Views; +using GalaSoft.MvvmLight.Command; + +namespace ConfuserEx.ViewModel { + internal class SettingsTabVM : TabViewModel { + private bool hasPacker; + private IRuleContainer selectedList; + private int selectedRuleIndex; + + public SettingsTabVM(AppVM app) + : base(app, "Settings") { + app.PropertyChanged += (sender, e) => { + if (e.PropertyName == "Project") + InitProject(); + }; + InitProject(); + } + + public bool HasPacker { + get { return hasPacker; } + set { SetProperty(ref hasPacker, value, "HasPacker"); } + } + + public IList ModulesView { get; private set; } + + public IRuleContainer SelectedList { + get { return selectedList; } + set { + if (SetProperty(ref selectedList, value, "SelectedList")) + SelectedRuleIndex = -1; + } + } + + public int SelectedRuleIndex { + get { return selectedRuleIndex; } + set { SetProperty(ref selectedRuleIndex, value, "SelectedRuleIndex"); } + } + + public ICommand Add { + get { + return new RelayCommand(() => { + Debug.Assert(SelectedList != null); + + var rule = new ProjectRuleVM(App.Project, new Rule()); + rule.Pattern = "true"; + SelectedList.Rules.Add(rule); + SelectedRuleIndex = SelectedList.Rules.Count - 1; + }, () => SelectedList != null); + } + } + + public ICommand Remove { + get { + return new RelayCommand(() => { + int selIndex = SelectedRuleIndex; + Debug.Assert(SelectedList != null); + Debug.Assert(selIndex != -1); + + ProjectRuleVM rule = SelectedList.Rules[selIndex]; + SelectedList.Rules.RemoveAt(selIndex); + SelectedRuleIndex = selIndex >= SelectedList.Rules.Count ? SelectedList.Rules.Count - 1 : selIndex; + }, () => SelectedRuleIndex != -1 && SelectedList != null); + } + } + + public ICommand Edit { + get { + return new RelayCommand(() => { + Debug.Assert(SelectedRuleIndex != -1); + var dialog = new ProjectRuleView(App.Project, SelectedList.Rules[SelectedRuleIndex]); + dialog.Owner = Application.Current.MainWindow; + dialog.ShowDialog(); + dialog.Cleanup(); + }, () => SelectedRuleIndex != -1 && SelectedList != null); + } + } + + private void InitProject() { + ModulesView = new CompositeCollection { + App.Project, + new CollectionContainer { Collection = App.Project.Modules } + }; + OnPropertyChanged("ModulesView"); + HasPacker = App.Project.Packer != null; + } + + protected override void OnPropertyChanged(string property) { + if (property == "HasPacker") { + if (hasPacker) + App.Project.Packer = new ProjectSettingVM(App.Project, new SettingItem { Id = App.Project.Packers[0].Id }); + else + App.Project.Packer = null; + } + base.OnPropertyChanged(property); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/UI/TabViewModel.cs b/ConfuserEx/ViewModel/UI/TabViewModel.cs new file mode 100644 index 000000000..41c5ff40c --- /dev/null +++ b/ConfuserEx/ViewModel/UI/TabViewModel.cs @@ -0,0 +1,13 @@ +using System; + +namespace ConfuserEx.ViewModel { + public abstract class TabViewModel : ViewModelBase { + protected TabViewModel(AppVM app, string header) { + App = app; + Header = header; + } + + public AppVM App { get; private set; } + public string Header { get; private set; } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/Utils.cs b/ConfuserEx/ViewModel/Utils.cs new file mode 100644 index 000000000..02e546b21 --- /dev/null +++ b/ConfuserEx/ViewModel/Utils.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace ConfuserEx.ViewModel { + public static class Utils { + public static ObservableCollection Wrap(IList list) { + var ret = new ObservableCollection(list); + + ret.CollectionChanged += (sender, e) => { + var collection = (ObservableCollection)sender; + switch (e.Action) { + case NotifyCollectionChangedAction.Reset: + list.Clear(); + foreach (T item in collection) + list.Add(item); + break; + + case NotifyCollectionChangedAction.Add: + for (int i = 0; i < e.NewItems.Count; i++) + list.Insert(e.NewStartingIndex + i, (T)e.NewItems[i]); + break; + + case NotifyCollectionChangedAction.Remove: + for (int i = 0; i < e.OldItems.Count; i++) + list.RemoveAt(e.OldStartingIndex); + break; + + case NotifyCollectionChangedAction.Move: + list.RemoveAt(e.OldStartingIndex); + list.Insert(e.NewStartingIndex, (T)e.NewItems[0]); + break; + + case NotifyCollectionChangedAction.Replace: + list[e.NewStartingIndex] = (T)e.NewItems[0]; + break; + } + }; + return ret; + } + + public static ObservableCollection Wrap(IList list, Func transform) where TViewModel : IViewModel { + var ret = new ObservableCollection(list.Select(item => transform(item))); + + ret.CollectionChanged += (sender, e) => { + var collection = (ObservableCollection)sender; + switch (e.Action) { + case NotifyCollectionChangedAction.Reset: + list.Clear(); + foreach (TViewModel item in collection) + list.Add(item.Model); + break; + + case NotifyCollectionChangedAction.Add: + for (int i = 0; i < e.NewItems.Count; i++) + list.Insert(e.NewStartingIndex + i, ((TViewModel)e.NewItems[i]).Model); + break; + + case NotifyCollectionChangedAction.Remove: + for (int i = 0; i < e.OldItems.Count; i++) + list.RemoveAt(e.OldStartingIndex); + break; + + case NotifyCollectionChangedAction.Move: + list.RemoveAt(e.OldStartingIndex); + list.Insert(e.NewStartingIndex, ((TViewModel)e.NewItems[0]).Model); + break; + + case NotifyCollectionChangedAction.Replace: + list[e.NewStartingIndex] = ((TViewModel)e.NewItems[0]).Model; + break; + } + }; + return ret; + } + } +} \ No newline at end of file diff --git a/ConfuserEx/ViewModel/ViewModelBase.cs b/ConfuserEx/ViewModel/ViewModelBase.cs new file mode 100644 index 000000000..6d6cc5a75 --- /dev/null +++ b/ConfuserEx/ViewModel/ViewModelBase.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace ConfuserEx.ViewModel { + public class ViewModelBase : INotifyPropertyChanged { + // http://stackoverflow.com/a/1316417/462805 + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string property) { + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs(property)); + } + + protected bool SetProperty(ref T field, T value, string property) { + if (!EqualityComparer.Default.Equals(field, value)) { + field = value; + OnPropertyChanged(property); + return true; + } + return false; + } + + protected bool SetProperty(bool changed, Action setter, T value, string property) { + if (changed) { + setter(value); + OnPropertyChanged(property); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/ConfuserEx/Views.xaml b/ConfuserEx/Views.xaml new file mode 100644 index 000000000..ab38891f4 --- /dev/null +++ b/ConfuserEx/Views.xaml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/Views/ProjectModuleView.xaml b/ConfuserEx/Views/ProjectModuleView.xaml new file mode 100644 index 000000000..6ae201b1d --- /dev/null +++ b/ConfuserEx/Views/ProjectModuleView.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/Views/ProjectTabAdvancedView.xaml.cs b/ConfuserEx/Views/ProjectTabAdvancedView.xaml.cs new file mode 100644 index 000000000..2e580a111 --- /dev/null +++ b/ConfuserEx/Views/ProjectTabAdvancedView.xaml.cs @@ -0,0 +1,63 @@ +using System; +using System.Diagnostics; +using System.Windows; +using ConfuserEx.ViewModel; +using GalaSoft.MvvmLight.Command; +using Ookii.Dialogs.Wpf; + +namespace ConfuserEx.Views { + public partial class ProjectTabAdvancedView : Window { + private readonly ProjectVM project; + + public ProjectTabAdvancedView(ProjectVM project) { + InitializeComponent(); + this.project = project; + DataContext = project; + } + + public override void OnApplyTemplate() { + base.OnApplyTemplate(); + + AddPlugin.Command = new RelayCommand(() => { + var ofd = new VistaOpenFileDialog(); + ofd.Filter = ".NET assemblies (*.exe, *.dll)|*.exe;*.dll|All Files (*.*)|*.*"; + ofd.Multiselect = true; + if (ofd.ShowDialog() ?? false) { + foreach (string plugin in ofd.FileNames) { + try { + ComponentDiscovery.LoadComponents(project.Protections, project.Packers, plugin); + project.Plugins.Add(new StringItem(plugin)); + } catch { + MessageBox.Show("Failed to load plugin '" + plugin + "'."); + } + } + } + }); + + RemovePlugin.Command = new RelayCommand(() => { + int selIndex = PluginPaths.SelectedIndex; + Debug.Assert(selIndex != -1); + + string plugin = project.Plugins[selIndex].Item; + ComponentDiscovery.RemoveComponents(project.Protections, project.Packers, plugin); + project.Plugins.RemoveAt(selIndex); + + PluginPaths.SelectedIndex = selIndex >= project.Plugins.Count ? project.Plugins.Count - 1 : selIndex; + }, () => PluginPaths.SelectedIndex != -1); + + + AddProbe.Command = new RelayCommand(() => { + var fbd = new VistaFolderBrowserDialog(); + if (fbd.ShowDialog() ?? false) + project.ProbePaths.Add(new StringItem(fbd.SelectedPath)); + }); + + RemoveProbe.Command = new RelayCommand(() => { + int selIndex = ProbePaths.SelectedIndex; + Debug.Assert(selIndex != -1); + project.ProbePaths.RemoveAt(selIndex); + ProbePaths.SelectedIndex = selIndex >= project.ProbePaths.Count ? project.ProbePaths.Count - 1 : selIndex; + }, () => ProbePaths.SelectedIndex != -1); + } + } +} \ No newline at end of file diff --git a/ConfuserEx/Views/ProjectTabView.xaml b/ConfuserEx/Views/ProjectTabView.xaml new file mode 100644 index 000000000..fe8cf5a37 --- /dev/null +++ b/ConfuserEx/Views/ProjectTabView.xaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/Views/ProtectTabView.xaml b/ConfuserEx/Views/ProtectTabView.xaml new file mode 100644 index 000000000..34fd123be --- /dev/null +++ b/ConfuserEx/Views/ProtectTabView.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/app.config b/ConfuserEx/app.config new file mode 100644 index 000000000..aa7fbdfa2 --- /dev/null +++ b/ConfuserEx/app.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfuserEx/packages.config b/ConfuserEx/packages.config new file mode 100644 index 000000000..7742d6624 --- /dev/null +++ b/ConfuserEx/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 9deab2f64..bc769d318 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Usage `Confuser.CLI ` The project file is a ConfuserEx Project (*.crproj). -The format of project file can be found in ProjectFormat.md +The format of project file can be found in docs\ProjectFormat.md Bug Report ---------- diff --git a/additional/Icon.pdn b/additional/Icon.pdn new file mode 100644 index 000000000..3b51762bf Binary files /dev/null and b/additional/Icon.pdn differ diff --git a/additional/Icon16.pdn b/additional/Icon16.pdn new file mode 100644 index 000000000..65b31e0a5 Binary files /dev/null and b/additional/Icon16.pdn differ diff --git a/additional/Icon256.pdn b/additional/Icon256.pdn new file mode 100644 index 000000000..7c51275ac Binary files /dev/null and b/additional/Icon256.pdn differ diff --git a/additional/Icon32.pdn b/additional/Icon32.pdn new file mode 100644 index 000000000..2423e4fd6 Binary files /dev/null and b/additional/Icon32.pdn differ diff --git a/additional/Icon48.pdn b/additional/Icon48.pdn new file mode 100644 index 000000000..d56b6266e Binary files /dev/null and b/additional/Icon48.pdn differ diff --git a/additional/Icon64.pdn b/additional/Icon64.pdn new file mode 100644 index 000000000..a6730baac Binary files /dev/null and b/additional/Icon64.pdn differ diff --git a/deps/Ookii.Dialogs.Wpf.dll b/deps/Ookii.Dialogs.Wpf.dll new file mode 100644 index 000000000..b4e4956cf Binary files /dev/null and b/deps/Ookii.Dialogs.Wpf.dll differ diff --git a/ProjectFormat.md b/docs/ProjectFormat.md similarity index 100% rename from ProjectFormat.md rename to docs/ProjectFormat.md diff --git a/docs.shfbproj b/docs/docs.shfbproj similarity index 100% rename from docs.shfbproj rename to docs/docs.shfbproj