Skip to content

Commit

Permalink
Even more improvements
Browse files Browse the repository at this point in the history
including but not limited to:
- detect protobuf well-known types
- detect NonUserCodeAttribute
- detect ObsoleteAttribute to mark protobufs/fields as deprecated
- parse generated client/server types for gRPC services
- fix "optional" parsing
- fix obfuscated name translation
- fix foreign nested protobufs
- support protobuf editions and corresponding options
- rewrite protobuf models to better reflect the specification, including decoupling toplevels and files
- rewrite a bunch of things to make more sense
  • Loading branch information
Xpl0itR committed Jun 7, 2024
1 parent 68ee943 commit d43deef
Show file tree
Hide file tree
Showing 24 changed files with 1,173 additions and 369 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ 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:
--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
--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.
```

Limitations
-----------
- Integers are assumed to be (u)int32/64 as CIL doesn't differentiate between them and sint32/64 and (s)fixed32/64.
- Package names are not preserved in protobuf compilation so naturally we cannot recover them during decompilation, which may result in naming conflicts.
- When decompiling from [Il2CppDumper](https://github.com/Perfare/Il2CppDumper) DummyDLLs
- The `Name` parameter of `OriginalNameAttribute` is not dumped. In this case, the CIL enum field names are used after conforming them to protobuf conventions

Expand Down
5 changes: 5 additions & 0 deletions protodec.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protodec", "src\protodec\pr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibProtodec", "src\LibProtodec\LibProtodec.csproj", "{5F6DAD82-D9AD-4CE5-86E6-D20C9F059A4D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{22F217C3-0FC2-4D06-B5F3-AA1E3AFC402E}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
40 changes: 30 additions & 10 deletions src/LibProtodec/AssemblyInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,36 @@ public AssemblyInspector(string assemblyPath)

public IEnumerable<Type> GetProtobufMessageTypes()
{
Type? googleProtobufIMessage = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Google.Protobuf.IMessage", null)
?? AssemblyContext.LoadFromAssemblyName("Google.Protobuf")
.GetType("Google.Protobuf.IMessage");
return from type
in LoadedTypes
where !type.IsNested
&& type.IsSealed
&& type.Namespace?.StartsWith("Google.Protobuf", StringComparison.Ordinal) != true
&& type.IsAssignableTo(googleProtobufIMessage)
select type;
Type? iMessage = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Google.Protobuf.IMessage", null)
?? AssemblyContext.LoadFromAssemblyName("Google.Protobuf")
.GetType("Google.Protobuf.IMessage");

return LoadedTypes.Where(
type => type is { IsNested: false, IsSealed: true }
&& type.Namespace?.StartsWith("Google.Protobuf", StringComparison.Ordinal) != true
&& type.IsAssignableTo(iMessage));
}

public IEnumerable<Type> GetProtobufServiceClientTypes()
{
Type? clientBase = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.ClientBase", null)
?? AssemblyContext.LoadFromAssemblyName("Grpc.Core.Api")
.GetType("Grpc.Core.ClientBase");

return LoadedTypes.Where(
type => type is { IsNested: true, IsAbstract: false }
&& type.IsAssignableTo(clientBase));
}

public IEnumerable<Type> GetProtobufServiceServerTypes()
{
Type? bindServiceMethodAttribute = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.BindServiceMethodAttribute", null)
?? AssemblyContext.LoadFromAssemblyName("Grpc.Core.Api")
.GetType("Grpc.Core.BindServiceMethodAttribute");

return LoadedTypes.Where(
type => type is { IsNested: true, IsAbstract: true, DeclaringType: { IsNested: false, IsSealed: true, IsAbstract: true } }
&& type.GetCustomAttributesData().Any(attribute => attribute.AttributeType == bindServiceMethodAttribute));
}

public void Dispose() =>
Expand Down
46 changes: 0 additions & 46 deletions src/LibProtodec/Enum.cs

This file was deleted.

26 changes: 0 additions & 26 deletions src/LibProtodec/FieldTypeName.cs

This file was deleted.

98 changes: 0 additions & 98 deletions src/LibProtodec/Message.cs

This file was deleted.

29 changes: 29 additions & 0 deletions src/LibProtodec/Models/Fields/EnumField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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/.

namespace LibProtodec.Models.Fields;

public sealed class EnumField
{
public required string Name { get; init; }
public required int Id { get; init; }

public bool IsObsolete { get; init; }

public void WriteTo(System.IO.TextWriter writer)
{
writer.Write(Name);
writer.Write(" = ");
writer.Write(Id);

if (IsObsolete)
{
writer.Write(" [deprecated = true]");
}

writer.WriteLine(';');
}
}
42 changes: 42 additions & 0 deletions src/LibProtodec/Models/Fields/MessageField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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.IO;
using LibProtodec.Models.TopLevels;
using LibProtodec.Models.Types;

namespace LibProtodec.Models.Fields;

public sealed class MessageField
{
public required IType Type { get; init; }
public required string Name { get; init; }
public required int Id { get; init; }

public bool IsObsolete { get; init; }
public bool HasHasProp { get; init; }

public void WriteTo(TextWriter writer, TopLevel topLevel, bool isOneOf)
{
if (HasHasProp && !isOneOf && Type is not Repeated)
{
writer.Write("optional ");
}

Protobuf.WriteTypeNameTo(writer, Type, topLevel);
writer.Write(' ');
writer.Write(Name);
writer.Write(" = ");
writer.Write(Id);

if (IsObsolete)
{
writer.Write(" [deprecated = true]");
}

writer.WriteLine(';');
}
}
60 changes: 60 additions & 0 deletions src/LibProtodec/Models/Fields/ServiceMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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.CodeDom.Compiler;
using LibProtodec.Models.TopLevels;
using LibProtodec.Models.Types;

namespace LibProtodec.Models.Fields;

public sealed class ServiceMethod
{
public required string Name { get; init; }
public required IType RequestType { get; init; }
public required IType ResponseType { get; init; }

public bool IsRequestStreamed { get; init; }
public bool IsResponseStreamed { get; init; }
public bool IsObsolete { get; init; }

public void WriteTo(IndentedTextWriter writer, TopLevel topLevel)
{
writer.Write("rpc ");
writer.Write(Name);
writer.Write(" (");

if (IsRequestStreamed)
{
writer.Write("stream ");
}

Protobuf.WriteTypeNameTo(writer, RequestType, topLevel);
writer.Write(") returns (");

if (IsResponseStreamed)
{
writer.Write("stream ");
}

Protobuf.WriteTypeNameTo(writer, ResponseType, topLevel);
writer.Write(')');

if (IsObsolete)
{
writer.WriteLine(" {");
writer.Indent++;

Protobuf.WriteOptionTo(writer, "deprecated", "true");

writer.Indent--;
writer.WriteLine('}');
}
else
{
writer.WriteLine(';');
}
}
}
Loading

0 comments on commit d43deef

Please sign in to comment.