diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a7b78cc..539faac 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x'
- name: Build protodec
- run: dotnet publish --configuration Release --runtime ${{ matrix.runtime }} /p:VersionPrefix=${{ github.ref_name }} /p:ContinuousIntegrationBuild=true
+ run: dotnet publish --configuration Release --runtime ${{ matrix.runtime }} /p:Version=${{ github.ref_name }} /p:ContinuousIntegrationBuild=true
- name: Pack LibProtodec
if: matrix.os == 'ubuntu-latest'
- run: dotnet pack --configuration Release /p:VersionPrefix=${{ github.ref_name }} /p:ContinuousIntegrationBuild=true
+ run: dotnet pack --configuration Release /p:Version=${{ github.ref_name }} /p:ContinuousIntegrationBuild=true
- name: Release
uses: softprops/action-gh-release@v1
diff --git a/README.md b/README.md
index 283af37..fb55d3a 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ 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.
diff --git a/src/LibProtodec/LibProtodec.csproj b/src/LibProtodec/LibProtodec.csproj
index 8120bb0..87a204e 100644
--- a/src/LibProtodec/LibProtodec.csproj
+++ b/src/LibProtodec/LibProtodec.csproj
@@ -18,9 +18,10 @@
+
-
-
+
+
\ No newline at end of file
diff --git a/src/LibProtodec/Loaders/CilAssemblyLoader.cs b/src/LibProtodec/Loaders/CilAssemblyLoader.cs
new file mode 100644
index 0000000..ab803b2
--- /dev/null
+++ b/src/LibProtodec/Loaders/CilAssemblyLoader.cs
@@ -0,0 +1,34 @@
+// Copyright © 2024 Xpl0itR
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+using System;
+using System.Collections.Generic;
+using LibProtodec.Models.Cil;
+
+namespace LibProtodec.Loaders;
+
+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 LoadedTypes { get; protected init; }
+
+ public virtual void Dispose() { }
+
+ protected abstract ICilType FindType(string typeFullName, string assemblySimpleName);
+}
\ No newline at end of file
diff --git a/src/LibProtodec/Loaders/ClrAssemblyLoader.cs b/src/LibProtodec/Loaders/ClrAssemblyLoader.cs
index 01abb22..2aaf143 100644
--- a/src/LibProtodec/Loaders/ClrAssemblyLoader.cs
+++ b/src/LibProtodec/Loaders/ClrAssemblyLoader.cs
@@ -12,14 +12,15 @@ using System.Reflection;
using CommunityToolkit.Diagnostics;
using LibProtodec.Models.Cil;
using LibProtodec.Models.Cil.Clr;
+using Microsoft.Extensions.Logging;
namespace LibProtodec.Loaders;
-public sealed class ClrAssemblyLoader : ICilAssemblyLoader
+public sealed class ClrAssemblyLoader : CilAssemblyLoader
{
public readonly MetadataLoadContext LoadContext;
- public ClrAssemblyLoader(string assemblyPath)
+ public ClrAssemblyLoader(string assemblyPath, ILogger? logger = null)
{
bool isFile = File.Exists(assemblyPath);
string assemblyDir = isFile
@@ -28,67 +29,33 @@ public sealed class ClrAssemblyLoader : ICilAssemblyLoader
PermissiveAssemblyResolver assemblyResolver = new(
Directory.EnumerateFiles(assemblyDir, searchPattern: "*.dll"));
-
LoadContext = new MetadataLoadContext(assemblyResolver);
- LoadedTypes = isFile
- ? LoadContext.LoadFromAssemblyPath(assemblyPath)
- .GetTypes()
- .Select(ClrType.GetOrCreate)
- .ToList()
+
+ IEnumerable allTypes = isFile
+ ? LoadContext.LoadFromAssemblyPath(assemblyPath).GetTypes()
: assemblyResolver.AssemblyPathLookup.Values
- .SelectMany(path => LoadContext.LoadFromAssemblyPath(path).GetTypes())
- .Select(ClrType.GetOrCreate)
- .ToList();
+ .SelectMany(path => LoadContext.LoadFromAssemblyPath(path).GetTypes());
+
+ this.LoadedTypes = allTypes.Where(static type => type.GenericTypeArguments.Length == 0)
+ .Select(ClrType.GetOrCreate)
+ .ToList();
+
+ logger?.LogLoadedTypeAndAssemblyCount(this.LoadedTypes.Count, LoadContext.GetAssemblies().Count());
}
- public IReadOnlyList LoadedTypes { get; }
-
- public ICilType IMessage
+ protected override ICilType FindType(string typeFullName, string assemblySimpleName)
{
- get
- {
- ICilType? iMessage = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Google.Protobuf.IMessage", null);
- if (iMessage is not null)
- return iMessage;
+ ICilType? type = this.LoadedTypes.SingleOrDefault(type => type?.FullName == typeFullName, null);
+ if (type is not null)
+ return type;
- Type? iMessageType = LoadContext.LoadFromAssemblyName("Google.Protobuf").GetType("Google.Protobuf.IMessage");
- Guard.IsNotNull(iMessageType);
+ Type? clrType = LoadContext.LoadFromAssemblyName(assemblySimpleName).GetType(typeFullName);
+ Guard.IsNotNull(clrType);
- return ClrType.GetOrCreate(iMessageType);
- }
+ return ClrType.GetOrCreate(clrType);
}
- public ICilType ClientBase
- {
- get
- {
- ICilType? clientBase = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.ClientBase", null);
- if (clientBase is not null)
- return clientBase;
-
- Type? clientBaseType = LoadContext.LoadFromAssemblyName("Grpc.Core.Api").GetType("Grpc.Core.ClientBase");
- Guard.IsNotNull(clientBaseType);
-
- return ClrType.GetOrCreate(clientBaseType);
- }
- }
-
- public ICilType BindServiceMethodAttribute
- {
- get
- {
- ICilType? attribute = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.BindServiceMethodAttribute", null);
- if (attribute is not null)
- return attribute;
-
- Type? attributeType = LoadContext.LoadFromAssemblyName("Grpc.Core.Api").GetType("Grpc.Core.BindServiceMethodAttribute");
- Guard.IsNotNull(attributeType);
-
- return ClrType.GetOrCreate(attributeType);
- }
- }
-
- public void Dispose() =>
+ public override void Dispose() =>
LoadContext.Dispose();
///
diff --git a/src/LibProtodec/Loaders/ICilAssemblyLoader.cs b/src/LibProtodec/Loaders/ICilAssemblyLoader.cs
deleted file mode 100644
index 5069e4c..0000000
--- a/src/LibProtodec/Loaders/ICilAssemblyLoader.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright © 2024 Xpl0itR
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// 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/.
-
-using System;
-using System.Collections.Generic;
-using LibProtodec.Models.Cil;
-
-namespace LibProtodec.Loaders;
-
-public interface ICilAssemblyLoader : IDisposable
-{
- IReadOnlyList LoadedTypes { get; }
-
- // ReSharper disable once InconsistentNaming
- ICilType IMessage { get; }
-
- ICilType ClientBase { get; }
-
- ICilType BindServiceMethodAttribute { get; }
-}
\ No newline at end of file
diff --git a/src/LibProtodec/LoggerMessages.cs b/src/LibProtodec/LoggerMessages.cs
new file mode 100644
index 0000000..d69d10a
--- /dev/null
+++ b/src/LibProtodec/LoggerMessages.cs
@@ -0,0 +1,86 @@
+// Copyright © 2024 Xpl0itR
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace LibProtodec;
+
+// ReSharper disable InconsistentNaming, StringLiteralTypo
+internal static partial class LoggerMessages
+{
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to locate corresponding id field; likely stripped or otherwise obfuscated.")]
+ internal static partial void LogFailedToLocateIdField(this ILogger logger);
+
+ [LoggerMessage(Level = LogLevel.Information, Message = "Loaded {typeCount} types from {assemblyCount} assemblies for parsing.")]
+ internal static partial void LogLoadedTypeAndAssemblyCount(this ILogger logger, int typeCount, int assemblyCount);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as enum \"{name}\".")]
+ internal static partial void LogParsedEnum(this ILogger logger, string name);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as field \"{name}\" with id \"{id}\".")]
+ internal static partial void LogParsedField(this ILogger logger, string name, int id);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as field \"{name}\" with id \"{id}\" of type \"{typeName}\".")]
+ internal static partial void LogParsedField(this ILogger logger, string name, int id, string typeName);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as message \"{name}\".")]
+ internal static partial void LogParsedMessage(this ILogger logger, string name);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as method \"{name}\" with request type \"{reqType}\" and response type \"{resType}\".")]
+ internal static partial void LogParsedMethod(this ILogger logger, string name, string reqType, string resType);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as oneof field \"{name}\".")]
+ internal static partial void LogParsedOneOfField(this ILogger logger, string name);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as service \"{name}\".")]
+ internal static partial void LogParsedService(this ILogger logger, string name);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Skipping duplicate method.")]
+ internal static partial void LogSkippingDuplicateMethod(this ILogger logger);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Skipping property without required NonUserCodeAttribute.")]
+ internal static partial void LogSkippingPropertyWithoutNonUserCodeAttribute(this ILogger logger);
+
+ [LoggerMessage(Level = LogLevel.Debug, Message = "Skipping method without required GeneratedCodeAttribute.")]
+ internal static partial void LogSkippingMethodWithoutGeneratedCodeAttribute(this ILogger logger);
+
+ internal static IDisposable? BeginScopeParsingEnum(this ILogger logger, string typeName) =>
+ __BeginScopeParsingEnumCallback(logger, typeName);
+
+ internal static IDisposable? BeginScopeParsingField(this ILogger logger, string name) =>
+ __BeginScopeParsingFieldCallback(logger, name);
+
+ internal static IDisposable? BeginScopeParsingMessage(this ILogger logger, string name) =>
+ __BeginScopeParsingMessageCallback(logger, name);
+
+ internal static IDisposable? BeginScopeParsingMethod(this ILogger logger, string name) =>
+ __BeginScopeParsingMethodCallback(logger, name);
+
+ internal static IDisposable? BeginScopeParsingProperty(this ILogger logger, string name, string typeName) =>
+ __BeginScopeParsingPropertyCallback(logger, name, typeName);
+
+ internal static IDisposable? BeginScopeParsingService(this ILogger logger, string typeName) =>
+ __BeginScopeParsingServiceCallback(logger, typeName);
+
+ private static readonly Func __BeginScopeParsingEnumCallback =
+ LoggerMessage.DefineScope("Parsing enum from type \"{typeName}\"");
+
+ private static readonly Func __BeginScopeParsingFieldCallback =
+ LoggerMessage.DefineScope("Parsing field \"{name}\"");
+
+ private static readonly Func __BeginScopeParsingMessageCallback =
+ LoggerMessage.DefineScope("Parsing message from type \"{name}\"");
+
+ private static readonly Func __BeginScopeParsingMethodCallback =
+ LoggerMessage.DefineScope("Parsing method \"{name}\"");
+
+ private static readonly Func __BeginScopeParsingPropertyCallback =
+ LoggerMessage.DefineScope("Parsing property \"{name}\" of type \"{typeName}\"");
+
+ private static readonly Func __BeginScopeParsingServiceCallback =
+ LoggerMessage.DefineScope("Parsing service from type \"{typeName}\"");
+}
\ No newline at end of file
diff --git a/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs b/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs
index 3b3ef1a..493ae88 100644
--- a/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs
+++ b/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs
@@ -6,37 +6,38 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
namespace LibProtodec.Models.Cil.Clr;
public sealed class ClrAttribute(CustomAttributeData clrAttribute) : ICilAttribute
{
- private IList