diff --git a/README.md b/README.md index 8971219..6ee788a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Arguments: 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. 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 diff --git a/protodec.sln b/protodec.sln index f7ffb1b..06ab2b8 100644 --- a/protodec.sln +++ b/protodec.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.33513.286 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 -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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/LibProtodec/AssemblyInspector.cs b/src/LibProtodec/AssemblyInspector.cs similarity index 100% rename from LibProtodec/AssemblyInspector.cs rename to src/LibProtodec/AssemblyInspector.cs diff --git a/LibProtodec/Enum.cs b/src/LibProtodec/Enum.cs similarity index 85% rename from LibProtodec/Enum.cs rename to src/LibProtodec/Enum.cs index 9f7d068..36b1610 100644 --- a/LibProtodec/Enum.cs +++ b/src/LibProtodec/Enum.cs @@ -5,7 +5,7 @@ namespace LibProtodec; public sealed class Enum : Protobuf { - public readonly List> Fields = new(); + public readonly List> Fields = []; public override void WriteFileTo(IndentedTextWriter writer) { @@ -22,7 +22,7 @@ public sealed class Enum : Protobuf if (Fields.ContainsDuplicateKey()) { - writer.WriteLine("""option allow_alias = true;"""); + writer.WriteLine("option allow_alias = true;"); } foreach ((int id, string name) in Fields) diff --git a/LibProtodec/Extensions.cs b/src/LibProtodec/Extensions.cs similarity index 100% rename from LibProtodec/Extensions.cs rename to src/LibProtodec/Extensions.cs diff --git a/LibProtodec/LibProtodec.csproj b/src/LibProtodec/LibProtodec.csproj similarity index 81% rename from LibProtodec/LibProtodec.csproj rename to src/LibProtodec/LibProtodec.csproj index 4e5a51a..bdc59be 100644 --- a/LibProtodec/LibProtodec.csproj +++ b/src/LibProtodec/LibProtodec.csproj @@ -2,24 +2,25 @@ Xpl0itR + $(SolutionDir)bin/$(MSBuildProjectName) Copyright © 2023 Xpl0itR A library to decompile protobuf parser/serializer classes compiled by protoc, from dotnet assemblies back into .proto definitions true true - 11 + Latest enable Library MPL-2.0 true true snupkg - net7.0 + net8.0 - - + + \ No newline at end of file diff --git a/LibProtodec/Message.cs b/src/LibProtodec/Message.cs similarity index 94% rename from LibProtodec/Message.cs rename to src/LibProtodec/Message.cs index 16bab7a..975d803 100644 --- a/LibProtodec/Message.cs +++ b/src/LibProtodec/Message.cs @@ -7,10 +7,10 @@ namespace LibProtodec; public sealed class Message : Protobuf { - public readonly HashSet Imports = new(); - public readonly Dictionary OneOfs = new(); - public readonly Dictionary Fields = new(); - public readonly Dictionary Nested = new(); + public readonly HashSet Imports = []; + public readonly Dictionary OneOfs = []; + public readonly Dictionary Fields = []; + public readonly Dictionary Nested = []; public override void WriteFileTo(IndentedTextWriter writer) { diff --git a/LibProtodec/Protobuf.cs b/src/LibProtodec/Protobuf.cs similarity index 100% rename from LibProtodec/Protobuf.cs rename to src/LibProtodec/Protobuf.cs diff --git a/LibProtodec/Protodec.cs b/src/LibProtodec/Protodec.cs similarity index 85% rename from LibProtodec/Protodec.cs rename to src/LibProtodec/Protodec.cs index d1a95cf..43e2d50 100644 --- a/LibProtodec/Protodec.cs +++ b/src/LibProtodec/Protodec.cs @@ -12,14 +12,8 @@ public sealed class Protodec { public delegate bool LookupFunc(string key, [MaybeNullWhen(false)] out string value); - private readonly Dictionary _protobufs; - private readonly HashSet _currentDescent; - - public Protodec() - { - _protobufs = new Dictionary(); - _currentDescent = new HashSet(); - } + private readonly Dictionary _protobufs = []; + private readonly HashSet _currentDescent = []; 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); - ParseMessageInternal(type, skipEnums, null); + ParseMessageInternal(type, skipEnums, skipPropertiesWithoutProtocAttribute, null); _currentDescent.Clear(); } @@ -66,7 +60,7 @@ public sealed class Protodec || !_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 protobufs)) { @@ -87,7 +81,9 @@ public sealed class Protodec { PropertyInfo property = properties[pi]; - if (property.GetMethod is null || property.GetMethod.IsVirtual) + if (property.GetMethod is null + || property.GetMethod.IsVirtual + || (skipPropertiesWithoutProtocAttribute && !HasProtocAttribute(property))) { fi--; continue; @@ -116,7 +112,7 @@ public sealed class Protodec int msgFieldId = (int)idField.GetRawConstantValue()!; bool msgFieldIsOptional = false; - string msgFieldType = ParseFieldType(propertyType, skipEnums, message); + string msgFieldType = ParseFieldType(propertyType, skipEnums, skipPropertiesWithoutProtocAttribute, message); string msgFieldName = TranslateMessageFieldName(property.Name); // 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); } - private string ParseFieldType(Type type, bool skipEnums, Message message) + private string ParseFieldType(Type type, bool skipEnums, bool skipPropertiesWithoutProtocAttribute, Message message) { switch (type.Name) { @@ -184,11 +180,11 @@ public sealed class Protodec switch (type.GenericTypeArguments.Length) { case 1: - string t = ParseFieldType(type.GenericTypeArguments[0], skipEnums, message); + string t = ParseFieldType(type.GenericTypeArguments[0], skipEnums, skipPropertiesWithoutProtocAttribute, message); return "repeated " + t; case 2: - string t1 = ParseFieldType(type.GenericTypeArguments[0], skipEnums, message); - string t2 = ParseFieldType(type.GenericTypeArguments[1], skipEnums, message); + string t1 = ParseFieldType(type.GenericTypeArguments[0], skipEnums, skipPropertiesWithoutProtocAttribute, message); + string t2 = ParseFieldType(type.GenericTypeArguments[1], skipEnums, skipPropertiesWithoutProtocAttribute, message); return $"map<{t1}, {t2}>"; } @@ -208,7 +204,7 @@ public sealed class Protodec } else { - ParseMessageInternal(type, skipEnums, message); + ParseMessageInternal(type, skipEnums, skipPropertiesWithoutProtocAttribute, message); } if (!type.IsNested) @@ -219,6 +215,12 @@ public sealed class Protodec 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) => CustomNameLookup?.Invoke(name, out string? translatedName) == true ? translatedName diff --git a/protodec/Program.cs b/src/protodec/Program.cs similarity index 78% rename from protodec/Program.cs rename to src/protodec/Program.cs index 595c809..7e9f79b 100644 --- a/protodec/Program.cs +++ b/src/protodec/Program.cs @@ -12,7 +12,8 @@ const string help = """ 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. 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) @@ -30,13 +31,14 @@ if (args.Length > 2 && !args[2].StartsWith('-')) string assemblyDir = args[0]; string outPath = Path.GetFullPath(args[1]); bool skipEnums = args.Contains("--skip_enums"); +bool skipPropertiesWithoutProtocAttribute = args.Contains("--skip_properties_without_protoc_attribute"); using AssemblyInspector inspector = new(assemblyDir, assemblyName); Protodec protodec = new(); foreach (Type message in inspector.GetProtobufMessageTypes()) { - protodec.ParseMessage(message, skipEnums); + protodec.ParseMessage(message, skipEnums, skipPropertiesWithoutProtocAttribute); } if (Directory.Exists(outPath)) diff --git a/protodec/protodec.csproj b/src/protodec/protodec.csproj similarity index 66% rename from protodec/protodec.csproj rename to src/protodec/protodec.csproj index 8ae4569..36814e3 100644 --- a/protodec/protodec.csproj +++ b/src/protodec/protodec.csproj @@ -1,11 +1,12 @@  - 11 + $(SolutionDir)bin/$(MSBuildProjectName) + Latest enable Exe win-x64;linux-x64 - net7.0 + net8.0