Add an option to skip parsing properties that aren't decorated with the protoc attribute

also update to dotnet 8 and update the project folder structure
This commit is contained in:
Xpl0itR 2023-12-25 01:58:12 +00:00
parent b1eed86d6c
commit e41df439ea
Signed by: Xpl0itR
GPG Key ID: 91798184109676AD
11 changed files with 42 additions and 35 deletions

View File

@ -11,7 +11,8 @@ Arguments:
out_path An existing directory to output into individual files, otherwise output to a single file. out_path An existing directory to output into individual files, otherwise output to a single file.
target_assembly_name The name of an assembly to parse. If omitted, all assemblies in the target_assembly_dir will be parsed. target_assembly_name The name of an assembly to parse. If omitted, all assemblies in the target_assembly_dir will be parsed.
Options: Options:
--skip_enums Skip parsing enums and replace references to them with int32. --skip_enums Skip parsing enums and replace references to them with int32.
--skip_properties_without_protoc_attribute Skip properties that aren't decorated with `GeneratedCode("protoc")` when parsing
``` ```
Limitations Limitations

View File

@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.6.33513.286 VisualStudioVersion = 17.6.33513.286
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protodec", "protodec\protodec.csproj", "{A5493BF4-F78C-4DCF-B449-D9A9A52FB5F0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protodec", "src\protodec\protodec.csproj", "{A5493BF4-F78C-4DCF-B449-D9A9A52FB5F0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibProtodec", "LibProtodec\LibProtodec.csproj", "{5F6DAD82-D9AD-4CE5-86E6-D20C9F059A4D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibProtodec", "src\LibProtodec\LibProtodec.csproj", "{5F6DAD82-D9AD-4CE5-86E6-D20C9F059A4D}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -5,7 +5,7 @@ namespace LibProtodec;
public sealed class Enum : Protobuf public sealed class Enum : Protobuf
{ {
public readonly List<KeyValuePair<int, string>> Fields = new(); public readonly List<KeyValuePair<int, string>> Fields = [];
public override void WriteFileTo(IndentedTextWriter writer) public override void WriteFileTo(IndentedTextWriter writer)
{ {
@ -22,7 +22,7 @@ public sealed class Enum : Protobuf
if (Fields.ContainsDuplicateKey()) if (Fields.ContainsDuplicateKey())
{ {
writer.WriteLine("""option allow_alias = true;"""); writer.WriteLine("option allow_alias = true;");
} }
foreach ((int id, string name) in Fields) foreach ((int id, string name) in Fields)

View File

@ -2,24 +2,25 @@
<PropertyGroup> <PropertyGroup>
<Authors>Xpl0itR</Authors> <Authors>Xpl0itR</Authors>
<BaseOutputPath>$(SolutionDir)bin/$(MSBuildProjectName)</BaseOutputPath>
<Copyright>Copyright © 2023 Xpl0itR</Copyright> <Copyright>Copyright © 2023 Xpl0itR</Copyright>
<Description>A library to decompile protobuf parser/serializer classes compiled by protoc, from dotnet assemblies back into .proto definitions</Description> <Description>A library to decompile protobuf parser/serializer classes compiled by protoc, from dotnet assemblies back into .proto definitions</Description>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<LangVersion>11</LangVersion> <LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<PackageLicenseExpression>MPL-2.0</PackageLicenseExpression> <PackageLicenseExpression>MPL-2.0</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.2.2" /> <PackageReference Include="CommunityToolkit.Diagnostics" Version="8.2.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" /> <PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,10 +7,10 @@ namespace LibProtodec;
public sealed class Message : Protobuf public sealed class Message : Protobuf
{ {
public readonly HashSet<string> Imports = new(); public readonly HashSet<string> Imports = [];
public readonly Dictionary<string, int[]> OneOfs = new(); public readonly Dictionary<string, int[]> OneOfs = [];
public readonly Dictionary<int, (bool IsOptional, string Type, string Name)> Fields = new(); public readonly Dictionary<int, (bool IsOptional, string Type, string Name)> Fields = [];
public readonly Dictionary<string, Protobuf> Nested = new(); public readonly Dictionary<string, Protobuf> Nested = [];
public override void WriteFileTo(IndentedTextWriter writer) public override void WriteFileTo(IndentedTextWriter writer)
{ {

View File

@ -12,14 +12,8 @@ public sealed class Protodec
{ {
public delegate bool LookupFunc(string key, [MaybeNullWhen(false)] out string value); public delegate bool LookupFunc(string key, [MaybeNullWhen(false)] out string value);
private readonly Dictionary<string, Protobuf> _protobufs; private readonly Dictionary<string, Protobuf> _protobufs = [];
private readonly HashSet<string> _currentDescent; private readonly HashSet<string> _currentDescent = [];
public Protodec()
{
_protobufs = new Dictionary<string, Protobuf>();
_currentDescent = new HashSet<string>();
}
public LookupFunc? CustomTypeLookup { get; init; } public LookupFunc? CustomTypeLookup { get; init; }
@ -40,11 +34,11 @@ public sealed class Protodec
} }
} }
public void ParseMessage(Type type, bool skipEnums = false) public void ParseMessage(Type type, bool skipEnums = false, bool skipPropertiesWithoutProtocAttribute = false)
{ {
Guard.IsTrue(type.IsClass); Guard.IsTrue(type.IsClass);
ParseMessageInternal(type, skipEnums, null); ParseMessageInternal(type, skipEnums, skipPropertiesWithoutProtocAttribute, null);
_currentDescent.Clear(); _currentDescent.Clear();
} }
@ -66,7 +60,7 @@ public sealed class Protodec
|| !_currentDescent.Add(type.Name); || !_currentDescent.Add(type.Name);
} }
private void ParseMessageInternal(Type messageClass, bool skipEnums, Message? parentMessage) private void ParseMessageInternal(Type messageClass, bool skipEnums, bool skipPropertiesWithoutProtocAttribute, Message? parentMessage)
{ {
if (IsParsed(messageClass, parentMessage, out Dictionary<string, Protobuf> protobufs)) if (IsParsed(messageClass, parentMessage, out Dictionary<string, Protobuf> protobufs))
{ {
@ -87,7 +81,9 @@ public sealed class Protodec
{ {
PropertyInfo property = properties[pi]; PropertyInfo property = properties[pi];
if (property.GetMethod is null || property.GetMethod.IsVirtual) if (property.GetMethod is null
|| property.GetMethod.IsVirtual
|| (skipPropertiesWithoutProtocAttribute && !HasProtocAttribute(property)))
{ {
fi--; fi--;
continue; continue;
@ -116,7 +112,7 @@ public sealed class Protodec
int msgFieldId = (int)idField.GetRawConstantValue()!; int msgFieldId = (int)idField.GetRawConstantValue()!;
bool msgFieldIsOptional = false; bool msgFieldIsOptional = false;
string msgFieldType = ParseFieldType(propertyType, skipEnums, message); string msgFieldType = ParseFieldType(propertyType, skipEnums, skipPropertiesWithoutProtocAttribute, message);
string msgFieldName = TranslateMessageFieldName(property.Name); string msgFieldName = TranslateMessageFieldName(property.Name);
// optional protobuf fields will generate an additional "Has" get-only boolean property immediately after the real property // optional protobuf fields will generate an additional "Has" get-only boolean property immediately after the real property
@ -157,7 +153,7 @@ public sealed class Protodec
protobufs.Add(@enum.Name, @enum); protobufs.Add(@enum.Name, @enum);
} }
private string ParseFieldType(Type type, bool skipEnums, Message message) private string ParseFieldType(Type type, bool skipEnums, bool skipPropertiesWithoutProtocAttribute, Message message)
{ {
switch (type.Name) switch (type.Name)
{ {
@ -184,11 +180,11 @@ public sealed class Protodec
switch (type.GenericTypeArguments.Length) switch (type.GenericTypeArguments.Length)
{ {
case 1: case 1:
string t = ParseFieldType(type.GenericTypeArguments[0], skipEnums, message); string t = ParseFieldType(type.GenericTypeArguments[0], skipEnums, skipPropertiesWithoutProtocAttribute, message);
return "repeated " + t; return "repeated " + t;
case 2: case 2:
string t1 = ParseFieldType(type.GenericTypeArguments[0], skipEnums, message); string t1 = ParseFieldType(type.GenericTypeArguments[0], skipEnums, skipPropertiesWithoutProtocAttribute, message);
string t2 = ParseFieldType(type.GenericTypeArguments[1], skipEnums, message); string t2 = ParseFieldType(type.GenericTypeArguments[1], skipEnums, skipPropertiesWithoutProtocAttribute, message);
return $"map<{t1}, {t2}>"; return $"map<{t1}, {t2}>";
} }
@ -208,7 +204,7 @@ public sealed class Protodec
} }
else else
{ {
ParseMessageInternal(type, skipEnums, message); ParseMessageInternal(type, skipEnums, skipPropertiesWithoutProtocAttribute, message);
} }
if (!type.IsNested) if (!type.IsNested)
@ -219,6 +215,12 @@ public sealed class Protodec
return type.Name; return type.Name;
} }
private static bool HasProtocAttribute(PropertyInfo property) =>
property.GetCustomAttributesData()
.Any(attr =>
attr.AttributeType.Name == "GeneratedCodeAttribute"
&& attr.ConstructorArguments[0].Value as string == "protoc");
private string TranslateProtobufName(string name) => private string TranslateProtobufName(string name) =>
CustomNameLookup?.Invoke(name, out string? translatedName) == true CustomNameLookup?.Invoke(name, out string? translatedName) == true
? translatedName ? translatedName

View File

@ -12,7 +12,8 @@ const string help = """
out_path An existing directory to output into individual files, otherwise output to a single file. out_path An existing directory to output into individual files, otherwise output to a single file.
target_assembly_name The name of an assembly to parse. If omitted, all assemblies in the target_assembly_dir will be parsed. target_assembly_name The name of an assembly to parse. If omitted, all assemblies in the target_assembly_dir will be parsed.
Options: Options:
--skip_enums Skip parsing enums and replace references to them with int32. --skip_enums Skip parsing enums and replace references to them with int32.
--skip_properties_without_protoc_attribute Skip properties that aren't decorated with `GeneratedCode("protoc")` when parsing
"""; """;
if (args.Length < 2) if (args.Length < 2)
@ -30,13 +31,14 @@ if (args.Length > 2 && !args[2].StartsWith('-'))
string assemblyDir = args[0]; string assemblyDir = args[0];
string outPath = Path.GetFullPath(args[1]); string outPath = Path.GetFullPath(args[1]);
bool skipEnums = args.Contains("--skip_enums"); bool skipEnums = args.Contains("--skip_enums");
bool skipPropertiesWithoutProtocAttribute = args.Contains("--skip_properties_without_protoc_attribute");
using AssemblyInspector inspector = new(assemblyDir, assemblyName); using AssemblyInspector inspector = new(assemblyDir, assemblyName);
Protodec protodec = new(); Protodec protodec = new();
foreach (Type message in inspector.GetProtobufMessageTypes()) foreach (Type message in inspector.GetProtobufMessageTypes())
{ {
protodec.ParseMessage(message, skipEnums); protodec.ParseMessage(message, skipEnums, skipPropertiesWithoutProtocAttribute);
} }
if (Directory.Exists(outPath)) if (Directory.Exists(outPath))

View File

@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<LangVersion>11</LangVersion> <BaseOutputPath>$(SolutionDir)bin/$(MSBuildProjectName)</BaseOutputPath>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>