diff --git a/.gitattributes b/.gitattributes index 1ff0c42..5896c16 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,7 +17,7 @@ # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following +# the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below @@ -46,9 +46,9 @@ ############################################################################### # diff behavior for common document formats -# +# # Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the +# is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain diff --git a/JackTheEnumRipper/Core/Extensions.cs b/JackTheEnumRipper/Core/Extensions.cs new file mode 100644 index 0000000..a58eca1 --- /dev/null +++ b/JackTheEnumRipper/Core/Extensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.Text; + +namespace JackTheEnumRipper.Core +{ + public static class Extensions + { + public static string Repeat(this string source, int times) + { + return (times != 0) ? string.Concat(Enumerable.Repeat(source, times)) : source; + } + + public static bool IsValidEncoding(this Encoding encoding, string name) + { + try + { + _ = Encoding.GetEncoding(name); + return true; + } + catch (ArgumentException) + { + return false; + } + } + } +} diff --git a/JackTheEnumRipper/Core/Ini.cs b/JackTheEnumRipper/Core/Ini.cs new file mode 100644 index 0000000..6aa0256 --- /dev/null +++ b/JackTheEnumRipper/Core/Ini.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using JackTheEnumRipper.Models; + +namespace JackTheEnumRipper.Core +{ + public class Ini + { + #region Private Fields + + private readonly List
_sections = []; + + #endregion + + public Ini(IEnumerable
? sections = null, string? comment = null, char? commentSymbol = null, StringComparison? comparisonType = null) + { + this.Comment = comment ?? string.Empty; + this.CommentSymbol = commentSymbol ?? ';'; + this.ComparisonType = comparisonType ?? StringComparison.InvariantCultureIgnoreCase; + + if (sections != null) + { + foreach (Section section in sections) + { + this.AddSection(section); + } + } + } + + #region Properties + + public string Comment { get; set; } + + public char CommentSymbol { get; private set; } + + public StringComparison ComparisonType { get; private set; } + + #endregion + + #region Methods + + public void AddSection(Section section) + { + if (this._sections.Exists(s => string.Equals(s.Name, section.Name, ComparisonType))) + throw new ArgumentException($"Duplicated section: {section.Name}"); + + this._sections.Add(section); + } + + public Section? GetSection(string name) + { + return this._sections.First(section => string.Equals(name, section.Name, this.ComparisonType)); + } + + public bool RemoveSection(string name) + { + Section section = this._sections.FirstOrDefault(section => string.Equals(name, section.Name, this.ComparisonType)); + + if (section == default) return false; + + return this._sections.Remove(section); + } + + public IEnumerable GetSections() + { + return this._sections.Select(section => section.Name); + } + + private void Serialize(StringBuilder builder, Section section) + { + if (!section.IsGlobal) + { + if (!string.IsNullOrEmpty(section.Comment)) + builder.AppendLine($"{this.CommentSymbol} {section.Comment}"); + + builder.AppendLine($"[{section.Name}]"); + } + + foreach (Setting setting in section.Settings) + { + if (!string.IsNullOrEmpty(setting.Comment)) + builder.AppendLine($"{this.CommentSymbol} {setting.Comment}"); + + builder.AppendLine($"{setting.Name} = {setting.Value}"); + } + + builder.AppendLine(string.Empty); + } + + public override string ToString() + { + StringBuilder builder = new(); + + var sections = this._sections.OrderBy(x => x.IsGlobal); + + if (!string.IsNullOrEmpty(this.Comment)) + builder.AppendLine($"{this.CommentSymbol} {this.Comment} {Environment.NewLine}"); + + foreach (Section section in sections) + { + this.Serialize(builder, section); + } + + return builder.ToString(); + } + + #endregion + } +} diff --git a/JackTheEnumRipper/Core/Project.cs b/JackTheEnumRipper/Core/Project.cs new file mode 100644 index 0000000..db209e1 --- /dev/null +++ b/JackTheEnumRipper/Core/Project.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; + +namespace JackTheEnumRipper.Core +{ + public static class Project + { + public static Assembly Assembly { get; } = Assembly.GetExecutingAssembly(); + + public static string BasePath { get; } = Directory.GetCurrentDirectory(); + + public static string CompileTimeEnvironment + { + get + { +#if DEBUG + return Environments.Development; +#else + return Environments.Production; +#endif + } + } + + public static string Name + { + get + { + return Assembly.GetName().Name ?? string.Empty; + } + } + + public static string Description + { + get + { + return Assembly.GetCustomAttribute()?.Description ?? string.Empty; + } + } + + public static string Version + { + get + { + return Assembly.GetName().Version?.ToString() ?? string.Empty; + } + } + } +} diff --git a/JackTheEnumRipper/Core/Startup.cs b/JackTheEnumRipper/Core/Startup.cs new file mode 100644 index 0000000..c516c2b --- /dev/null +++ b/JackTheEnumRipper/Core/Startup.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using JackTheEnumRipper.Factories; +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; +using JackTheEnumRipper.Serializer; +using JackTheEnumRipper.Services; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using NLog.Extensions.Logging; + +using Serializer; + +using JsonSerializer = Serializer.JsonSerializer; + +namespace JackTheEnumRipper.Core +{ + public static class Startup + { + public static IConfigurationBuilder ConfigureAppBuilder(this IConfigurationBuilder builder, IHostEnvironment environment) + { + return builder.SetBasePath(environment.ContentRootPath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + } + + public static IServiceCollection ConfigureAppServices(this IServiceCollection services, IHostEnvironment envionment, IConfigurationRoot configurationRoot) + { + services.AddSingleton(envionment); + + services + .AddOptions() + .Bind(configurationRoot.GetRequiredSection(nameof(AppSettings))) + .Validate(option => + { + if (!Encoding.Default.IsValidEncoding(option.Encoding)) + { + return false; + } + + return true; + }) + .ValidateOnStart(); + + services.AddLogging(logBuilder => + { + logBuilder.ClearProviders(); + logBuilder.SetMinimumLevel(string.Equals(envionment.EnvironmentName, Environments.Development) ? LogLevel.Debug : LogLevel.Error); + logBuilder.AddNLog(); + }); + + services.AddSerializerFactory(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + public static void AddSerializerFactory(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddSingleton>>(x => () => x.GetService>()!); + + services.AddSingleton(); + } + } +} diff --git a/JackTheEnumRipper/Core/Utils.cs b/JackTheEnumRipper/Core/Utils.cs new file mode 100644 index 0000000..3ac6c70 --- /dev/null +++ b/JackTheEnumRipper/Core/Utils.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using JackTheEnumRipper.Models; + +using Mono.Cecil; + +namespace JackTheEnumRipper.Core +{ + public class Utils + { + public static IEnumerable ParseEnum(IEnumerable types) + { + return types.Select(x => new AbstractEnum + { + Namespace = x.Namespace, + IsPublic = x.IsPublic, + Name = x.Name, + Type = x.Fields.Select(y => y.FieldType.Name).First(), + Fields = x.Fields.Skip(1).Select(y => new AbstractField { Name = y.Name, Value = y.Constant }) + }); + } + + public static string GetExtension(Format format) + { + return format switch + { + Format.CSharp => ".cs", + Format.Ini => ".ini", + Format.Json => ".json", + Format.Php => ".php", + Format.Rust => ".rs", + Format.Python => ".py", + _ => throw new NotImplementedException(), + }; + } + } +} diff --git a/JackTheEnumRipper/EnumRipper.cs b/JackTheEnumRipper/EnumRipper.cs deleted file mode 100644 index 8d2d700..0000000 --- a/JackTheEnumRipper/EnumRipper.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Mono.Cecil; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -class EnumRipper -{ - private readonly IEnumWriter _writer; - - public EnumRipper(IEnumWriter writer) - { - _writer = writer; - } - - public void ExtractEnumsFromAssembly(string outputDir, string assemblyPath) - { - try - { - AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath); - Console.WriteLine($"Successfully loaded {assembly.FullName}"); - - foreach (var module in assembly.Modules) - { - foreach (TypeDefinition type in module.Types) - { - ProcessType(type, outputDir, null); - } - } - } - catch (Exception ex) - { - Console.WriteLine($"{ex.GetType()}: {ex.Message}"); - Console.ReadLine(); - } - } - - private void ProcessType(TypeDefinition type, string outputDir, string parentNamespace) - { - string typeNamespace = GetTypeNamespace(type, parentNamespace); - - if (type.IsEnum) - { - WriteEnumToFile(type, outputDir, typeNamespace); - } - - // Recursively process nested types - foreach (var nestedType in type.NestedTypes) - { - ProcessType(nestedType, outputDir, typeNamespace); - } - } - - private string GetTypeNamespace(TypeDefinition type, string parentNamespace) - { - string namespacePath; - if (type.IsNested) // Build the namespace from the parent if the type is nested, otherwise use the type's namespace - { - namespacePath = $"{parentNamespace}.{type.DeclaringType.Name}.{type.Name}"; - } - else - { - namespacePath = (!string.IsNullOrEmpty(type.Namespace) ? type.Namespace : parentNamespace); - } - - return namespacePath?.Replace(".", Path.DirectorySeparatorChar.ToString()); - } - - private void WriteEnumToFile(TypeDefinition enumType, string outputDir, string typeNamespace) - { - // Construct the full path using the namespace (and possibly nested class names) - var folderPath = Path.Combine(outputDir, typeNamespace); - Directory.CreateDirectory(folderPath); - - var fileName = $"{enumType.Name}"; - var fullPath = Path.Combine(folderPath, fileName); - var enumValues = GetEnumValues(enumType); - - _writer.WriteEnum(enumType, enumValues, fullPath); - Console.WriteLine($"Enum: {typeNamespace}{Path.DirectorySeparatorChar}{fileName}"); - } - - private IEnumerable<(string Name, object Value)> GetEnumValues(TypeDefinition enumType) - { - // First, determine the underlying type of the enum - var underlyingType = enumType.Fields.FirstOrDefault(f => f.Name.Equals("value__"))?.FieldType; - if (underlyingType == null) - { - yield break; // not sure if this is possible, but just in case - } - - var fields = enumType.Fields.Where(f => f.IsStatic && f.HasConstant); - foreach (var field in fields) - { - yield return (field.Name, field.Constant); - } - } -} diff --git a/JackTheEnumRipper/Factories/SerializerFactory.cs b/JackTheEnumRipper/Factories/SerializerFactory.cs new file mode 100644 index 0000000..67e491b --- /dev/null +++ b/JackTheEnumRipper/Factories/SerializerFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +namespace JackTheEnumRipper.Factories +{ + public class SerializerFactory : ISerializerFactory + { + public IEnumerable Serializers { get; } + + public SerializerFactory(Func> factory) + { + this.Serializers = factory(); + } + + public ISerializer? Create(Format format) + { + return this.Serializers.FirstOrDefault(x => x.Format == format); + } + } +} diff --git a/JackTheEnumRipper/Interfaces/IExtractorService.cs b/JackTheEnumRipper/Interfaces/IExtractorService.cs new file mode 100644 index 0000000..e2c8e67 --- /dev/null +++ b/JackTheEnumRipper/Interfaces/IExtractorService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +using Mono.Cecil; + +namespace JackTheEnumRipper.Interfaces +{ + public interface IExtractorService + { + public IEnumerable ExtractEnums(string path); + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Interfaces/ISerializer.cs b/JackTheEnumRipper/Interfaces/ISerializer.cs new file mode 100644 index 0000000..d90e8e1 --- /dev/null +++ b/JackTheEnumRipper/Interfaces/ISerializer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +using JackTheEnumRipper.Models; + +namespace JackTheEnumRipper.Interfaces +{ + public interface ISerializer + { + public Format Format { get; } + + public void Serialize(IEnumerable enums, string path); + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Interfaces/ISerializerFactory.cs b/JackTheEnumRipper/Interfaces/ISerializerFactory.cs new file mode 100644 index 0000000..4ffa164 --- /dev/null +++ b/JackTheEnumRipper/Interfaces/ISerializerFactory.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +using JackTheEnumRipper.Models; + +namespace JackTheEnumRipper.Interfaces +{ + public interface ISerializerFactory + { + public IEnumerable Serializers { get; } + + public ISerializer? Create(Format format); + } +} diff --git a/JackTheEnumRipper/Interfaces/ISerializerService.cs b/JackTheEnumRipper/Interfaces/ISerializerService.cs new file mode 100644 index 0000000..9a69cf8 --- /dev/null +++ b/JackTheEnumRipper/Interfaces/ISerializerService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +using JackTheEnumRipper.Models; + +namespace JackTheEnumRipper.Interfaces +{ + public interface ISerializerService + { + public void Serialize(Format format, string assemblyPath, string filePath); + + public IEnumerable GetAvailableFormats(); + } +} diff --git a/JackTheEnumRipper/JackTheEnumRipper.csproj b/JackTheEnumRipper/JackTheEnumRipper.csproj index 4ddff5e..19f2d51 100644 --- a/JackTheEnumRipper/JackTheEnumRipper.csproj +++ b/JackTheEnumRipper/JackTheEnumRipper.csproj @@ -2,11 +2,35 @@ Exe - net8.0;net481 - AnyCPU;x86;x64 - Program + net8.0 + AnyCPU;x64 + JackTheEnumRipper.Program + true + enable + disable + + 1.1.0.0 + JackTheEnumRipper + Tool to extract enums from .NET assemblies and export them into various formats. + git + https://github.com/tolik518/JackTheEnumRipper + LICENSE + README.md jacktheenumripper_icon.ico - True + en-US + + + + + Release + Exe + x64 + win-x64 + none + false + true + Link + true @@ -14,7 +38,28 @@ + + True + \ + + + True + \ + + + Always + + + + + + + + + + + diff --git a/JackTheEnumRipper/Models/AbstractEnum.cs b/JackTheEnumRipper/Models/AbstractEnum.cs new file mode 100644 index 0000000..38c994d --- /dev/null +++ b/JackTheEnumRipper/Models/AbstractEnum.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JackTheEnumRipper.Models +{ + public readonly record struct AbstractEnum + { + public required string Namespace { get; init; } + + public required bool IsPublic { get; init; } + + public required string Name { get; init; } + + public required string Type { get; init; } + + public required IEnumerable Fields { get; init; } + } +} diff --git a/JackTheEnumRipper/Models/AbstractField.cs b/JackTheEnumRipper/Models/AbstractField.cs new file mode 100644 index 0000000..e2b32c6 --- /dev/null +++ b/JackTheEnumRipper/Models/AbstractField.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JackTheEnumRipper.Models +{ + public readonly record struct AbstractField + { + public required string Name { get; init; } + + public required object Value { get; init; } + } +} diff --git a/JackTheEnumRipper/Models/AppSettings.cs b/JackTheEnumRipper/Models/AppSettings.cs new file mode 100644 index 0000000..9fdf74b --- /dev/null +++ b/JackTheEnumRipper/Models/AppSettings.cs @@ -0,0 +1,11 @@ +namespace JackTheEnumRipper.Models +{ + public record AppSettings + { + public required string Encoding { get; set; } + + public string? Comment { get; set; } + + public string? Indentation { get; set; } + } +} diff --git a/JackTheEnumRipper/Models/Format.cs b/JackTheEnumRipper/Models/Format.cs new file mode 100644 index 0000000..c03f689 --- /dev/null +++ b/JackTheEnumRipper/Models/Format.cs @@ -0,0 +1,12 @@ +namespace JackTheEnumRipper.Models +{ + public enum Format + { + CSharp, + Ini, + Json, + Php, + Rust, + Python + } +} diff --git a/JackTheEnumRipper/Models/Section.cs b/JackTheEnumRipper/Models/Section.cs new file mode 100644 index 0000000..5cd326b --- /dev/null +++ b/JackTheEnumRipper/Models/Section.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace JackTheEnumRipper.Models +{ + public readonly record struct Section + { + public string Comment { get; init; } + + public required string Name { get; init; } + + public required IEnumerable Settings { get; init; } + + public bool IsGlobal { get; init; } + } +} diff --git a/JackTheEnumRipper/Models/Setting.cs b/JackTheEnumRipper/Models/Setting.cs new file mode 100644 index 0000000..910bc7b --- /dev/null +++ b/JackTheEnumRipper/Models/Setting.cs @@ -0,0 +1,11 @@ +namespace JackTheEnumRipper.Models +{ + public readonly record struct Setting + { + public string Comment { get; init; } + + public required string Name { get; init; } + + public required object Value { get; init; } + } +} diff --git a/JackTheEnumRipper/Program.cs b/JackTheEnumRipper/Program.cs index 0903393..26f1c16 100644 --- a/JackTheEnumRipper/Program.cs +++ b/JackTheEnumRipper/Program.cs @@ -1,142 +1,134 @@ -using Mono.Cecil; -using System; -using System.Collections.Generic; -using System.Globalization; +using System; using System.IO; -using System.Linq; -class Program -{ - private static string version = "1.1.0"; - static void Main(string[] args) - { - // print the version and exit - could be useful when using with other tools - if (args.Length == 1 && (args[0] == "--version" || args[0] == "-v")) - { - Console.WriteLine(version); - return; - } +using JackTheEnumRipper.Core; +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; - // print the supported formats and exit, also useful when using with other tools - if (args.Length == 1 && (args[0] == "--formats" || args[0] == "-f")) - { - Console.WriteLine(GetAvailableWritersAsString(prefix: false)); - return; - } +using McMaster.Extensions.CommandLineUtils; - Console.Title = $"JackTheEnumRipper v{version}"; - PrintBanner(); - - if (args.Length == 0 || args.Contains("--help") || args.Contains("-h")) - { - Console.WriteLine("Usage: JackTheEnumRipper "); - Console.WriteLine(" : The output format. Supported formats: " + GetAvailableWritersAsString(prefix: true)); - Console.ReadLine(); - return; - } - - string assemblyPath = args[0]; - if (!File.Exists(assemblyPath)) - { - Console.WriteLine($"File not found: {assemblyPath}"); - return; - } +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.Internal; - string formatArg = args.Length > 1 ? args[1] : "--csharp"; // Default to csharp if no format is provided - if (!formatArg.StartsWith("--")) - { - Console.WriteLine("Invalid format. Use --format. Example: --csharp"); - return; - } +using NLog; +using NLog.Extensions.Logging; - string format = formatArg.Substring(2).ToLower(); - ReadAssemblyAndExtractEnums(format, assemblyPath); - } +using ILogger = NLog.Logger; - private static void ReadAssemblyAndExtractEnums(string format, string assemblyPath) +namespace JackTheEnumRipper +{ + public class Program { - try - { - var assembly = AssemblyDefinition.ReadAssembly(assemblyPath); - var outputDir = Path.Combine( - Path.GetDirectoryName(assemblyPath), - $"Enums.{assembly.Name.Name}" - ); - Directory.CreateDirectory(outputDir); - - var writer = GetWriterForFormat(format, outputDir); - if (writer == null) - { - Console.WriteLine($"No writer found for format: {format}"); - Console.ReadLine(); - return; - } + private readonly ILogger? _logger; - var ripper = new EnumRipper(writer); - ripper.ExtractEnumsFromAssembly(outputDir, assemblyPath); - Console.WriteLine($"Output directory: \"{outputDir}\""); - Console.WriteLine("Operation completed"); - Console.ReadLine(); - } - catch (Exception ex) - { - Console.WriteLine($"No access to given file or the file is not written using .Net"); - Console.WriteLine($"{ex.GetType()}: {ex.Message}"); - Console.ReadLine(); - } - } + private readonly IHostEnvironment _environment; - private static IEnumWriter GetWriterForFormat(string format, string outputDir) - { - var writerTypeName = $"{CultureInfo.CurrentCulture.TextInfo.ToTitleCase(format)}Writer"; - var writerType = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => a.GetTypes()) - .FirstOrDefault(t => typeof(IEnumWriter).IsAssignableFrom(t) && !t.IsInterface && t.Name.Equals(writerTypeName, StringComparison.OrdinalIgnoreCase)); + private readonly IServiceProvider? _serviceProvider; + + private readonly IConfigurationRoot? _configurationRoot; - if (writerType == null) + public Program() { - return null; - } + this._environment = new HostingEnvironment + { + ApplicationName = Project.Name, + EnvironmentName = Project.CompileTimeEnvironment, + ContentRootPath = Project.BasePath + }; - return (IEnumWriter)Activator.CreateInstance(writerType, new object[] { outputDir }); - } + this._configurationRoot = new ConfigurationBuilder() + .ConfigureAppBuilder(this._environment) + .Build(); - private static IEnumerable GetAvailableWriters() - { - return AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => a.GetTypes()) - .Where(t => typeof(IEnumWriter).IsAssignableFrom(t) && !t.IsInterface) - .Select(t => t.Name.Replace("Writer", "").ToLower()); - } + this._serviceProvider = new ServiceCollection() + .ConfigureAppServices(this._environment, this._configurationRoot) + .BuildServiceProvider(); - private static string GetAvailableWritersAsString(bool prefix) - { - if (prefix) + LogManager.Configuration = new NLogLoggingConfiguration(this._configurationRoot.GetSection("NLog")); + this._logger = LogManager.GetCurrentClassLogger(); + } + public static void Main(string[] args) { - return string.Join(", ", GetAvailableWriters().Select(f => $"--{f}")); + var app = new Program(); + app.Run(args); } - return string.Join(", ", GetAvailableWriters()); - } - - private static void PrintBanner() - { - Console.WriteLine(" "); - Console.WriteLine(" ▄██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██▄ "); - Console.WriteLine(" █▀ ▀█ "); - Console.WriteLine(" █ ▀███ ▄ ▄▄ ██ ▄ T ▄██▀▀▀▀ █▄ ██ ██ ▐█ ██▄ ▄██ █ "); - Console.WriteLine(" █ ██▌ ▄█▀█▄ ▄███▀█▄ ██▄██▀ H ███▄▄▄ ███▄ ██ ██ ▐█ ████▄████ █ "); - Console.WriteLine(" █ ▄▄ ██▌▄█████▄ ███▄ ██▀█▄ E ███▀▀ ██▌▀███ ██▄▐█ ███ ██ ██ █ "); - Console.WriteLine(" █ ███▄███▌█▀ ▀█▀ ▀████▀ ▀█ ▀██ ▀██████ ██▌ ▀█ ▀███▀ ██▀ ██ █ "); - Console.WriteLine(" █ ▀▀▀▀▀▀ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄ █ "); - Console.WriteLine(" █ ███▀▀███ ▄▄ ███▀▀███ ███▀▀███ ████▀▀▀ ███▀▀███ █ "); - Console.WriteLine(" █ ███ ▄█▀ ▌ ███▄▄██▀ ███▄▄██▀ ██████▀ ███ ▄█▀ █ "); - Console.WriteLine(" █ ███▀▀██▄ ██ ███▀▀ ███▀▀ ███▌ ███▀▀██▄ █ "); - Console.WriteLine(" █ ▄▄ ▀█ █▀▀▄█▀▄███▄ ▄███▄ ▄████████▄▀██▌ █▀ ▄▄ █ "); - Console.WriteLine(" █ ████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄████ █ "); - Console.WriteLine(" ██▄ ▀▀ ▀▀ ▄██ "); - Console.WriteLine(" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ "); - Console.WriteLine(" "); + public void Run(string[] args) + { + try + { + var cli = new CommandLineApplication + { + Name = Project.Name, + Description = Project.Description, + }; + + cli.HelpOption(inherited: true); + var version = cli.Option("-v|--version", "display program version and exit", CommandOptionType.NoValue); + + cli.Command("export", exportCommand => + { + exportCommand.Description = "provide one or more export format"; + var listOption = exportCommand.Option("--list", "list all available export formats and exit", CommandOptionType.NoValue); + var pathOption = exportCommand.Option("-p|--path", "path to assembly", CommandOptionType.SingleValue); + var formatOption = exportCommand.Option("-f|--format", "the name of a format writer", CommandOptionType.SingleValue, option => + { + option.DefaultValue = Enum.GetName(Format.CSharp)?.ToLower(); + }); + + exportCommand.OnExecute(() => + { + if (listOption.HasValue()) + { + var serializerService = this._serviceProvider?.GetService(); + var availableFormats = serializerService?.GetAvailableFormats(); + Console.WriteLine(string.Join(",", availableFormats!)); + Environment.Exit(0); + } + + if (pathOption.HasValue()) + { + string? requestedFormat = formatOption.Value(); + bool valid = Enum.TryParse(requestedFormat, ignoreCase: true, out Format format); + + if (!valid) throw new ArgumentException("invalid format type", nameof(requestedFormat)); + + string filePath = Path.Join(Project.BasePath, $"enum{Utils.GetExtension(format)}"); + + var serializerService = this._serviceProvider?.GetService(); + serializerService?.Serialize(format, pathOption.Value()!, filePath); + Environment.Exit(0); + } + }); + }); + + cli.OnExecute(() => + { + if (version.HasValue()) + { + Console.WriteLine($"{cli.Name}, version {Project.Version}"); + } + else + { + cli.ShowHelp(); + } + }); + + _ = cli.Execute(args); + } + catch (Exception exception) + { + _logger?.Error(exception); + Console.Error.WriteLine("invalid arguments"); + } + finally + { + LogManager.Shutdown(); + Environment.Exit(0); + } + } } -} \ No newline at end of file +} diff --git a/JackTheEnumRipper/Properties/launchSettings.json b/JackTheEnumRipper/Properties/launchSettings.json index b9ca80f..05ecc4f 100644 --- a/JackTheEnumRipper/Properties/launchSettings.json +++ b/JackTheEnumRipper/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "JackTheEnumRipper": { "commandName": "Project", - "commandLineArgs": "--help" + "commandLineArgs": "export --path .\\JackTheEnumRipper.dll --format csharp" } } } \ No newline at end of file diff --git a/JackTheEnumRipper/Serializer/CSharpSerializer.cs b/JackTheEnumRipper/Serializer/CSharpSerializer.cs new file mode 100644 index 0000000..5e54d8d --- /dev/null +++ b/JackTheEnumRipper/Serializer/CSharpSerializer.cs @@ -0,0 +1,95 @@ +using System; +using System.CodeDom; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +using Microsoft.Extensions.Options; + +namespace Serializer +{ + public class CSharpSerializer(IOptions appSettings) : ISerializer + { + public Format Format => Format.CSharp; + + private readonly AppSettings _appSettings = appSettings.Value; + + private static CodeCompileUnit GenerateEnumCode(IEnumerable enums) + { + var compileUnit = new CodeCompileUnit(); + + foreach (IGrouping enumGroup in enums.GroupBy(x => x.Namespace)) + { + using IEnumerator enumerator = enumGroup.GetEnumerator(); + + while (enumerator.MoveNext()) + { + AbstractEnum abstractEnum = enumerator.Current; + var @namespace = new CodeNamespace(abstractEnum.Namespace); + + var @enum = new CodeTypeDeclaration(abstractEnum.Name) + { + IsEnum = true + }; + + @enum.Attributes |= abstractEnum.IsPublic ? MemberAttributes.Public : MemberAttributes.Assembly; + + foreach (AbstractField field in abstractEnum.Fields) + { + @enum.Members.Add(new CodeMemberField(abstractEnum.Type, field.Name) + { + InitExpression = new CodePrimitiveExpression(field.Value) + }); + } + + @namespace.Types.Add(@enum); + compileUnit.Namespaces.Add(@namespace); + } + } + + return compileUnit; + } + + private static void ReplaceTopLevelComment(StringWriter writer, string? newComment) + { + if (string.IsNullOrEmpty(newComment)) return; + + IEnumerable lines = writer + .ToString() + .Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .SkipWhile(line => line.TrimStart().StartsWith("//")); + + writer.GetStringBuilder().Clear(); + writer.WriteLine($"// {newComment}"); + + foreach (string line in lines) + { + writer.WriteLine(line); + } + } + + public void Serialize(IEnumerable enums, string path) + { + var provider = CodeDomProvider.CreateProvider(Enum.GetName(this.Format)); + var compileUnit = GenerateEnumCode(enums); + + var options = new CodeGeneratorOptions + { + BracingStyle = "C", + BlankLinesBetweenMembers = false, + IndentString = this._appSettings.Indentation ?? "\t" + }; + + using StringWriter writer = new(); + var encoding = Encoding.GetEncoding(this._appSettings.Encoding); + provider.GenerateCodeFromCompileUnit(compileUnit, writer, options); + ReplaceTopLevelComment(writer, this._appSettings.Comment); + File.WriteAllText(path, writer.ToString(), encoding); + } + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Serializer/IniSerializer.cs b/JackTheEnumRipper/Serializer/IniSerializer.cs new file mode 100644 index 0000000..87550cd --- /dev/null +++ b/JackTheEnumRipper/Serializer/IniSerializer.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using JackTheEnumRipper.Core; +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +using Microsoft.Extensions.Options; + +namespace Serializer +{ + public class IniSerializer(IOptions appSettings) : ISerializer + { + public Format Format => Format.Ini; + + private readonly AppSettings _appSettings = appSettings.Value; + + public void Serialize(IEnumerable enums, string path) + { + var sections = enums.Select(@enum => new Section + { + Comment = $"scope={(@enum.IsPublic ? "public" : "internal")},type={@enum.Type}", + Name = $"{@enum.Namespace}.{@enum.Name}", + Settings = @enum.Fields.Select(field => new Setting + { + Name = field.Name, + Value = field.Value, + }), + }); ; + + var encoding = Encoding.GetEncoding(this._appSettings.Encoding); + var ini = new Ini(sections, this._appSettings.Comment, commentSymbol: '#'); + File.WriteAllText(path, ini.ToString(), encoding); + } + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Serializer/JsonSerializer.cs b/JackTheEnumRipper/Serializer/JsonSerializer.cs new file mode 100644 index 0000000..3e3e088 --- /dev/null +++ b/JackTheEnumRipper/Serializer/JsonSerializer.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +using Microsoft.Extensions.Options; + +using Newtonsoft.Json; + +namespace Serializer +{ + public class JsonSerializer(IOptions appSettings) : ISerializer + { + public Format Format => Format.Json; + + private readonly AppSettings _appSettings = appSettings.Value; + + public void Serialize(IEnumerable enums, string path) + { + var encoding = Encoding.GetEncoding(this._appSettings.Encoding); + string json = JsonConvert.SerializeObject(enums, Formatting.Indented); + File.WriteAllText(path, json, encoding); + } + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Serializer/PhpSerializer.cs b/JackTheEnumRipper/Serializer/PhpSerializer.cs new file mode 100644 index 0000000..d123f47 --- /dev/null +++ b/JackTheEnumRipper/Serializer/PhpSerializer.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Text; + +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +using Microsoft.Extensions.Options; + +namespace Serializer +{ + public class PhpSerializer(IOptions appSettings) : ISerializer + { + public Format Format => Format.Php; + + private readonly AppSettings _appSettings = appSettings.Value; + + public void Serialize(IEnumerable enums, string path) + { + var builder = new StringBuilder(); + string identation = this._appSettings.Indentation ?? "\t"; + + builder.AppendLine(" x.Namespace)) + { + using IEnumerator enumerator = group.GetEnumerator(); + string @namespace = group.Key.Replace(".", "\\"); + builder.AppendLine($"namespace {@namespace} {{"); + + while (enumerator.MoveNext()) + { + AbstractEnum @enum = enumerator.Current; + builder.AppendLine($"{identation}// scope={(@enum.IsPublic ? "public" : "internal")},type={@enum.Type}"); + builder.AppendLine($"{identation}class {@enum.Name} {{"); + + foreach (AbstractField field in @enum.Fields) + { + builder.AppendLine($"{identation}{identation}const {field.Name} = {field.Value};"); + } + + builder.AppendLine($"{identation}}}"); + } + + builder.AppendLine("}"); + } + + builder.AppendLine(); + builder.AppendLine("?>"); + + var encoding = Encoding.GetEncoding(this._appSettings.Encoding); + string content = builder.ToString(); + File.WriteAllText(path, content, encoding); + } + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Serializer/PythonSerializer.cs b/JackTheEnumRipper/Serializer/PythonSerializer.cs new file mode 100644 index 0000000..3f83dec --- /dev/null +++ b/JackTheEnumRipper/Serializer/PythonSerializer.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +using Microsoft.Extensions.Options; + +namespace JackTheEnumRipper.Serializer +{ + public class PythonSerializer(IOptions appSettings) : ISerializer + { + public Format Format => Format.Python; + + private readonly AppSettings _appSettings = appSettings.Value; + + public void Serialize(IEnumerable enums, string path) + { + var builder = new StringBuilder(); + string identation = this._appSettings.Indentation ?? "\t"; + + if (!string.IsNullOrEmpty(this._appSettings.Comment)) + { + builder.AppendLine($"# {this._appSettings.Comment}{Environment.NewLine}"); + } + + builder.AppendLine($"from enum import Enum, unique"); + + foreach (AbstractEnum @enum in enums) + { + builder.AppendLine(Environment.NewLine); + builder.AppendLine($"# namespace={@enum.Namespace},scope={(@enum.IsPublic ? "public" : "internal")},type={@enum.Type}"); + builder.AppendLine($"@unique"); + builder.AppendLine($"class {@enum.Name}(Enum):"); + + foreach (AbstractField field in @enum.Fields) + { + builder.AppendLine($"{identation}{field.Name.ToUpper()} = {field.Value}"); + } + } + + var encoding = Encoding.GetEncoding(this._appSettings.Encoding); + string content = builder.ToString(); + File.WriteAllText(path, content, encoding); + } + } +} diff --git a/JackTheEnumRipper/Serializer/RustSerializer.cs b/JackTheEnumRipper/Serializer/RustSerializer.cs new file mode 100644 index 0000000..e82b553 --- /dev/null +++ b/JackTheEnumRipper/Serializer/RustSerializer.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using JackTheEnumRipper.Core; +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +using Microsoft.Extensions.Options; + +namespace Serializer +{ + public class RustSerializer(IOptions appSettings) : ISerializer + { + public Format Format => Format.Rust; + + private readonly AppSettings _appSettings = appSettings.Value; + + public void Serialize(IEnumerable enums, string path) + { + var builder = new StringBuilder(); + string identation = this._appSettings.Indentation ?? "\t"; + + if (!string.IsNullOrEmpty(this._appSettings.Comment)) + { + builder.AppendLine($"// {this._appSettings.Comment}{Environment.NewLine}"); + } + + foreach (IGrouping group in enums.GroupBy(x => x.Namespace)) + { + using IEnumerator enumerator = group.GetEnumerator(); + string[] namespaces = group.Key.Split('.'); + int maxIdentation = namespaces.Length; + + while (enumerator.MoveNext()) + { + AbstractEnum @enum = enumerator.Current; + + for (int i = 0; i < maxIdentation; i++) + { + builder.AppendLine($"{(i == 0 ? string.Empty : identation.Repeat(i))}mod {@namespaces[i]} {{"); + } + + string enumIdentation = identation.Repeat(maxIdentation); + string fieldIdentation = identation.Repeat(maxIdentation + 1); + + builder.AppendLine($"{enumIdentation}// type={@enum.Type}"); + builder.AppendLine($"{enumIdentation}{(@enum.IsPublic ? "pub" : string.Empty)} enum {@enum.Name} {{"); + + foreach (AbstractField field in @enum.Fields) + { + builder.AppendLine($"{fieldIdentation}{field.Name} = {field.Value},"); + } + + for (int i = maxIdentation; i >= 0; i--) + { + builder.AppendLine($"{(i == 0 ? string.Empty : identation.Repeat(i))}}}"); + } + } + } + + var encoding = Encoding.GetEncoding(this._appSettings.Encoding); + string content = builder.ToString(); + File.WriteAllText(path, content, encoding); + } + } +} \ No newline at end of file diff --git a/JackTheEnumRipper/Services/ExtractorService.cs b/JackTheEnumRipper/Services/ExtractorService.cs new file mode 100644 index 0000000..c76bcf3 --- /dev/null +++ b/JackTheEnumRipper/Services/ExtractorService.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using JackTheEnumRipper.Interfaces; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using Mono.Cecil; + +namespace JackTheEnumRipper.Services +{ + public class ExtractorService(ILogger logger, IHostEnvironment environment) : IExtractorService + { + private readonly ILogger _logger = logger; + private readonly IHostEnvironment _environment = environment; + + public IEnumerable ExtractEnums(string path) + { + string fullPath = Path.Join(this._environment.ContentRootPath, path); + + if (!Path.Exists(fullPath)) + { + this._logger.LogError("Assembly Path: {Path}", fullPath); + throw new DirectoryNotFoundException("The ExtractorService suspends its operation because it cannot locate the path to an assembly."); + } + + var assemblyDefinition = AssemblyDefinition.ReadAssembly(fullPath); + + return assemblyDefinition.Modules + .SelectMany(x => x.Types) + .Where(x => x.IsEnum); + } + } +} diff --git a/JackTheEnumRipper/Services/SerializerService.cs b/JackTheEnumRipper/Services/SerializerService.cs new file mode 100644 index 0000000..1f228f9 --- /dev/null +++ b/JackTheEnumRipper/Services/SerializerService.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using JackTheEnumRipper.Core; +using JackTheEnumRipper.Interfaces; +using JackTheEnumRipper.Models; + +namespace JackTheEnumRipper.Services +{ + public class SerializerService(ISerializerFactory serializerFactory, IExtractorService extractorService) : ISerializerService + { + private readonly ISerializerFactory _serializerFactory = serializerFactory; + private readonly IExtractorService _extractorService = extractorService; + + public void Serialize(Format format, string assemblyPath, string filePath) + { + var serializer = this._serializerFactory.Create(format); + + ArgumentNullException.ThrowIfNull(serializer, nameof(serializer)); + + var types = this._extractorService.ExtractEnums(assemblyPath); + var enums = Utils.ParseEnum(types); + serializer.Serialize(enums, filePath); + } + + public IEnumerable GetAvailableFormats() + { + return this._serializerFactory.Serializers.Select(x => Enum.GetName(x.Format)?.ToLower()!); + } + } +} diff --git a/JackTheEnumRipper/Writer/CSharpWriter.cs b/JackTheEnumRipper/Writer/CSharpWriter.cs deleted file mode 100644 index e5114dd..0000000 --- a/JackTheEnumRipper/Writer/CSharpWriter.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Mono.Cecil; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -class CSharpWriter : IEnumWriter -{ - private readonly string _outputDir; - - public CSharpWriter(string outputDir) - { - _outputDir = outputDir; - } - - void IEnumWriter.WriteEnum(TypeDefinition enumType, IEnumerable<(string Name, object Value)> enumValues, string fileName) - { - var filePath = Path.Combine(_outputDir, $"{fileName}.cs"); - var fileDirectory = Path.GetDirectoryName(filePath); - Directory.CreateDirectory(fileDirectory); - - using (StreamWriter file = new StreamWriter(filePath)) - { - var underlyingType = enumType.Fields.FirstOrDefault(f => f.Name.Equals("value__"))?.FieldType.Name; - file.WriteLine("// Generated by JackTheEnumRipper"); - file.WriteLine("using System;"); - file.WriteLine(); - file.WriteLine($"public enum {enumType.Name} : {underlyingType}"); - file.WriteLine("{"); - - foreach (var enumField in enumValues) - { - WriteValue(file, enumField.Name, enumField.Value); - } - - file.WriteLine("}"); - } - } - - private void WriteValue(StreamWriter file, string name, object value) - { - file.WriteLine($"\t{name} = {value},"); - } -} diff --git a/JackTheEnumRipper/Writer/IEnumWriter.cs b/JackTheEnumRipper/Writer/IEnumWriter.cs deleted file mode 100644 index 4768155..0000000 --- a/JackTheEnumRipper/Writer/IEnumWriter.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Mono.Cecil; -using System.Collections.Generic; - -interface IEnumWriter -{ - void WriteEnum(TypeDefinition enumType, IEnumerable<(string Name, object Value)> enumValues, string fileName); -} diff --git a/JackTheEnumRipper/Writer/IniWriter.cs b/JackTheEnumRipper/Writer/IniWriter.cs deleted file mode 100644 index b8db745..0000000 --- a/JackTheEnumRipper/Writer/IniWriter.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Mono.Cecil; -using System.Collections.Generic; -using System.IO; - -class IniWriter : IEnumWriter -{ - private readonly string _outputDir; - - public IniWriter(string outputDir) - { - _outputDir = outputDir; - } - - void IEnumWriter.WriteEnum(TypeDefinition enumType, IEnumerable<(string Name, object Value)> enumValues, string fileName) - { - var filePath = Path.Combine(_outputDir, $"{fileName}.ini"); - var fileDirectory = Path.GetDirectoryName(filePath); - Directory.CreateDirectory(fileDirectory); - - using (StreamWriter file = new StreamWriter(filePath)) - { - // Write the section header for the enum - file.WriteLine($"[{enumType.Name}]"); - - // Iterate over fields to get enum values - foreach (var enumField in enumValues) - { - WriteValue(file, enumField.Name, enumField.Value); - } - } - } - - private void WriteValue(StreamWriter file, string name, object value) - { - file.WriteLine($"{name} = {value}"); - } -} diff --git a/JackTheEnumRipper/Writer/JsonWriter.cs b/JackTheEnumRipper/Writer/JsonWriter.cs deleted file mode 100644 index a6e2838..0000000 --- a/JackTheEnumRipper/Writer/JsonWriter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Mono.Cecil; -using System.Collections.Generic; -using System.IO; - -class JsonWriter : IEnumWriter -{ - private readonly string _outputDir; - - public JsonWriter(string outputDir) - { - _outputDir = outputDir; - } - - void IEnumWriter.WriteEnum(TypeDefinition enumType, IEnumerable<(string Name, object Value)> enumValues, string fileName) - { - var filePath = Path.Combine(_outputDir, $"{fileName}.json"); - var fileDirectory = Path.GetDirectoryName(filePath); - Directory.CreateDirectory(fileDirectory); - - using (StreamWriter file = new StreamWriter(filePath)) - { - file.WriteLine("{"); - file.WriteLine($" \"{enumType.Name}\": {{"); - - foreach (var enumField in enumValues) - { - WriteValue(file, enumField.Name, enumField.Value); - } - - file.WriteLine(" }"); - file.WriteLine("}"); - } - } - - private void WriteValue(StreamWriter file, string name, object value) - { - file.WriteLine($" \"{name}\": {value},"); - } -} diff --git a/JackTheEnumRipper/Writer/PhpWriter.cs b/JackTheEnumRipper/Writer/PhpWriter.cs deleted file mode 100644 index 34c1cdb..0000000 --- a/JackTheEnumRipper/Writer/PhpWriter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Mono.Cecil; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; - -class PhpWriter : IEnumWriter -{ - private readonly string _outputDir; - - public PhpWriter(string outputDir) - { - _outputDir = outputDir; - } - - void IEnumWriter.WriteEnum(TypeDefinition enumType, IEnumerable<(string Name, object Value)> enumValues, string fileName) - { - var filePath = Path.Combine(_outputDir, $"{fileName}.php"); - var fileDirectory = Path.GetDirectoryName(filePath); - Directory.CreateDirectory(fileDirectory); - - using (StreamWriter file = new StreamWriter(filePath)) - { - string phpEnumName = ConvertToPascalCase(enumType.Name); - file.WriteLine(" textInfo.ToTitleCase(word.ToLower()))); - } -} diff --git a/JackTheEnumRipper/Writer/RustWriter.cs b/JackTheEnumRipper/Writer/RustWriter.cs deleted file mode 100644 index 28d6e87..0000000 --- a/JackTheEnumRipper/Writer/RustWriter.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Mono.Cecil; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; - -class RustWriter : IEnumWriter -{ - private readonly string _outputDir; - - public RustWriter(string outputDir) - { - _outputDir = outputDir; - } - - void IEnumWriter.WriteEnum(TypeDefinition enumType, IEnumerable<(string Name, object Value)> enumValues, string fileName) - { - var filePath = Path.Combine(_outputDir, $"{fileName}.rs"); - var fileDirectory = Path.GetDirectoryName(filePath); - Directory.CreateDirectory(fileDirectory); - - using (StreamWriter file = new StreamWriter(filePath)) - { - string rustEnumName = ConvertToPascalCase(enumType.Name); - file.WriteLine($"// Generated by JackTheEnumRipper"); - file.WriteLine($"#[repr(C)]"); //TODO: See if repr(C) is best for all cases - file.WriteLine($"enum {rustEnumName} {{"); - - foreach (var enumField in enumValues) - { - string rustVariantName = ConvertToPascalCase(enumField.Name); - WriteValue(file, rustVariantName, enumField.Value); - } - - file.WriteLine("}"); - } - } - - private void WriteValue(StreamWriter file, string name, object value) - { - file.WriteLine($"\t{name} = {value},"); - } - - private string ConvertToPascalCase(string input) - { - // Simple conversion to PascalCase for now; I should consider edge cases and improvements - TextInfo textInfo = CultureInfo.InvariantCulture.TextInfo; - return string.Concat(input.Split(new char[] { '_', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(word => textInfo.ToTitleCase(word.ToLower()))); - } -} diff --git a/JackTheEnumRipper/appsettings.json b/JackTheEnumRipper/appsettings.json new file mode 100644 index 0000000..b835b22 --- /dev/null +++ b/JackTheEnumRipper/appsettings.json @@ -0,0 +1,28 @@ +{ + "AppSettings": { + "Encoding": "utf-8", + "Comment": "This code was generated by JackTheEnumRipper. Changes to this file will be lost if the code is regenerated.", + "Indentation": "\t" + }, + "NLog": { + "extensions": [ + { + "assembly": "NLog.Extensions.Logging" + } + ], + "targets": { + "allFile": { + "type": "File", + "fileName": "C:\\Temp\\JackTheEnumRipper-${shortdate}.log", + "layout": "${longdate} [${uppercase:${level}}] ${message}" + } + }, + "rules": [ + { + "logger": "JackTheEnumRipper.*", + "minLevel": "Debug", + "writeTo": "allFile" + } + ] + } +} diff --git a/JackTheEnumRipper/nuget.config b/JackTheEnumRipper/nuget.config new file mode 100644 index 0000000..de291b7 --- /dev/null +++ b/JackTheEnumRipper/nuget.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/README.md b/README.md index c6a5fc5..6725783 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,16 @@ -

- +

+

Jack the Enum Ripper

-

- Jack the Enum Ripper is a CLI tool designed to extract enums from .NET assemblies and output them in various formats. This tool supports both .NET Framework 4.8 and .NET 8.0. +

+ Jack the Enum Ripper is a CLI tool designed to extract enums from .NET assemblies and output them in various formats.

-## Table of Contents - -- [Features](#features) -- [Getting Started](#getting-started) - - [Usage](#usage) - - [Example](#example) -- [Building](#building) - - [Prerequisites](#prerequisites) - - [Steps](#steps) -- [Contributing](#contributing) -- [License](#license) - ## Features - **Dump Enums**: Load a .NET assembly (.exe or .dll) to dump all enumeration types. -- **Output Formats**: Supports exporting enums into multiple formats, including C#, JSON, INI, PHP, and Rust, adhering to each language's conventions. +- **Output Formats**: Supports exporting enums into multiple formats, including C#, JSON, INI, PHP, Rust, and Python adhering to each language's conventions. ## Getting Started @@ -30,47 +18,38 @@ Run `JackTheEnumRipper.exe` from the command line with the following syntax: +```powershell +JackTheEnumRipper.exe export --path [--format ] ``` -JackTheEnumRipper.exe [format] -``` -_if you have compatibility issues, you can alternatively run JackTheEnumRipper_net481.exe_ -#### Formats +Alternatively, use the `--help` option to read the help manual. -- `--json`: Output enums in JSON format -- `--ini`: Output enums in INI format -- `--php`: Output enums in PHP format -- `--rust`: Output enums in Rust format -- `--cs`: Output enums in C# format +### Supported Formats -If no options are specified, the tool defaults to exporting enums in C# format. +- `json` +- `ini` +- `php` +- `rust` +- `csharp` +- `python` + +A default format or encoding can be configured in the `appsettings.json` configuration file. ### Example To extract enums from `MyExecutable.exe` in Rust format: +```powershell +JackTheEnumRipper.exe export --path ./path/to/MyExecutable.exe --format rust ``` -JackTheEnumRipper.exe path\to\MyExecutable.exe --rust -``` - -This will create a directory named `Enums.MyExecutable` in the same location as `MyExecutable.exe`, containing the extracted enums in Rust format. -You can alternatively just drag and drop the assembly to the `JackTheEnumRipper.exe` +This will command will output a `enum.rs` file in the current working directory. ## Building -### Prerequisites - -- .NET Framework 4.8 SDK or .NET 8.0 SDK, depending on your target environment. -- Recommended: Visual Studio 2019 or newer - -### Steps - -1. Clone the repository or download the source code -2. Open the solution in Visual Studio -3. Build the project for .NET Framework 4.8 or .NET 8.0 as required -4. The `JackTheEnumRipper.exe` executable is generated in `bin/Release` or `bin/Debug` - +```powershell +dotnet publish -c Release -r win-x64 --framework net8.0 --self-contained -o ./bin/publish/release +``` ## Contributing @@ -79,20 +58,3 @@ Contributions are welcome! If you have suggestions for improvements, please fork ## License Jack the Enum Ripper is open-source software licensed under the MIT License. See the `LICENSE` file for more details. - -``` - ▄██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██▄ - █▀ ▀█ - █ ▀███ ▄ ▄▄ ██ ▄ T ▄██▀▀▀▀ █▄ ██ ██ ██ ██▄ ▄██ █ - █ ██ ▄█▀█▄ ▄███▀█▄ ██▄██▀ H ███▄▄▄ ███▄ ██ ██ ██ ████▄████ █ - █ ▄▄ ██ ▄█████▄ ███▄ ██▀█▄ E ███▀▀ ██ ▀███ ██▄██ ███ ██ ██ █ - █ ███▄███ █▀ ▀█▀ ▀████▀ ▀█ ▀██ ▀██████ ▀█ ▀█ ▀███▀ ██▀ ██ █ - █ ▀▀▀▀▀ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄ █ - █ ███▀▀███ ▄▄ ███▀▀███ ███▀▀███ ████▀▀▀ ███▀▀███ █ - █ ███ ▄█▀ ███▄▄██▀ ███▄▄██▀ ██████▀ ███ ▄█▀ █ - █ ███▀▀██▄ ██ ███▀▀ ███▀▀ ███ ███▀▀██▄ █ - █ ▄▄ ▀█ █▀▀▄█▀▄███▄ ▄███▄ ▄████████▄▀██ █▀ ▄▄ █ - █ ████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄████ █ - ██▄ ▀▀ ▀▀ ▄██ - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - ```