From 1c1e14c58ff601d4170998c081906f9ef44dfd28 Mon Sep 17 00:00:00 2001 From: Xpl0itR Date: Sun, 6 Oct 2024 00:22:25 +0100 Subject: [PATCH] Make the necessary infrastructural changes for parsing protos from lua source files --- README.md | 1 + src/LibProtodec/LibProtodec.csproj | 1 + src/LibProtodec/Loaders/LuaSourceLoader.cs | 41 ++++++++++++ src/LibProtodec/LoggerMessages.cs | 3 + src/LibProtodec/ProtodecContext.Lua.cs | 18 +++++ src/LibProtodec/ProtodecContext.cs | 2 +- src/protodec/Program.cs | 77 ++++++++++++++++++++-- 7 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 src/LibProtodec/Loaders/LuaSourceLoader.cs create mode 100644 src/LibProtodec/ProtodecContext.Lua.cs diff --git a/README.md b/README.md index 03f7d98..b7f032a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Options: Commands: il2cpp Use LibCpp2IL backend to directly load Il2Cpp compiled game assembly. EXPERIMENTAL. + lua Use Lua AST backend to load Lua source files. ``` See per-command help message for more info. diff --git a/src/LibProtodec/LibProtodec.csproj b/src/LibProtodec/LibProtodec.csproj index a846f36..cabbf8f 100644 --- a/src/LibProtodec/LibProtodec.csproj +++ b/src/LibProtodec/LibProtodec.csproj @@ -18,6 +18,7 @@ + diff --git a/src/LibProtodec/Loaders/LuaSourceLoader.cs b/src/LibProtodec/Loaders/LuaSourceLoader.cs new file mode 100644 index 0000000..3789757 --- /dev/null +++ b/src/LibProtodec/Loaders/LuaSourceLoader.cs @@ -0,0 +1,41 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using Loretta.CodeAnalysis; +using Loretta.CodeAnalysis.Lua; +using Loretta.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace LibProtodec.Loaders; + +public sealed class LuaSourceLoader +{ + public IReadOnlyList LoadedSyntaxTrees { get; } + + public LuaSourceLoader(string sourcePath, ILogger? logger = null) + { + LoadedSyntaxTrees = File.Exists(sourcePath) + ? [LoadSyntaxTreeFromSourceFile(sourcePath)] + : Directory.EnumerateFiles(sourcePath, searchPattern: "*.lua") + .Select(LoadSyntaxTreeFromSourceFile) + .ToList(); + + logger?.LogLoadedLuaSyntaxTrees(LoadedSyntaxTrees.Count); + } + + private static SyntaxTree LoadSyntaxTreeFromSourceFile(string filePath) + { + using FileStream fileStream = File.OpenRead(filePath); + + return LuaSyntaxTree.ParseText( + SourceText.From(fileStream), + null, // TODO: maybe expose the options parameter + Path.GetFileName(filePath)); + } +} \ No newline at end of file diff --git a/src/LibProtodec/LoggerMessages.cs b/src/LibProtodec/LoggerMessages.cs index d69d10a..632b930 100644 --- a/src/LibProtodec/LoggerMessages.cs +++ b/src/LibProtodec/LoggerMessages.cs @@ -18,6 +18,9 @@ internal static partial class LoggerMessages [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); + [LoggerMessage(Level = LogLevel.Information, Message = "Loaded {treeCount} Lua syntax trees for parsing.")] + internal static partial void LogLoadedLuaSyntaxTrees(this ILogger logger, int treeCount); + [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as enum \"{name}\".")] internal static partial void LogParsedEnum(this ILogger logger, string name); diff --git a/src/LibProtodec/ProtodecContext.Lua.cs b/src/LibProtodec/ProtodecContext.Lua.cs new file mode 100644 index 0000000..2125685 --- /dev/null +++ b/src/LibProtodec/ProtodecContext.Lua.cs @@ -0,0 +1,18 @@ +// 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; +using Loretta.CodeAnalysis; + +namespace LibProtodec; + +partial class ProtodecContext +{ + public void ParseLuaSyntaxTree(SyntaxTree ast) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibProtodec/ProtodecContext.cs b/src/LibProtodec/ProtodecContext.cs index 71f7f71..48b28a7 100644 --- a/src/LibProtodec/ProtodecContext.cs +++ b/src/LibProtodec/ProtodecContext.cs @@ -24,7 +24,7 @@ namespace LibProtodec; public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName); // ReSharper disable ClassWithVirtualMembersNeverInherited.Global, MemberCanBePrivate.Global, MemberCanBeProtected.Global, PropertyCanBeMadeInitOnly.Global -public class ProtodecContext +public partial class ProtodecContext { private readonly Dictionary _parsed = []; diff --git a/src/protodec/Program.cs b/src/protodec/Program.cs index 792d6de..7e4a40c 100644 --- a/src/protodec/Program.cs +++ b/src/protodec/Program.cs @@ -14,6 +14,7 @@ using LibProtodec; using LibProtodec.Loaders; using LibProtodec.Models.Cil; using LibProtodec.Models.Protobuf; +using Loretta.CodeAnalysis; using Microsoft.Extensions.Logging; ConsoleApp.ConsoleAppBuilder app = ConsoleApp.Create(); @@ -47,10 +48,10 @@ internal sealed class Commands using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel); ILogger logger = CreateProtodecLogger(loggerFactory); - logger.LogInformation("Loading target assemblies..."); + logger.LogInformation("Loading CIL assemblies..."); using ClrAssemblyLoader loader = new(targetPath, loggerFactory.CreateLogger()); - Handle( + HandleCil( loader, outPath, skipEnums, @@ -100,10 +101,10 @@ internal sealed class Commands using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel); ILogger logger = CreateProtodecLogger(loggerFactory); - logger.LogInformation("Loading target assemblies..."); + logger.LogInformation("Loading Il2Cpp assembly and metadata..."); Il2CppAssemblyLoader loader = new(gameAssembly, globalMetadata, unityVer, loggerFactory); - Handle( + HandleCil( loader, outPath, skipEnums, @@ -115,7 +116,73 @@ internal sealed class Commands logger); } - private static void Handle( + /// + /// Use Lua AST backend to load Lua source files. + /// + /// Either the path to the target lua file or a directory of lua files, all of which be parsed. + /// An existing directory to output into individual files, otherwise output to a single file. + /// Logging severity level. + [Command("lua")] + public void Lua( + [Argument] string targetPath, + [Argument] string outPath, + LogLevel logLevel = LogLevel.Information) + { + using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel); + ILogger logger = CreateProtodecLogger(loggerFactory); + + logger.LogInformation("Loading Lua sources..."); + LuaSourceLoader loader = new(targetPath, loggerFactory.CreateLogger()); + + ProtodecContext ctx = new() + { + Logger = loggerFactory.CreateLogger() + }; + + logger.LogInformation("Parsing Lua syntax trees..."); + foreach (SyntaxTree ast in loader.LoadedSyntaxTrees) + { + ctx.ParseLuaSyntaxTree(ast); + } + + // NOTE: I'm duplicating this code rather than refactoring because I have a lot of uncommited changes on another computer, + // therefore, so as to not give myself brain cancer, I will not touch anything that would cause git conflicts. + const string indent = " "; + if (Directory.Exists(outPath)) + { + logger.LogInformation("Writing {count} Protobuf files to \"{path}\"...", ctx.Protobufs.Count, outPath); + + HashSet writtenFiles = []; + foreach (Protobuf protobuf in ctx.Protobufs) + { + // This workaround stops files from being overwritten in the case of a naming conflict, + // however the actual conflict will still have to be resolved manually + string fileName = protobuf.FileName; + while (!writtenFiles.Add(fileName)) + { + fileName = '_' + fileName; + } + + string protobufPath = Path.Join(outPath, fileName); + + using StreamWriter streamWriter = new(protobufPath); + using IndentedTextWriter indentWriter = new(streamWriter, indent); + + protobuf.WriteTo(indentWriter); + } + } + else + { + logger.LogInformation("Writing Protobufs as a single file to \"{path}\"...", outPath); + + using StreamWriter streamWriter = new(outPath); + using IndentedTextWriter indentWriter = new(streamWriter, indent); + + ctx.WriteAllTo(indentWriter); + } + } + + private static void HandleCil( CilAssemblyLoader loader, string outPath, bool skipEnums,