mirror of https://github.com/Xpl0itR/protodec.git
Compare commits
2 Commits
1c1e14c58f
...
b3ff1fe608
Author | SHA1 | Date |
---|---|---|
Xpl0itR | b3ff1fe608 | |
Xpl0itR | 41db9569dc |
|
@ -35,7 +35,8 @@ public sealed class LuaSourceLoader
|
|||
|
||||
return LuaSyntaxTree.ParseText(
|
||||
SourceText.From(fileStream),
|
||||
null, // TODO: maybe expose the options parameter
|
||||
null, // TODO: maybe expose this as a parameter
|
||||
Path.GetFileName(filePath));
|
||||
//TODO: consider checking lua source validity via SyntaxTree.GetDiagnostics()
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@ 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.Warning, Message = "{msg} is not yet implemented. Open an issue if this is something you need.")]
|
||||
internal static partial void LogNotImplemented(this ILogger logger, string msg);
|
||||
|
||||
[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);
|
||||
|
||||
|
|
|
@ -4,17 +4,21 @@
|
|||
// 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 CommunityToolkit.Diagnostics;
|
||||
|
||||
namespace LibProtodec.Models.Protobuf.Fields;
|
||||
|
||||
public sealed class EnumField
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required int Id { get; init; }
|
||||
public string? Name { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
public bool IsObsolete { get; init; }
|
||||
|
||||
public void WriteTo(System.IO.TextWriter writer)
|
||||
{
|
||||
Guard.IsNotNull(Name);
|
||||
|
||||
writer.Write(Name);
|
||||
writer.Write(" = ");
|
||||
writer.Write(Id);
|
||||
|
|
|
@ -5,29 +5,43 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.IO;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using LibProtodec.Models.Protobuf.TopLevels;
|
||||
using LibProtodec.Models.Protobuf.Types;
|
||||
|
||||
namespace LibProtodec.Models.Protobuf.Fields;
|
||||
|
||||
public sealed class MessageField(Message declaringMessage)
|
||||
public sealed class MessageField
|
||||
{
|
||||
public required IProtobufType Type { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required int Id { get; init; }
|
||||
public Message? DeclaringMessage { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
public bool IsObsolete { get; init; }
|
||||
public bool HasHasProp { get; init; }
|
||||
|
||||
public void WriteTo(TextWriter writer, bool isOneOf)
|
||||
{
|
||||
if (HasHasProp && !isOneOf && Type is not Repeated)
|
||||
Guard.IsNotNull(Type);
|
||||
Guard.IsNotNull(Name);
|
||||
Guard.IsNotNull(DeclaringMessage);
|
||||
|
||||
if (IsOptional || (HasHasProp && !isOneOf && Type is not Repeated))
|
||||
{
|
||||
writer.Write("optional ");
|
||||
}
|
||||
|
||||
if (IsRequired)
|
||||
{
|
||||
writer.Write("required ");
|
||||
}
|
||||
|
||||
writer.Write(
|
||||
declaringMessage.QualifyTypeName(Type));
|
||||
DeclaringMessage.QualifyTypeName(Type));
|
||||
writer.Write(' ');
|
||||
writer.Write(Name);
|
||||
writer.Write(" = ");
|
||||
|
|
|
@ -8,23 +8,39 @@ using System.CodeDom.Compiler;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using LibProtodec.Models.Protobuf.TopLevels;
|
||||
|
||||
namespace LibProtodec.Models.Protobuf;
|
||||
|
||||
public sealed class Protobuf
|
||||
{
|
||||
private int _version = 3;
|
||||
private string? _fileName;
|
||||
private HashSet<string>? _imports;
|
||||
|
||||
public readonly List<TopLevel> TopLevels = [];
|
||||
|
||||
public string? Edition { get; set; }
|
||||
public string? AssemblyName { get; init; }
|
||||
public string? Namespace { get; init; }
|
||||
public string? SourceName { get; init; }
|
||||
public string? Edition { get; set; }
|
||||
public string? CilNamespace { get; init; }
|
||||
|
||||
public string FileName =>
|
||||
_fileName ??= $"{string.Join('_', TopLevels.Select(static topLevel => topLevel.Name))}.proto";
|
||||
public string FileName
|
||||
{
|
||||
get => _fileName ??= $"{string.Join('_', TopLevels.Select(static topLevel => topLevel.Name))}.proto";
|
||||
set => _fileName = value;
|
||||
}
|
||||
|
||||
public int Version
|
||||
{
|
||||
get => _version;
|
||||
set
|
||||
{
|
||||
Guard.IsBetweenOrEqualTo(value, 2, 3);
|
||||
_version = value;
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<string> Imports =>
|
||||
_imports ??= [];
|
||||
|
@ -39,10 +55,16 @@ public sealed class Protobuf
|
|||
writer.WriteLine(AssemblyName);
|
||||
}
|
||||
|
||||
if (SourceName is not null)
|
||||
{
|
||||
writer.Write("// Source: ");
|
||||
writer.WriteLine(SourceName);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
writer.WriteLine(
|
||||
Edition is null
|
||||
? """syntax = "proto3";"""
|
||||
? $"""syntax = "proto{Version}";"""
|
||||
: $"""edition = "{Edition}";""");
|
||||
|
||||
if (_imports is not null)
|
||||
|
@ -57,10 +79,10 @@ public sealed class Protobuf
|
|||
}
|
||||
}
|
||||
|
||||
if (Namespace is not null)
|
||||
if (CilNamespace is not null)
|
||||
{
|
||||
writer.WriteLine();
|
||||
WriteOptionTo(writer, "csharp_namespace", Namespace, true);
|
||||
WriteOptionTo(writer, "csharp_namespace", CilNamespace, true);
|
||||
}
|
||||
|
||||
foreach (TopLevel topLevel in TopLevels)
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace LibProtodec.Models.Protobuf.TopLevels;
|
|||
|
||||
public abstract class TopLevel
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Name { get; set; }
|
||||
|
||||
public bool IsObsolete { get; init; }
|
||||
public Protobuf? Protobuf { get; set; }
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// 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 CommunityToolkit.Diagnostics;
|
||||
using LibProtodec.Models.Protobuf.TopLevels;
|
||||
|
||||
namespace LibProtodec.Models.Protobuf.Types;
|
||||
|
||||
public sealed class Descriptor : IProtobufType
|
||||
{
|
||||
public int TypeIndex { get; set; }
|
||||
public bool IsRepeated { get; set; }
|
||||
public /*TopLevel*/IProtobufType? TopLevelType { get; set; }
|
||||
|
||||
public string Name =>
|
||||
Type.Name;
|
||||
|
||||
public IProtobufType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
IProtobufType type = TopLevelType as IProtobufType ?? TypeIndex switch
|
||||
{
|
||||
1 => Scalar.Double,
|
||||
2 => Scalar.Float,
|
||||
3 => Scalar.Int64,
|
||||
4 => Scalar.UInt64,
|
||||
5 => Scalar.Int32,
|
||||
6 => Scalar.Fixed64,
|
||||
7 => Scalar.Fixed32,
|
||||
8 => Scalar.Bool,
|
||||
9 => Scalar.String,
|
||||
10 => ThrowHelper.ThrowNotSupportedException<IProtobufType>("Parsing proto2 groups are not supported. Open an issue if you need this."),
|
||||
12 => Scalar.Bytes,
|
||||
13 => Scalar.UInt32,
|
||||
15 => Scalar.SFixed32,
|
||||
16 => Scalar.SFixed64,
|
||||
17 => Scalar.SInt32,
|
||||
18 => Scalar.SInt64,
|
||||
_ => ThrowHelper.ThrowArgumentOutOfRangeException<IProtobufType>()
|
||||
};
|
||||
|
||||
if (IsRepeated)
|
||||
{
|
||||
type = new Repeated(type);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,15 +4,222 @@
|
|||
// 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 System.IO;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using LibProtodec.Models.Protobuf;
|
||||
using LibProtodec.Models.Protobuf.Fields;
|
||||
using LibProtodec.Models.Protobuf.TopLevels;
|
||||
using LibProtodec.Models.Protobuf.Types;
|
||||
using Loretta.CodeAnalysis;
|
||||
using Loretta.CodeAnalysis.Lua.Syntax;
|
||||
using SystemEx;
|
||||
|
||||
namespace LibProtodec;
|
||||
|
||||
partial class ProtodecContext
|
||||
{
|
||||
public void ParseLuaSyntaxTree(SyntaxTree ast)
|
||||
public Protobuf ParseLuaSyntaxTree(SyntaxTree ast)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
CompilationUnitSyntax root = (CompilationUnitSyntax)ast.GetRoot();
|
||||
SyntaxList<StatementSyntax> statements = root.Statements.Statements;
|
||||
|
||||
bool importedProtobufLib = false;
|
||||
Dictionary<string, object> pbTable = [];
|
||||
Protobuf protobuf = new()
|
||||
{
|
||||
Version = 2,
|
||||
SourceName = ast.FilePath
|
||||
};
|
||||
|
||||
foreach (StatementSyntax statement in statements)
|
||||
{
|
||||
switch (statement)
|
||||
{
|
||||
case LocalVariableDeclarationStatementSyntax
|
||||
{
|
||||
EqualsValues.Values: [FunctionCallExpressionSyntax { Expression: IdentifierNameSyntax { Name: "require" } } call]
|
||||
}:
|
||||
switch (call.Argument)
|
||||
{
|
||||
case StringFunctionArgumentSyntax { Expression.Token.ValueText: "protobuf/protobuf" }:
|
||||
importedProtobufLib = true;
|
||||
break;
|
||||
case ExpressionListFunctionArgumentSyntax { Expressions: [LiteralExpressionSyntax { Token.ValueText: {} import }] }:
|
||||
import = Path.GetFileNameWithoutExtension(import).TrimEnd("_pb"); //todo: handle imports properly
|
||||
protobuf.Imports.Add($"{import}.proto");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AssignmentStatementSyntax
|
||||
{
|
||||
Variables: [MemberAccessExpressionSyntax
|
||||
{
|
||||
Expression: IdentifierNameSyntax,
|
||||
MemberName.ValueText: {} tableKey
|
||||
}],
|
||||
EqualsValues.Values: [FunctionCallExpressionSyntax { Expression: MemberAccessExpressionSyntax { MemberName.ValueText: {} factory } }]
|
||||
}:
|
||||
switch (factory)
|
||||
{
|
||||
case "Descriptor":
|
||||
Message message = new()
|
||||
{
|
||||
Name = tableKey,
|
||||
Protobuf = protobuf
|
||||
};
|
||||
protobuf.TopLevels.Add(message);
|
||||
pbTable.Add(tableKey, message);
|
||||
break;
|
||||
case "EnumDescriptor":
|
||||
Enum @enum = new()
|
||||
{
|
||||
Name = tableKey,
|
||||
Protobuf = protobuf
|
||||
};
|
||||
protobuf.TopLevels.Add(@enum);
|
||||
pbTable.Add(tableKey, @enum);
|
||||
break;
|
||||
case "FieldDescriptor":
|
||||
pbTable.Add(tableKey, new MessageField { Type = new Descriptor() });
|
||||
break;
|
||||
case "EnumValueDescriptor":
|
||||
pbTable.Add(tableKey, new EnumField());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AssignmentStatementSyntax
|
||||
{
|
||||
Variables: [MemberAccessExpressionSyntax
|
||||
{
|
||||
Expression: MemberAccessExpressionSyntax { MemberName.ValueText: {} tableKey },
|
||||
MemberName.ValueText: { } memberName
|
||||
}],
|
||||
EqualsValues.Values: [{ } valueExpr]
|
||||
}:
|
||||
var valueTableElements =
|
||||
(valueExpr as TableConstructorExpressionSyntax)?.Fields
|
||||
.Cast<UnkeyedTableFieldSyntax>()
|
||||
.Select(static element => element.Value);
|
||||
object? valueLiteral = (valueExpr as LiteralExpressionSyntax)?.Token.Value;
|
||||
|
||||
switch (pbTable[tableKey])
|
||||
{
|
||||
case Message message:
|
||||
switch (memberName)
|
||||
{
|
||||
case "name":
|
||||
message.Name = (string)valueLiteral!;
|
||||
break;
|
||||
case "fields":
|
||||
foreach (MessageField messageField in valueTableElements!
|
||||
.Cast<MemberAccessExpressionSyntax>()
|
||||
.Select(x => pbTable[x.MemberName.ValueText])
|
||||
.Cast<MessageField>())
|
||||
{
|
||||
messageField.DeclaringMessage = message;
|
||||
message.Fields.Add(messageField.Id, messageField);
|
||||
}
|
||||
break;
|
||||
case "nested_types":
|
||||
if (valueTableElements!.Any())
|
||||
Logger?.LogNotImplemented("Parsing nested messages from lua");
|
||||
break;
|
||||
case "enum_types":
|
||||
if (valueTableElements!.Any())
|
||||
Logger?.LogNotImplemented("Parsing nested enums from lua");
|
||||
break;
|
||||
case "is_extendable":
|
||||
if ((bool)valueLiteral!)
|
||||
Logger?.LogNotImplemented("Parsing message extensions from lua");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Enum @enum:
|
||||
switch (memberName)
|
||||
{
|
||||
case "name":
|
||||
@enum.Name = (string)valueLiteral!;
|
||||
break;
|
||||
case "values":
|
||||
@enum.Fields.AddRange(
|
||||
valueTableElements!
|
||||
.Cast<MemberAccessExpressionSyntax>()
|
||||
.Select(x => pbTable[x.MemberName.ValueText])
|
||||
.Cast<EnumField>());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MessageField messageField:
|
||||
Descriptor descriptor = (Descriptor)messageField.Type;
|
||||
switch (memberName)
|
||||
{
|
||||
case "name":
|
||||
messageField.Name = (string)valueLiteral!;
|
||||
break;
|
||||
case "number":
|
||||
messageField.Id = (int)(double)valueLiteral!;
|
||||
break;
|
||||
case "label":
|
||||
switch ((int)(double)valueLiteral!)
|
||||
{
|
||||
case 1:
|
||||
messageField.IsOptional = true;
|
||||
break;
|
||||
case 2:
|
||||
messageField.IsRequired = true;
|
||||
break;
|
||||
case 3:
|
||||
descriptor.IsRepeated = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "type":
|
||||
descriptor.TypeIndex = (int)(double)valueLiteral!;
|
||||
break;
|
||||
case "message_type":
|
||||
case "enum_type":
|
||||
string typeTableKey = ((MemberAccessExpressionSyntax)valueExpr).MemberName.ValueText;
|
||||
if (pbTable.TryGetValue(typeTableKey, out object? topLevel)) //TODO: if this is false, then the top level is from an import, we will need to handle this
|
||||
descriptor.TopLevelType = (IProtobufType)topLevel;
|
||||
else
|
||||
descriptor.TopLevelType = new Scalar(typeTableKey); // temporary hack
|
||||
break;
|
||||
case "has_default_value":
|
||||
if ((bool)valueLiteral!)
|
||||
Logger?.LogNotImplemented("Parsing default field values from lua");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EnumField enumField:
|
||||
switch (memberName)
|
||||
{
|
||||
case "name":
|
||||
enumField.Name = (string)valueLiteral!;
|
||||
break;
|
||||
case "number" when valueExpr is UnaryExpressionSyntax { Operand: LiteralExpressionSyntax { Token.Value: { } negativeNumber } }:
|
||||
enumField.Id = -(int)(double)negativeNumber;
|
||||
break;
|
||||
case "number":
|
||||
enumField.Id = (int)(double)valueLiteral!;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ReturnStatementSyntax { Expressions: [IdentifierNameSyntax identifier] }:
|
||||
protobuf.FileName = $"{identifier.Name.TrimEnd("_pbTable")}.proto";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!importedProtobufLib)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidDataException();
|
||||
}
|
||||
|
||||
this.Protobufs.Add(protobuf);
|
||||
return protobuf;
|
||||
}
|
||||
}
|
|
@ -38,8 +38,6 @@ public partial class ProtodecContext
|
|||
{
|
||||
writer.WriteLine("// Decompiled with protodec");
|
||||
writer.WriteLine();
|
||||
writer.WriteLine("""syntax = "proto3";""");
|
||||
writer.WriteLine();
|
||||
|
||||
foreach (TopLevel topLevel in Protobufs.SelectMany(static proto => proto.TopLevels))
|
||||
{
|
||||
|
@ -119,13 +117,14 @@ public partial class ProtodecContext
|
|||
continue;
|
||||
}
|
||||
|
||||
MessageField field = new(message)
|
||||
MessageField field = new()
|
||||
{
|
||||
Type = ParseFieldType(propertyType, options, protobuf),
|
||||
Name = TranslateMessageFieldName(property.Name),
|
||||
Id = (int)idFields[fi].ConstantValue!,
|
||||
IsObsolete = HasObsoleteAttribute(property.CustomAttributes),
|
||||
HasHasProp = msgFieldHasHasProp
|
||||
HasHasProp = msgFieldHasHasProp,
|
||||
DeclaringMessage = message
|
||||
};
|
||||
|
||||
Logger?.LogParsedField(field.Name, field.Id, field.Type.Name);
|
||||
|
@ -499,7 +498,7 @@ public partial class ProtodecContext
|
|||
Protobuf protobuf = new()
|
||||
{
|
||||
AssemblyName = topLevelType.DeclaringAssemblyName,
|
||||
Namespace = topLevelType.Namespace
|
||||
CilNamespace = topLevelType.Namespace
|
||||
};
|
||||
|
||||
topLevel.Protobuf = protobuf;
|
||||
|
|
Loading…
Reference in New Issue