Compare commits

..

No commits in common. "b3ff1fe60893ec73c3c8c8b7c9e470fd274e626a" and "1c1e14c58ff601d4170998c081906f9ef44dfd28" have entirely different histories.

9 changed files with 24 additions and 328 deletions

View File

@ -35,8 +35,7 @@ public sealed class LuaSourceLoader
return LuaSyntaxTree.ParseText( return LuaSyntaxTree.ParseText(
SourceText.From(fileStream), SourceText.From(fileStream),
null, // TODO: maybe expose this as a parameter null, // TODO: maybe expose the options parameter
Path.GetFileName(filePath)); Path.GetFileName(filePath));
//TODO: consider checking lua source validity via SyntaxTree.GetDiagnostics()
} }
} }

View File

@ -15,9 +15,6 @@ internal static partial class LoggerMessages
[LoggerMessage(Level = LogLevel.Warning, Message = "Failed to locate corresponding id field; likely stripped or otherwise obfuscated.")] [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to locate corresponding id field; likely stripped or otherwise obfuscated.")]
internal static partial void LogFailedToLocateIdField(this ILogger logger); 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.")] [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); internal static partial void LogLoadedTypeAndAssemblyCount(this ILogger logger, int typeCount, int assemblyCount);

View File

@ -4,21 +4,17 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
using CommunityToolkit.Diagnostics;
namespace LibProtodec.Models.Protobuf.Fields; namespace LibProtodec.Models.Protobuf.Fields;
public sealed class EnumField public sealed class EnumField
{ {
public string? Name { get; set; } public required string Name { get; init; }
public int Id { get; set; } public required int Id { get; init; }
public bool IsObsolete { get; init; } public bool IsObsolete { get; init; }
public void WriteTo(System.IO.TextWriter writer) public void WriteTo(System.IO.TextWriter writer)
{ {
Guard.IsNotNull(Name);
writer.Write(Name); writer.Write(Name);
writer.Write(" = "); writer.Write(" = ");
writer.Write(Id); writer.Write(Id);

View File

@ -5,43 +5,29 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
using System.IO; using System.IO;
using CommunityToolkit.Diagnostics;
using LibProtodec.Models.Protobuf.TopLevels; using LibProtodec.Models.Protobuf.TopLevels;
using LibProtodec.Models.Protobuf.Types; using LibProtodec.Models.Protobuf.Types;
namespace LibProtodec.Models.Protobuf.Fields; namespace LibProtodec.Models.Protobuf.Fields;
public sealed class MessageField public sealed class MessageField(Message declaringMessage)
{ {
public required IProtobufType Type { get; init; } public required IProtobufType Type { get; init; }
public Message? DeclaringMessage { get; set; } public required string Name { get; init; }
public required int Id { get; init; }
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 IsObsolete { get; init; }
public bool HasHasProp { get; init; } public bool HasHasProp { get; init; }
public void WriteTo(TextWriter writer, bool isOneOf) public void WriteTo(TextWriter writer, bool isOneOf)
{ {
Guard.IsNotNull(Type); if (HasHasProp && !isOneOf && Type is not Repeated)
Guard.IsNotNull(Name);
Guard.IsNotNull(DeclaringMessage);
if (IsOptional || (HasHasProp && !isOneOf && Type is not Repeated))
{ {
writer.Write("optional "); writer.Write("optional ");
} }
if (IsRequired)
{
writer.Write("required ");
}
writer.Write( writer.Write(
DeclaringMessage.QualifyTypeName(Type)); declaringMessage.QualifyTypeName(Type));
writer.Write(' '); writer.Write(' ');
writer.Write(Name); writer.Write(Name);
writer.Write(" = "); writer.Write(" = ");

View File

@ -8,39 +8,23 @@ using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using CommunityToolkit.Diagnostics;
using LibProtodec.Models.Protobuf.TopLevels; using LibProtodec.Models.Protobuf.TopLevels;
namespace LibProtodec.Models.Protobuf; namespace LibProtodec.Models.Protobuf;
public sealed class Protobuf public sealed class Protobuf
{ {
private int _version = 3;
private string? _fileName; private string? _fileName;
private HashSet<string>? _imports; private HashSet<string>? _imports;
public readonly List<TopLevel> TopLevels = []; public readonly List<TopLevel> TopLevels = [];
public string? AssemblyName { get; init; }
public string? SourceName { get; init; }
public string? Edition { get; set; } public string? Edition { get; set; }
public string? CilNamespace { get; init; } public string? AssemblyName { get; init; }
public string? Namespace { get; init; }
public string FileName public string FileName =>
{ _fileName ??= $"{string.Join('_', TopLevels.Select(static topLevel => topLevel.Name))}.proto";
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 => public HashSet<string> Imports =>
_imports ??= []; _imports ??= [];
@ -55,16 +39,10 @@ public sealed class Protobuf
writer.WriteLine(AssemblyName); writer.WriteLine(AssemblyName);
} }
if (SourceName is not null)
{
writer.Write("// Source: ");
writer.WriteLine(SourceName);
}
writer.WriteLine(); writer.WriteLine();
writer.WriteLine( writer.WriteLine(
Edition is null Edition is null
? $"""syntax = "proto{Version}";""" ? """syntax = "proto3";"""
: $"""edition = "{Edition}";"""); : $"""edition = "{Edition}";""");
if (_imports is not null) if (_imports is not null)
@ -79,10 +57,10 @@ public sealed class Protobuf
} }
} }
if (CilNamespace is not null) if (Namespace is not null)
{ {
writer.WriteLine(); writer.WriteLine();
WriteOptionTo(writer, "csharp_namespace", CilNamespace, true); WriteOptionTo(writer, "csharp_namespace", Namespace, true);
} }
foreach (TopLevel topLevel in TopLevels) foreach (TopLevel topLevel in TopLevels)

View File

@ -12,7 +12,7 @@ namespace LibProtodec.Models.Protobuf.TopLevels;
public abstract class TopLevel public abstract class TopLevel
{ {
public required string Name { get; set; } public required string Name { get; init; }
public bool IsObsolete { get; init; } public bool IsObsolete { get; init; }
public Protobuf? Protobuf { get; set; } public Protobuf? Protobuf { get; set; }

View File

@ -1,54 +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 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;
}
}
}

View File

@ -4,222 +4,15 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
using System.Collections.Generic; using System;
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;
using Loretta.CodeAnalysis.Lua.Syntax;
using SystemEx;
namespace LibProtodec; namespace LibProtodec;
partial class ProtodecContext partial class ProtodecContext
{ {
public Protobuf ParseLuaSyntaxTree(SyntaxTree ast) public void ParseLuaSyntaxTree(SyntaxTree ast)
{ {
CompilationUnitSyntax root = (CompilationUnitSyntax)ast.GetRoot(); throw new NotImplementedException();
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;
} }
} }

View File

@ -38,6 +38,8 @@ public partial class ProtodecContext
{ {
writer.WriteLine("// Decompiled with protodec"); writer.WriteLine("// Decompiled with protodec");
writer.WriteLine(); writer.WriteLine();
writer.WriteLine("""syntax = "proto3";""");
writer.WriteLine();
foreach (TopLevel topLevel in Protobufs.SelectMany(static proto => proto.TopLevels)) foreach (TopLevel topLevel in Protobufs.SelectMany(static proto => proto.TopLevels))
{ {
@ -117,14 +119,13 @@ public partial class ProtodecContext
continue; continue;
} }
MessageField field = new() MessageField field = new(message)
{ {
Type = ParseFieldType(propertyType, options, protobuf), Type = ParseFieldType(propertyType, options, protobuf),
Name = TranslateMessageFieldName(property.Name), Name = TranslateMessageFieldName(property.Name),
Id = (int)idFields[fi].ConstantValue!, Id = (int)idFields[fi].ConstantValue!,
IsObsolete = HasObsoleteAttribute(property.CustomAttributes), IsObsolete = HasObsoleteAttribute(property.CustomAttributes),
HasHasProp = msgFieldHasHasProp, HasHasProp = msgFieldHasHasProp
DeclaringMessage = message
}; };
Logger?.LogParsedField(field.Name, field.Id, field.Type.Name); Logger?.LogParsedField(field.Name, field.Id, field.Type.Name);
@ -498,7 +499,7 @@ public partial class ProtodecContext
Protobuf protobuf = new() Protobuf protobuf = new()
{ {
AssemblyName = topLevelType.DeclaringAssemblyName, AssemblyName = topLevelType.DeclaringAssemblyName,
CilNamespace = topLevelType.Namespace Namespace = topLevelType.Namespace
}; };
topLevel.Protobuf = protobuf; topLevel.Protobuf = protobuf;