mirror of https://github.com/Xpl0itR/protodec.git
Use ConsoleAppFramework for CLI handling
also move Get*Types to CilAssemblyLoader
This commit is contained in:
parent
e94fecce66
commit
2126f61cbf
20
README.md
20
README.md
|
@ -5,17 +5,19 @@ A tool to decompile protobuf classes compiled by [protoc](https://github.com/pro
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
```
|
```
|
||||||
Usage: protodec(.exe) <target_assembly_path> <out_path> [options]
|
Usage: [arguments...] [options...] [-h|--help] [--version]
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
target_assembly_path Either the path to the target assembly or a directory of assemblies, all of which be parsed.
|
[0] <string> Either the path to the target assembly or a directory of assemblies, all of which be parsed.
|
||||||
out_path An existing directory to output into individual files, otherwise output to a single file.
|
[1] <string> An existing directory to output into individual files, otherwise output to a single file.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--debug Drops the minimum log level to Debug.
|
--skip-enums Skip parsing enums and replace references to them with int32. (Optional)
|
||||||
--parse_service_servers Parses gRPC service definitions from server classes.
|
--include-properties-without-non-user-code-attribute Includes properties that aren't decorated with `DebuggerNonUserCode` when parsing. (Optional)
|
||||||
--parse_service_clients Parses gRPC service definitions from client classes.
|
--include-service-methods-without-generated-code-attribute Includes methods that aren't decorated with `GeneratedCode("grpc_csharp_plugin")` when parsing gRPC services. (Optional)
|
||||||
--skip_enums Skip parsing enums and replace references to them with int32.
|
--parse-service-servers Parses gRPC service definitions from server classes. (Optional)
|
||||||
--include_properties_without_non_user_code_attribute Includes properties that aren't decorated with `DebuggerNonUserCode` when parsing.
|
--parse-service-clients Parses gRPC service definitions from client classes. (Optional)
|
||||||
--include_service_methods_without_generated_code_attribute Includes methods that aren't decorated with `GeneratedCode("grpc_csharp_plugin")` when parsing gRPC services.
|
--log-level <LogLevel> Logging severity level. (Default: Information)
|
||||||
```
|
```
|
||||||
|
|
||||||
Limitations
|
Limitations
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0-preview.5.24306.7" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0-preview.6.24327.7" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.0-preview.5.24306.7" />
|
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.0-preview.6.24327.7" />
|
||||||
<PackageReference Include="Xpl0itR.SystemEx" Version="1.2.0" />
|
<PackageReference Include="Xpl0itR.SystemEx" Version="1.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,43 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using LibProtodec.Models.Cil;
|
using LibProtodec.Models.Cil;
|
||||||
|
|
||||||
namespace LibProtodec.Loaders;
|
namespace LibProtodec.Loaders;
|
||||||
|
|
||||||
public abstract class CilAssemblyLoader : IDisposable
|
public abstract class CilAssemblyLoader : IDisposable
|
||||||
{
|
{
|
||||||
private ICilType? _iMessage;
|
|
||||||
private ICilType? _clientBase;
|
|
||||||
private ICilType? _bindServiceMethodAttribute;
|
|
||||||
|
|
||||||
// ReSharper disable once InconsistentNaming
|
|
||||||
public ICilType IMessage =>
|
|
||||||
_iMessage ??= FindType("Google.Protobuf.IMessage", "Google.Protobuf");
|
|
||||||
|
|
||||||
public ICilType ClientBase =>
|
|
||||||
_clientBase ??= FindType("Grpc.Core.ClientBase", "Grpc.Core.Api");
|
|
||||||
|
|
||||||
public ICilType BindServiceMethodAttribute =>
|
|
||||||
_bindServiceMethodAttribute ??= FindType("Grpc.Core.BindServiceMethodAttribute", "Grpc.Core.Api");
|
|
||||||
|
|
||||||
public IReadOnlyList<ICilType> LoadedTypes { get; protected init; }
|
public IReadOnlyList<ICilType> LoadedTypes { get; protected init; }
|
||||||
|
|
||||||
|
public IEnumerable<ICilType> GetProtobufMessageTypes()
|
||||||
|
{
|
||||||
|
ICilType iMessage = FindType("Google.Protobuf.IMessage", "Google.Protobuf");
|
||||||
|
|
||||||
|
return LoadedTypes.Where(
|
||||||
|
type => type is { IsNested: false, IsSealed: true }
|
||||||
|
&& type.Namespace?.StartsWith("Google.Protobuf", StringComparison.Ordinal) != true
|
||||||
|
&& type.IsAssignableTo(iMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ICilType> GetProtobufServiceClientTypes()
|
||||||
|
{
|
||||||
|
ICilType clientBase = FindType("Grpc.Core.ClientBase", "Grpc.Core.Api");
|
||||||
|
|
||||||
|
return LoadedTypes.Where(
|
||||||
|
type => type is { IsNested: true, IsAbstract: false }
|
||||||
|
&& type.IsAssignableTo(clientBase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ICilType> GetProtobufServiceServerTypes()
|
||||||
|
{
|
||||||
|
ICilType bindServiceMethodAttribute = FindType("Grpc.Core.BindServiceMethodAttribute", "Grpc.Core.Api");
|
||||||
|
|
||||||
|
return LoadedTypes.Where(
|
||||||
|
type => type is { IsNested: true, IsAbstract: true, DeclaringType: { IsNested: false, IsSealed: true, IsAbstract: true } }
|
||||||
|
&& type.CustomAttributes.Any(attribute => attribute.Type == bindServiceMethodAttribute));
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Dispose() { }
|
public virtual void Dispose() { }
|
||||||
|
|
||||||
protected abstract ICilType FindType(string typeFullName, string assemblySimpleName);
|
protected abstract ICilType FindType(string typeFullName, string assemblySimpleName);
|
||||||
|
|
|
@ -20,7 +20,7 @@ public sealed class ClrAssemblyLoader : CilAssemblyLoader
|
||||||
{
|
{
|
||||||
public readonly MetadataLoadContext LoadContext;
|
public readonly MetadataLoadContext LoadContext;
|
||||||
|
|
||||||
public ClrAssemblyLoader(string assemblyPath, ILogger? logger = null)
|
public ClrAssemblyLoader(string assemblyPath, ILogger<ClrAssemblyLoader>? logger = null)
|
||||||
{
|
{
|
||||||
bool isFile = File.Exists(assemblyPath);
|
bool isFile = File.Exists(assemblyPath);
|
||||||
string assemblyDir = isFile
|
string assemblyDir = isFile
|
||||||
|
|
|
@ -23,14 +23,14 @@ namespace LibProtodec;
|
||||||
|
|
||||||
public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName);
|
public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName);
|
||||||
|
|
||||||
// ReSharper disable ClassWithVirtualMembersNeverInherited.Global, MemberCanBePrivate.Global, MemberCanBeProtected.Global
|
// ReSharper disable ClassWithVirtualMembersNeverInherited.Global, MemberCanBePrivate.Global, MemberCanBeProtected.Global, PropertyCanBeMadeInitOnly.Global
|
||||||
public class ProtodecContext
|
public class ProtodecContext
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, TopLevel> _parsed = [];
|
private readonly Dictionary<string, TopLevel> _parsed = [];
|
||||||
|
|
||||||
public readonly List<Protobuf> Protobufs = [];
|
public readonly List<Protobuf> Protobufs = [];
|
||||||
|
|
||||||
public ILogger? Logger { get; set; }
|
public ILogger<ProtodecContext>? Logger { get; set; }
|
||||||
|
|
||||||
public NameLookupFunc? NameLookup { get; set; }
|
public NameLookupFunc? NameLookup { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -4,94 +4,96 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.CodeDom.Compiler;
|
using System.CodeDom.Compiler;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using ConsoleAppFramework;
|
||||||
using LibProtodec;
|
using LibProtodec;
|
||||||
using LibProtodec.Loaders;
|
using LibProtodec.Loaders;
|
||||||
using LibProtodec.Models.Cil;
|
using LibProtodec.Models.Cil;
|
||||||
using LibProtodec.Models.Protobuf;
|
using LibProtodec.Models.Protobuf;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
const string indent = " ";
|
ConsoleApp.ConsoleAppBuilder app = ConsoleApp.Create();
|
||||||
const string help = """
|
app.Add<Commands>();
|
||||||
Usage: protodec(.exe) <target_assembly_path> <out_path> [options]
|
app.Run(args);
|
||||||
Arguments:
|
|
||||||
target_assembly_path Either the path to the target assembly or a directory of assemblies, all of which be parsed.
|
|
||||||
out_path An existing directory to output into individual files, otherwise output to a single file.
|
|
||||||
Options:
|
|
||||||
--debug Drops the minimum log level to Debug.
|
|
||||||
--parse_service_servers Parses gRPC service definitions from server classes.
|
|
||||||
--parse_service_clients Parses gRPC service definitions from client classes.
|
|
||||||
--skip_enums Skip parsing enums and replace references to them with int32.
|
|
||||||
--include_properties_without_non_user_code_attribute Includes properties that aren't decorated with `DebuggerNonUserCode` when parsing.
|
|
||||||
--include_service_methods_without_generated_code_attribute Includes methods that aren't decorated with `GeneratedCode("grpc_csharp_plugin")` when parsing gRPC services.
|
|
||||||
""";
|
|
||||||
|
|
||||||
if (args.Length < 2)
|
internal sealed class Commands
|
||||||
{
|
{
|
||||||
Console.WriteLine(help);
|
/// <summary>
|
||||||
return;
|
/// A tool to decompile protobuf classes compiled by protoc, from CIL assemblies back into .proto definitions.
|
||||||
}
|
/// </summary>
|
||||||
|
/// <param name="targetPath">Either the path to the target assembly or a directory of assemblies, all of which be parsed.</param>
|
||||||
string assembly = args[0];
|
/// <param name="outPath">An existing directory to output into individual files, otherwise output to a single file.</param>
|
||||||
string outPath = Path.GetFullPath(args[1]);
|
/// <param name="logLevel">Logging severity level.</param>
|
||||||
ParserOptions options = ParserOptions.None;
|
/// <param name="parseServiceServers">Parses gRPC service definitions from server classes.</param>
|
||||||
LogLevel logLevel = args.Contains("--debug")
|
/// <param name="parseServiceClients">Parses gRPC service definitions from client classes.</param>
|
||||||
? LogLevel.Debug
|
/// <param name="skipEnums">Skip parsing enums and replace references to them with int32.</param>
|
||||||
: LogLevel.Information;
|
/// <param name="includePropertiesWithoutNonUserCodeAttribute">Includes properties that aren't decorated with `DebuggerNonUserCode` when parsing.</param>
|
||||||
|
/// <param name="includeServiceMethodsWithoutGeneratedCodeAttribute">Includes methods that aren't decorated with `GeneratedCode("grpc_csharp_plugin")` when parsing gRPC services.</param>
|
||||||
if (args.Contains("--skip_enums"))
|
[Command("")]
|
||||||
|
public void Root(
|
||||||
|
[Argument] string targetPath,
|
||||||
|
[Argument] string outPath,
|
||||||
|
bool skipEnums,
|
||||||
|
bool includePropertiesWithoutNonUserCodeAttribute,
|
||||||
|
bool includeServiceMethodsWithoutGeneratedCodeAttribute,
|
||||||
|
bool parseServiceServers,
|
||||||
|
bool parseServiceClients,
|
||||||
|
LogLevel logLevel = LogLevel.Information)
|
||||||
|
{
|
||||||
|
ParserOptions options = ParserOptions.None;
|
||||||
|
if (skipEnums)
|
||||||
options |= ParserOptions.SkipEnums;
|
options |= ParserOptions.SkipEnums;
|
||||||
|
if (includePropertiesWithoutNonUserCodeAttribute)
|
||||||
if (args.Contains("--include_properties_without_non_user_code_attribute"))
|
|
||||||
options |= ParserOptions.IncludePropertiesWithoutNonUserCodeAttribute;
|
options |= ParserOptions.IncludePropertiesWithoutNonUserCodeAttribute;
|
||||||
|
if (includeServiceMethodsWithoutGeneratedCodeAttribute)
|
||||||
if (args.Contains("--include_service_methods_without_generated_code_attribute"))
|
|
||||||
options |= ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute;
|
options |= ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute;
|
||||||
|
|
||||||
using ILoggerFactory loggerFactory = LoggerFactory.Create(
|
using ILoggerFactory loggerFactory = LoggerFactory.Create(
|
||||||
builder => builder.AddSimpleConsole(static console => console.IncludeScopes = true)
|
builder => builder.AddSimpleConsole(static console => console.IncludeScopes = true)
|
||||||
.SetMinimumLevel(logLevel));
|
.SetMinimumLevel(logLevel));
|
||||||
ILogger logger = loggerFactory.CreateLogger("protodec");
|
|
||||||
|
|
||||||
logger.LogInformation("Loading target assemblies...");
|
ILogger logger = loggerFactory.CreateLogger("protodec");
|
||||||
using CilAssemblyLoader loader = new ClrAssemblyLoader(
|
ConsoleApp.LogError = msg => logger.LogError(msg);
|
||||||
assembly, loggerFactory.CreateLogger<ClrAssemblyLoader>());
|
|
||||||
|
|
||||||
ProtodecContext ctx = new()
|
logger.LogInformation("Loading target assemblies...");
|
||||||
{
|
using CilAssemblyLoader loader = new ClrAssemblyLoader(
|
||||||
|
targetPath,
|
||||||
|
loggerFactory.CreateLogger<ClrAssemblyLoader>());
|
||||||
|
|
||||||
|
ProtodecContext ctx = new()
|
||||||
|
{
|
||||||
Logger = loggerFactory.CreateLogger<ProtodecContext>()
|
Logger = loggerFactory.CreateLogger<ProtodecContext>()
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.LogInformation("Parsing Protobuf message types...");
|
logger.LogInformation("Parsing Protobuf message types...");
|
||||||
foreach (ICilType message in GetProtobufMessageTypes())
|
foreach (ICilType message in loader.GetProtobufMessageTypes())
|
||||||
{
|
{
|
||||||
ctx.ParseMessage(message, options);
|
ctx.ParseMessage(message, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Contains("--parse_service_servers"))
|
if (parseServiceServers)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Parsing Protobuf service server types...");
|
logger.LogInformation("Parsing Protobuf service server types...");
|
||||||
foreach (ICilType service in GetProtobufServiceServerTypes())
|
foreach (ICilType service in loader.GetProtobufServiceServerTypes())
|
||||||
{
|
{
|
||||||
ctx.ParseService(service, options);
|
ctx.ParseService(service, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Contains("--parse_service_clients"))
|
if (parseServiceClients)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Parsing Protobuf service client types...");
|
logger.LogInformation("Parsing Protobuf service client types...");
|
||||||
foreach (ICilType service in GetProtobufServiceClientTypes())
|
foreach (ICilType service in loader.GetProtobufServiceClientTypes())
|
||||||
{
|
{
|
||||||
ctx.ParseService(service, options);
|
ctx.ParseService(service, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Directory.Exists(outPath))
|
const string indent = " ";
|
||||||
{
|
if (Directory.Exists(outPath))
|
||||||
|
{
|
||||||
logger.LogInformation("Writing {count} Protobuf files to \"{path}\"...", ctx.Protobufs.Count, outPath);
|
logger.LogInformation("Writing {count} Protobuf files to \"{path}\"...", ctx.Protobufs.Count, outPath);
|
||||||
|
|
||||||
HashSet<string> writtenFiles = [];
|
HashSet<string> writtenFiles = [];
|
||||||
|
@ -112,29 +114,15 @@ if (Directory.Exists(outPath))
|
||||||
|
|
||||||
protobuf.WriteTo(indentWriter);
|
protobuf.WriteTo(indentWriter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogInformation("Writing Protobufs as a single file to \"{path}\"...", outPath);
|
logger.LogInformation("Writing Protobufs as a single file to \"{path}\"...", outPath);
|
||||||
|
|
||||||
using StreamWriter streamWriter = new(outPath);
|
using StreamWriter streamWriter = new(outPath);
|
||||||
using IndentedTextWriter indentWriter = new(streamWriter, indent);
|
using IndentedTextWriter indentWriter = new(streamWriter, indent);
|
||||||
|
|
||||||
ctx.WriteAllTo(indentWriter);
|
ctx.WriteAllTo(indentWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<ICilType> GetProtobufMessageTypes() =>
|
|
||||||
loader.LoadedTypes.Where(
|
|
||||||
type => type is { IsNested: false, IsSealed: true }
|
|
||||||
&& type.Namespace?.StartsWith("Google.Protobuf", StringComparison.Ordinal) != true
|
|
||||||
&& type.IsAssignableTo(loader.IMessage));
|
|
||||||
|
|
||||||
IEnumerable<ICilType> GetProtobufServiceClientTypes() =>
|
|
||||||
loader.LoadedTypes.Where(
|
|
||||||
type => type is { IsNested: true, IsAbstract: false }
|
|
||||||
&& type.IsAssignableTo(loader.ClientBase));
|
|
||||||
|
|
||||||
IEnumerable<ICilType> GetProtobufServiceServerTypes() =>
|
|
||||||
loader.LoadedTypes.Where(
|
|
||||||
type => type is { IsNested: true, IsAbstract: true, DeclaringType: { IsNested: false, IsSealed: true, IsAbstract: true } }
|
|
||||||
&& type.CustomAttributes.Any(attribute => attribute.Type == loader.BindServiceMethodAttribute));
|
|
|
@ -12,7 +12,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="$(SolutionDir)src\LibProtodec\LibProtodec.csproj" />
|
<ProjectReference Include="$(SolutionDir)src\LibProtodec\LibProtodec.csproj" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0-preview.5.24306.7" />
|
<PackageReference Include="ConsoleAppFramework" Version="5.2.2" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0-preview.6.24327.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
Loading…
Reference in New Issue