Make the necessary infrastructural changes for parsing protos from lua source files

This commit is contained in:
Xpl0itR 2024-10-06 00:22:25 +01:00
parent bf2feae2e9
commit 1c1e14c58f
7 changed files with 137 additions and 6 deletions

View File

@ -22,6 +22,7 @@ Options:
Commands: Commands:
il2cpp Use LibCpp2IL backend to directly load Il2Cpp compiled game assembly. EXPERIMENTAL. 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. See per-command help message for more info.

View File

@ -18,6 +18,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Loretta.CodeAnalysis.Lua" Version="0.2.12" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0-preview.6.24327.7" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0-preview.6.24327.7" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Samboy063.LibCpp2IL" Version="2022.1.0-development.1027" /> <PackageReference Include="Samboy063.LibCpp2IL" Version="2022.1.0-development.1027" />

View File

@ -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<SyntaxTree> LoadedSyntaxTrees { get; }
public LuaSourceLoader(string sourcePath, ILogger<LuaSourceLoader>? 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));
}
}

View File

@ -18,6 +18,9 @@ internal static partial class LoggerMessages
[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);
[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}\".")] [LoggerMessage(Level = LogLevel.Debug, Message = "Parsed as enum \"{name}\".")]
internal static partial void LogParsedEnum(this ILogger logger, string name); internal static partial void LogParsedEnum(this ILogger logger, string name);

View File

@ -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();
}
}

View File

@ -24,7 +24,7 @@ namespace LibProtodec;
public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName); public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName);
// ReSharper disable ClassWithVirtualMembersNeverInherited.Global, MemberCanBePrivate.Global, MemberCanBeProtected.Global, PropertyCanBeMadeInitOnly.Global // ReSharper disable ClassWithVirtualMembersNeverInherited.Global, MemberCanBePrivate.Global, MemberCanBeProtected.Global, PropertyCanBeMadeInitOnly.Global
public class ProtodecContext public partial class ProtodecContext
{ {
private readonly Dictionary<string, TopLevel> _parsed = []; private readonly Dictionary<string, TopLevel> _parsed = [];

View File

@ -14,6 +14,7 @@ using LibProtodec;
using LibProtodec.Loaders; using LibProtodec.Loaders;
using LibProtodec.Models.Cil; using LibProtodec.Models.Cil;
using LibProtodec.Models.Protobuf; using LibProtodec.Models.Protobuf;
using Loretta.CodeAnalysis;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
ConsoleApp.ConsoleAppBuilder app = ConsoleApp.Create(); ConsoleApp.ConsoleAppBuilder app = ConsoleApp.Create();
@ -47,10 +48,10 @@ internal sealed class Commands
using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel); using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel);
ILogger logger = CreateProtodecLogger(loggerFactory); ILogger logger = CreateProtodecLogger(loggerFactory);
logger.LogInformation("Loading target assemblies..."); logger.LogInformation("Loading CIL assemblies...");
using ClrAssemblyLoader loader = new(targetPath, loggerFactory.CreateLogger<ClrAssemblyLoader>()); using ClrAssemblyLoader loader = new(targetPath, loggerFactory.CreateLogger<ClrAssemblyLoader>());
Handle( HandleCil(
loader, loader,
outPath, outPath,
skipEnums, skipEnums,
@ -100,10 +101,10 @@ internal sealed class Commands
using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel); using ILoggerFactory loggerFactory = CreateLoggerFactory(logLevel);
ILogger logger = CreateProtodecLogger(loggerFactory); ILogger logger = CreateProtodecLogger(loggerFactory);
logger.LogInformation("Loading target assemblies..."); logger.LogInformation("Loading Il2Cpp assembly and metadata...");
Il2CppAssemblyLoader loader = new(gameAssembly, globalMetadata, unityVer, loggerFactory); Il2CppAssemblyLoader loader = new(gameAssembly, globalMetadata, unityVer, loggerFactory);
Handle( HandleCil(
loader, loader,
outPath, outPath,
skipEnums, skipEnums,
@ -115,7 +116,73 @@ internal sealed class Commands
logger); logger);
} }
private static void Handle( /// <summary>
/// Use Lua AST backend to load Lua source files.
/// </summary>
/// <param name="targetPath">Either the path to the target lua file or a directory of lua files, all of which be parsed.</param>
/// <param name="outPath">An existing directory to output into individual files, otherwise output to a single file.</param>
/// <param name="logLevel">Logging severity level.</param>
[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<LuaSourceLoader>());
ProtodecContext ctx = new()
{
Logger = loggerFactory.CreateLogger<ProtodecContext>()
};
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<string> 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, CilAssemblyLoader loader,
string outPath, string outPath,
bool skipEnums, bool skipEnums,