From 9889004340495af859ef1b21ce76f7a738bd660c Mon Sep 17 00:00:00 2001
From: Bepis <36346617+bbepis@users.noreply.github.com>
Date: Sat, 31 Jul 2021 19:25:48 +1000
Subject: [PATCH] Initial code commit
---
NStrip.sln | 25 +++
NStrip/App.config | 6 +
NStrip/AssemblyStripper.cs | 96 +++++++++
NStrip/NArgs.cs | 320 ++++++++++++++++++++++++++++++
NStrip/NStrip.csproj | 65 ++++++
NStrip/Program.cs | 144 ++++++++++++++
NStrip/Properties/AssemblyInfo.cs | 36 ++++
NStrip/packages.config | 5 +
8 files changed, 697 insertions(+)
create mode 100644 NStrip.sln
create mode 100644 NStrip/App.config
create mode 100644 NStrip/AssemblyStripper.cs
create mode 100644 NStrip/NArgs.cs
create mode 100644 NStrip/NStrip.csproj
create mode 100644 NStrip/Program.cs
create mode 100644 NStrip/Properties/AssemblyInfo.cs
create mode 100644 NStrip/packages.config
diff --git a/NStrip.sln b/NStrip.sln
new file mode 100644
index 0000000..93f3d6d
--- /dev/null
+++ b/NStrip.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30011.22
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NStrip", "NStrip\NStrip.csproj", "{CE5BFF3E-2350-46AE-A54E-0631D89565F4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CE5BFF3E-2350-46AE-A54E-0631D89565F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE5BFF3E-2350-46AE-A54E-0631D89565F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE5BFF3E-2350-46AE-A54E-0631D89565F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE5BFF3E-2350-46AE-A54E-0631D89565F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {505788DE-B387-43E7-90DB-98E6903B88EE}
+ EndGlobalSection
+EndGlobal
diff --git a/NStrip/App.config b/NStrip/App.config
new file mode 100644
index 0000000..88fa402
--- /dev/null
+++ b/NStrip/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NStrip/AssemblyStripper.cs b/NStrip/AssemblyStripper.cs
new file mode 100644
index 0000000..e148b54
--- /dev/null
+++ b/NStrip/AssemblyStripper.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace NStrip
+{
+ public static class AssemblyStripper
+ {
+ static IEnumerable GetAllTypeDefinitions(AssemblyDefinition assembly)
+ {
+ var typeQueue = new Queue(assembly.MainModule.Types);
+
+ while (typeQueue.Count > 0)
+ {
+ var type = typeQueue.Dequeue();
+
+ yield return type;
+
+ foreach (var nestedType in type.NestedTypes)
+ typeQueue.Enqueue(nestedType);
+ }
+ }
+
+ static void ClearMethodBodies(TypeReference voidTypeReference, ICollection methods)
+ {
+ foreach (MethodDefinition method in methods)
+ {
+ if (!method.HasBody)
+ continue;
+
+ MethodBody body = new MethodBody(method);
+ var il = body.GetILProcessor();
+
+ // There's multiple ways we could handle this:
+ // - Only provide a ret. Smallest size, however if .NET tries to load this assembly during runtime it might fail.
+ // - Providing a value and ret (what we currently do). Slightly more space, however .NET should be fine loading it.
+ // - Null body, i.e. mark everything as extern. Should theoretically work when loaded into .NET and be the smallest size,
+ // but the size of assembly remains the same. Might be a bug within Mono.Cecil.
+
+ if (method.ReturnType.IsPrimitive)
+ {
+ il.Emit(OpCodes.Ldc_I4_0);
+ }
+ else if (method.ReturnType != voidTypeReference)
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Ret);
+
+ method.Body = body;
+
+ method.AggressiveInlining = false;
+ method.NoInlining = true;
+ }
+ }
+
+ public static void StripAssembly(AssemblyDefinition assembly)
+ {
+ if (!assembly.MainModule.TryGetTypeReference("System.Void", out var voidTypeReference))
+ {
+ voidTypeReference = assembly.MainModule.ImportReference(typeof(void));
+ }
+
+ foreach (TypeDefinition type in GetAllTypeDefinitions(assembly))
+ {
+ if (type.IsEnum || type.IsInterface)
+ continue;
+
+ ClearMethodBodies(voidTypeReference, type.Methods);
+ }
+
+ assembly.MainModule.Resources.Clear();
+ }
+
+ public static void MakePublic(AssemblyDefinition assembly, IList typeNameBlacklist)
+ {
+ foreach (var type in GetAllTypeDefinitions(assembly))
+ {
+ if (typeNameBlacklist.Contains(type.Name))
+ continue;
+
+ if (type.IsNested)
+ type.IsNestedPublic = true;
+ else
+ type.IsPublic = true;
+
+ foreach (var method in type.Methods)
+ method.IsPublic = true;
+
+ foreach (var field in type.Fields)
+ field.IsPublic = true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/NStrip/NArgs.cs b/NStrip/NArgs.cs
new file mode 100644
index 0000000..33d7f28
--- /dev/null
+++ b/NStrip/NArgs.cs
@@ -0,0 +1,320 @@
+/*
+ NArgs
+ The MIT License (MIT)
+
+ Copyright(c) 2021 Bepis
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace NArgs
+{
+ ///
+ /// Command-line argument parser.
+ ///
+ public static class Arguments
+ {
+ ///
+ /// Parses arguments and constructs an object.
+ ///
+ /// The type of the object to construct. Must inherit from
+ /// The command-line arguments to parse.
+ ///
+ public static T Parse(string[] args) where T : IArgumentCollection, new()
+ {
+ Dictionary> valueSwitches = new Dictionary>();
+ Dictionary> boolSwitches = new Dictionary>();
+
+ var config = new T { Values = new List() };
+
+ var commandProps = GetCommandProperties();
+
+ foreach (var kv in commandProps)
+ {
+ if (kv.Value.PropertyType == typeof(bool))
+ {
+ boolSwitches.Add(kv.Key, x => kv.Value.SetValue(config, x));
+ }
+ else if (kv.Value.PropertyType == typeof(string))
+ {
+ valueSwitches.Add(kv.Key, x => kv.Value.SetValue(config, x));
+ }
+ else if (typeof(IList).IsAssignableFrom(kv.Value.PropertyType))
+ {
+ if (kv.Value.GetValue(config) == null)
+ {
+ kv.Value.SetValue(config, new List());
+ }
+
+ valueSwitches.Add(kv.Key, x =>
+ {
+ var list = (IList)kv.Value.GetValue(config);
+ list.Add(x);
+ });
+ }
+ }
+
+ CommandDefinitionAttribute previousSwitchDefinition = null;
+ bool valuesOnly = false;
+
+ foreach (string arg in args)
+ {
+ if (arg == "--")
+ {
+ // no more switches, only values
+ valuesOnly = true;
+
+ continue;
+ }
+
+ if (valuesOnly)
+ {
+ config.Values.Add(arg);
+ continue;
+ }
+
+ if (arg.StartsWith("-")
+ || arg.StartsWith("--"))
+ {
+ string previousSwitch;
+
+ if (arg.StartsWith("--"))
+ previousSwitch = arg.Substring(2);
+ else
+ previousSwitch = arg.Substring(1);
+
+ if (boolSwitches.Keys.TryFirst(x
+ => x.LongArg.Equals(previousSwitch, StringComparison.InvariantCultureIgnoreCase)
+ || x.ShortArg?.Equals(previousSwitch, StringComparison.InvariantCultureIgnoreCase) == true,
+ out var definition))
+ {
+ boolSwitches[definition](true);
+ previousSwitch = null;
+
+ continue;
+ }
+
+ if (valueSwitches.Keys.TryFirst(x
+ => x.LongArg.Equals(previousSwitch, StringComparison.InvariantCultureIgnoreCase)
+ || x.ShortArg?.Equals(previousSwitch, StringComparison.InvariantCultureIgnoreCase) == true,
+ out definition))
+ {
+ previousSwitchDefinition = definition;
+
+ continue;
+ }
+
+ Console.WriteLine("Unrecognized command line option: " + arg);
+ throw new Exception();
+ }
+
+ if (previousSwitchDefinition != null)
+ {
+ valueSwitches[previousSwitchDefinition](arg);
+ previousSwitchDefinition = null;
+ }
+ else
+ {
+ config.Values.Add(arg);
+ }
+ }
+
+ foreach (var kv in commandProps)
+ {
+ if (!kv.Key.Required)
+ continue;
+
+ if (kv.Value.PropertyType == typeof(string))
+ if (kv.Value.GetValue(config) == null)
+ throw new ArgumentException($"Required argument not provided: {kv.Key.LongArg}");
+
+ if (kv.Value.PropertyType == typeof(IList))
+ if (((IList)kv.Value.GetValue(config)).Count == 0)
+ throw new ArgumentException($"Required argument not provided: {kv.Key.LongArg}");
+ }
+
+ return config;
+ }
+
+ ///
+ /// Generates a string to be printed as console help text.
+ ///
+ /// The type of the arguments object to create help instructions for. Must inherit from
+ /// The copyright text to add at the top, if any.
+ /// The usage text to add at the top, if any.
+ public static string PrintLongHelp(string copyrightText = null, string usageText = null) where T : IArgumentCollection
+ {
+ var commands = GetCommandProperties();
+
+ var builder = new StringBuilder();
+
+ if (copyrightText != null)
+ builder.AppendLine(copyrightText);
+
+ if (usageText != null)
+ builder.AppendLine(usageText);
+
+ builder.AppendLine();
+ builder.AppendLine();
+
+ foreach (var command in commands.OrderBy(x => x.Key.ShortArg ?? "zzzz").ThenBy(x => x.Key.LongArg))
+ {
+ var valueString = string.Empty;
+
+ if (command.Value.PropertyType == typeof(IList)
+ || command.Value.PropertyType == typeof(string))
+ {
+ valueString = " ";
+ }
+
+ string listing = command.Key.ShortArg != null
+ ? $" -{command.Key.ShortArg}{valueString}, --{command.Key.LongArg}{valueString}"
+ : $" --{command.Key.LongArg}{valueString}";
+
+ const int listingWidth = 45;
+ const int descriptionWidth = 65;
+
+ string listingWidthString = "".PadLeft(listingWidth);
+
+ builder.Append(listing.PadRight(listingWidth));
+
+ if (listing.Length > listingWidth - 3)
+ {
+ builder.AppendLine();
+ builder.Append(listingWidthString);
+ }
+
+ int lineLength = 0;
+ int lastIndex = 0;
+ int currentIndex = 0;
+
+ while ((currentIndex = command.Key.Description.IndexOf(' ', currentIndex + 1)) != -1)
+ {
+ if (currentIndex - lastIndex >= descriptionWidth)
+ {
+ var descriptionSubstring = command.Key.Description.Substring(lastIndex, lineLength);
+ builder.AppendLine(descriptionSubstring);
+ builder.Append(listingWidthString);
+
+ lastIndex += lineLength + 1;
+ }
+
+ lineLength = currentIndex - lastIndex;
+ }
+
+ if (lineLength > 0)
+ {
+ var remainingSubstring = command.Key.Description.Substring(lastIndex);
+ builder.AppendLine(remainingSubstring);
+ }
+
+ builder.AppendLine();
+ }
+
+ builder.AppendLine();
+
+ return builder.ToString();
+ }
+
+ private static Dictionary GetCommandProperties()
+ {
+ var commands = new Dictionary();
+
+ foreach (var prop in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
+ {
+ var commandDef = prop.GetCustomAttribute();
+
+ if (commandDef == null)
+ continue;
+
+ commands.Add(commandDef, prop);
+ }
+
+ return commands;
+ }
+
+ private static bool TryFirst(this IEnumerable enumerable, Func predicate, out T value)
+ {
+ foreach (var item in enumerable)
+ {
+ if (predicate(item))
+ {
+ value = item;
+ return true;
+ }
+ }
+
+ value = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Specifies an object is an argument collection.
+ ///
+ public interface IArgumentCollection
+ {
+ ///
+ /// A list of values that were passed in as arguments, but not associated with an option.
+ ///
+ IList Values { get; set; }
+ }
+
+ public class CommandDefinitionAttribute : Attribute
+ {
+ ///
+ /// The short version of an option, e.g. "-a". Optional.
+ ///
+ public string ShortArg { get; set; }
+
+ ///
+ /// The long version of an option, e.g. "--append". Required.
+ ///
+ public string LongArg { get; set; }
+
+ ///
+ /// Whether or not to fail parsing if this argument has not been provided.
+ ///
+ public bool Required { get; set; } = false;
+
+ ///
+ /// The description of the option, to be used in the help text.
+ ///
+ public string Description { get; set; } = null;
+
+ /// The long version of an option, e.g. "--append".
+ public CommandDefinitionAttribute(string longArg)
+ {
+ LongArg = longArg;
+ }
+
+ /// The short version of an option, e.g. "-a".
+ /// The long version of an option, e.g. "--append".
+ public CommandDefinitionAttribute(string shortArg, string longArg)
+ {
+ ShortArg = shortArg;
+ LongArg = longArg;
+ }
+ }
+}
\ No newline at end of file
diff --git a/NStrip/NStrip.csproj b/NStrip/NStrip.csproj
new file mode 100644
index 0000000..63aac86
--- /dev/null
+++ b/NStrip/NStrip.csproj
@@ -0,0 +1,65 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CE5BFF3E-2350-46AE-A54E-0631D89565F4}
+ Exe
+ NStrip
+ NStrip
+ v4.5.2
+ 512
+ true
+ true
+
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ 8
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ 8
+
+
+
+ ..\packages\Mono.Cecil.0.11.4\lib\net40\Mono.Cecil.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
\ No newline at end of file
diff --git a/NStrip/Program.cs b/NStrip/Program.cs
new file mode 100644
index 0000000..7bad862
--- /dev/null
+++ b/NStrip/Program.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Mono.Cecil;
+using NArgs;
+
+namespace NStrip
+{
+ class Program
+ {
+ static void LogError(string message)
+ {
+ var oldColor = Console.ForegroundColor;
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ Console.WriteLine(message);
+
+ Console.ForegroundColor = oldColor;
+ }
+
+ static void LogMessage(string message)
+ {
+ Console.WriteLine(message);
+ }
+
+ static void Main(string[] args)
+ {
+ NRedirectArguments arguments = Arguments.Parse(args);
+
+ if (arguments.Values.Count == 0 || arguments.Help)
+ {
+ LogMessage(Arguments.PrintLongHelp(
+ "NStrip v1.0.0, by Bepis",
+ "Usage: NStrip [options] (<.NET .exe / .dll> | ) [