mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add a default, reflection-less Result name resolver
This commit is contained in:
parent
53c8dceb87
commit
14dd2190a5
7 changed files with 265 additions and 31 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -266,3 +266,5 @@ global.json
|
||||||
|
|
||||||
!tests/LibHac.Tests/CryptoTests/TestVectors/*
|
!tests/LibHac.Tests/CryptoTests/TestVectors/*
|
||||||
**/DisasmoBin/
|
**/DisasmoBin/
|
||||||
|
|
||||||
|
ResultNameResolver.Generated.cs
|
|
@ -5,6 +5,8 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using CsvHelper;
|
using CsvHelper;
|
||||||
using CsvHelper.Configuration;
|
using CsvHelper.Configuration;
|
||||||
|
@ -25,14 +27,17 @@ namespace LibHacBuild.CodeGen
|
||||||
ValidateResults(modules);
|
ValidateResults(modules);
|
||||||
ValidateHierarchy(modules);
|
ValidateHierarchy(modules);
|
||||||
CheckIfAggressiveInliningNeeded(modules);
|
CheckIfAggressiveInliningNeeded(modules);
|
||||||
SetOutputPaths(modules);
|
|
||||||
|
|
||||||
foreach (ModuleInfo module in modules)
|
foreach (ModuleInfo module in modules)
|
||||||
{
|
{
|
||||||
string moduleResultFile = PrintModule(module);
|
string moduleResultFile = PrintModule(module);
|
||||||
|
|
||||||
WriteOutput(module, moduleResultFile);
|
WriteOutput(module.Path, moduleResultFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte[] archive = BuildArchive(modules);
|
||||||
|
string archiveStr = PrintArchive(archive);
|
||||||
|
WriteOutput("LibHac/ResultNameResolver.Generated.cs", archiveStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModuleInfo[] ReadResults()
|
private static ModuleInfo[] ReadResults()
|
||||||
|
@ -72,7 +77,11 @@ namespace LibHacBuild.CodeGen
|
||||||
{
|
{
|
||||||
foreach (ModuleInfo module in modules)
|
foreach (ModuleInfo module in modules)
|
||||||
{
|
{
|
||||||
foreach (ResultInfo result in module.Results.Where(x => string.IsNullOrWhiteSpace(x.Name)))
|
foreach (ResultInfo result in module.Results)
|
||||||
|
{
|
||||||
|
result.FullName = $"Result{module.Name}{result.Name}";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(result.Name))
|
||||||
{
|
{
|
||||||
if (result.IsRange)
|
if (result.IsRange)
|
||||||
{
|
{
|
||||||
|
@ -86,6 +95,7 @@ namespace LibHacBuild.CodeGen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void ValidateResults(ModuleInfo[] modules)
|
private static void ValidateResults(ModuleInfo[] modules)
|
||||||
{
|
{
|
||||||
|
@ -143,16 +153,6 @@ namespace LibHacBuild.CodeGen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetOutputPaths(ModuleInfo[] modules)
|
|
||||||
{
|
|
||||||
string rootPath = FindProjectDirectory();
|
|
||||||
|
|
||||||
foreach (ModuleInfo module in modules.Where(x => !string.IsNullOrWhiteSpace(x.Path)))
|
|
||||||
{
|
|
||||||
module.FullPath = Path.Combine(rootPath, module.Path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string PrintModule(ModuleInfo module)
|
private static string PrintModule(ModuleInfo module)
|
||||||
{
|
{
|
||||||
var sb = new IndentingStringBuilder();
|
var sb = new IndentingStringBuilder();
|
||||||
|
@ -283,19 +283,22 @@ namespace LibHacBuild.CodeGen
|
||||||
|
|
||||||
// Write the file only if it has changed
|
// Write the file only if it has changed
|
||||||
// Preserve the UTF-8 BOM usage if the file already exists
|
// Preserve the UTF-8 BOM usage if the file already exists
|
||||||
private static void WriteOutput(ModuleInfo module, string text)
|
private static void WriteOutput(string relativePath, string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(module.FullPath))
|
if (string.IsNullOrWhiteSpace(relativePath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
string rootPath = FindProjectDirectory();
|
||||||
|
string fullPath = Path.Combine(rootPath, relativePath);
|
||||||
|
|
||||||
// Default is true because Visual Studio saves .cs files with the BOM by default
|
// Default is true because Visual Studio saves .cs files with the BOM by default
|
||||||
bool hasBom = true;
|
bool hasBom = true;
|
||||||
byte[] bom = Encoding.UTF8.GetPreamble();
|
byte[] bom = Encoding.UTF8.GetPreamble();
|
||||||
byte[] oldFile = null;
|
byte[] oldFile = null;
|
||||||
|
|
||||||
if (File.Exists(module.FullPath))
|
if (File.Exists(fullPath))
|
||||||
{
|
{
|
||||||
oldFile = File.ReadAllBytes(module.FullPath);
|
oldFile = File.ReadAllBytes(fullPath);
|
||||||
|
|
||||||
if (oldFile.Length >= 3)
|
if (oldFile.Length >= 3)
|
||||||
hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom);
|
hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom);
|
||||||
|
@ -305,12 +308,66 @@ namespace LibHacBuild.CodeGen
|
||||||
|
|
||||||
if (oldFile?.SequenceEqual(newFile) == true)
|
if (oldFile?.SequenceEqual(newFile) == true)
|
||||||
{
|
{
|
||||||
Logger.Normal($"{module.Path} is already up-to-date");
|
Logger.Normal($"{relativePath} is already up-to-date");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Normal($"Generated file {module.Path}");
|
Logger.Normal($"Generated file {relativePath}");
|
||||||
File.WriteAllBytes(module.FullPath, newFile);
|
File.WriteAllBytes(fullPath, newFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] BuildArchive(ModuleInfo[] modules)
|
||||||
|
{
|
||||||
|
var builder = new ResultArchiveBuilder();
|
||||||
|
|
||||||
|
foreach (ModuleInfo module in modules.OrderBy(x => x.Index))
|
||||||
|
{
|
||||||
|
foreach (ResultInfo result in module.Results.OrderBy(x => x.DescriptionStart))
|
||||||
|
{
|
||||||
|
builder.Add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string PrintArchive(ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
var sb = new IndentingStringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine(GetHeader());
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine("using System;");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine("namespace LibHac");
|
||||||
|
sb.AppendLineAndIncrease("{");
|
||||||
|
|
||||||
|
sb.AppendLine("internal partial class ResultNameResolver");
|
||||||
|
sb.AppendLineAndIncrease("{");
|
||||||
|
|
||||||
|
sb.AppendLine("private static ReadOnlySpan<byte> ArchiveData => new byte[]");
|
||||||
|
sb.AppendLineAndIncrease("{");
|
||||||
|
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
if (i % 16 != 0) sb.Append(" ");
|
||||||
|
sb.Append($"0x{data[i]:x2}");
|
||||||
|
|
||||||
|
if (i != data.Length - 1)
|
||||||
|
{
|
||||||
|
sb.Append(",");
|
||||||
|
if (i % 16 == 15) sb.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.DecreaseAndAppendLine("};");
|
||||||
|
sb.DecreaseAndAppendLine("}");
|
||||||
|
sb.DecreaseAndAppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T[] ReadCsv<T>(string name)
|
private static T[] ReadCsv<T>(string name)
|
||||||
|
@ -387,6 +444,85 @@ namespace LibHacBuild.CodeGen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ResultArchiveBuilder
|
||||||
|
{
|
||||||
|
private List<ResultInfo> Results = new List<ResultInfo>();
|
||||||
|
|
||||||
|
public void Add(ResultInfo result)
|
||||||
|
{
|
||||||
|
Results.Add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Build()
|
||||||
|
{
|
||||||
|
int tableOffset = CalculateNameTableOffset();
|
||||||
|
var archive = new byte[tableOffset + CalculateNameTableSize()];
|
||||||
|
|
||||||
|
ref HeaderStruct header = ref Unsafe.As<byte, HeaderStruct>(ref archive[0]);
|
||||||
|
Span<Element> elements = MemoryMarshal.Cast<byte, Element>(
|
||||||
|
archive.AsSpan(Unsafe.SizeOf<HeaderStruct>(), Results.Count * Unsafe.SizeOf<Element>()));
|
||||||
|
Span<byte> nameTable = archive.AsSpan(tableOffset);
|
||||||
|
|
||||||
|
header.ElementCount = Results.Count;
|
||||||
|
header.NameTableOffset = tableOffset;
|
||||||
|
|
||||||
|
int curNameOffset = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < Results.Count; i++)
|
||||||
|
{
|
||||||
|
ResultInfo result = Results[i];
|
||||||
|
ref Element element = ref elements[i];
|
||||||
|
|
||||||
|
element.NameOffset = curNameOffset;
|
||||||
|
element.Module = (short)result.Module;
|
||||||
|
element.DescriptionStart = (short)result.DescriptionStart;
|
||||||
|
element.DescriptionEnd = (short)result.DescriptionEnd;
|
||||||
|
|
||||||
|
Span<byte> utf8Name = Encoding.UTF8.GetBytes(result.FullName);
|
||||||
|
utf8Name.CopyTo(nameTable.Slice(curNameOffset));
|
||||||
|
nameTable[curNameOffset + utf8Name.Length] = 0;
|
||||||
|
|
||||||
|
curNameOffset += utf8Name.Length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return archive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CalculateNameTableOffset()
|
||||||
|
{
|
||||||
|
return Unsafe.SizeOf<HeaderStruct>() + Unsafe.SizeOf<Element>() * Results.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CalculateNameTableSize()
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
Encoding encoding = Encoding.UTF8;
|
||||||
|
|
||||||
|
foreach (ResultInfo result in Results)
|
||||||
|
{
|
||||||
|
size += encoding.GetByteCount(result.FullName) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable NotAccessedField.Local
|
||||||
|
private struct HeaderStruct
|
||||||
|
{
|
||||||
|
public int ElementCount;
|
||||||
|
public int NameTableOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Element
|
||||||
|
{
|
||||||
|
public int NameOffset;
|
||||||
|
public short Module;
|
||||||
|
public short DescriptionStart;
|
||||||
|
public short DescriptionEnd;
|
||||||
|
}
|
||||||
|
// ReSharper restore NotAccessedField.Local
|
||||||
|
}
|
||||||
|
|
||||||
public class ModuleIndex
|
public class ModuleIndex
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
@ -408,7 +544,6 @@ namespace LibHacBuild.CodeGen
|
||||||
public string Namespace { get; set; }
|
public string Namespace { get; set; }
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
public string FullPath { get; set; }
|
|
||||||
public bool NeedsAggressiveInlining { get; set; }
|
public bool NeedsAggressiveInlining { get; set; }
|
||||||
public ResultInfo[] Results { get; set; }
|
public ResultInfo[] Results { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -420,6 +555,7 @@ namespace LibHacBuild.CodeGen
|
||||||
public int DescriptionStart { get; set; }
|
public int DescriptionStart { get; set; }
|
||||||
public int DescriptionEnd { get; set; }
|
public int DescriptionEnd { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
public string FullName { get; set; }
|
||||||
public string Summary { get; set; }
|
public string Summary { get; set; }
|
||||||
|
|
||||||
public bool IsRange => DescriptionStart != DescriptionEnd;
|
public bool IsRange => DescriptionStart != DescriptionEnd;
|
||||||
|
|
|
@ -37,6 +37,10 @@
|
||||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Condition="Exists('ResultNameResolver.Generated.cs')" Remove="ResultNameResolver.Archive.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace LibHac
|
||||||
public static Result Success => new Result(SuccessValue);
|
public static Result Success => new Result(SuccessValue);
|
||||||
|
|
||||||
private static IResultLogger Logger { get; set; }
|
private static IResultLogger Logger { get; set; }
|
||||||
private static IResultNameResolver NameResolver { get; set; }
|
private static IResultNameResolver NameResolver { get; set; } = new ResultNameResolver();
|
||||||
|
|
||||||
private const int ModuleBitsOffset = 0;
|
private const int ModuleBitsOffset = 0;
|
||||||
private const int ModuleBitsCount = 9;
|
private const int ModuleBitsCount = 9;
|
||||||
|
|
15
src/LibHac/ResultNameResolver.Archive.cs
Normal file
15
src/LibHac/ResultNameResolver.Archive.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LibHac
|
||||||
|
{
|
||||||
|
internal partial class ResultNameResolver
|
||||||
|
{
|
||||||
|
private static ReadOnlySpan<byte> ArchiveData => new byte[]
|
||||||
|
{
|
||||||
|
// This array will be populated when the build script is run.
|
||||||
|
|
||||||
|
// The script can be run with the "codegen" option to run only the
|
||||||
|
// code generation portion of the build.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
79
src/LibHac/ResultNameResolver.cs
Normal file
79
src/LibHac/ResultNameResolver.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using LibHac.Common;
|
||||||
|
|
||||||
|
namespace LibHac
|
||||||
|
{
|
||||||
|
internal partial class ResultNameResolver : Result.IResultNameResolver
|
||||||
|
{
|
||||||
|
private Lazy<Dictionary<Result, string>> ResultNames { get; } = new Lazy<Dictionary<Result, string>>(GetResultNames);
|
||||||
|
|
||||||
|
public bool TryResolveName(Result result, out string name)
|
||||||
|
{
|
||||||
|
return ResultNames.Value.TryGetValue(result, out name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<Result, string> GetResultNames()
|
||||||
|
{
|
||||||
|
var archiveReader = new ResultArchiveReader(ArchiveData);
|
||||||
|
return archiveReader.GetDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ref struct ResultArchiveReader
|
||||||
|
{
|
||||||
|
private readonly ReadOnlySpan<byte> _data;
|
||||||
|
|
||||||
|
private ref HeaderStruct Header => ref Unsafe.As<byte, HeaderStruct>(ref MemoryMarshal.GetReference(_data));
|
||||||
|
private ReadOnlySpan<byte> NameTable => _data.Slice(Header.NameTableOffset);
|
||||||
|
private ReadOnlySpan<Element> Elements => MemoryMarshal.Cast<byte, Element>(
|
||||||
|
_data.Slice(Unsafe.SizeOf<HeaderStruct>(), Header.ElementCount * Unsafe.SizeOf<Element>()));
|
||||||
|
|
||||||
|
public ResultArchiveReader(ReadOnlySpan<byte> archive)
|
||||||
|
{
|
||||||
|
_data = archive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<Result, string> GetDictionary()
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<Result, string>();
|
||||||
|
if (_data.Length < 8) return dict;
|
||||||
|
|
||||||
|
ReadOnlySpan<Element> elements = Elements;
|
||||||
|
|
||||||
|
foreach (ref readonly Element element in elements)
|
||||||
|
{
|
||||||
|
var result = new Result(element.Module, element.DescriptionStart);
|
||||||
|
dict.Add(result, GetName(element.NameOffset).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
private U8Span GetName(int offset)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> untrimmed = NameTable.Slice(offset);
|
||||||
|
int len = StringUtils.GetLength(untrimmed);
|
||||||
|
|
||||||
|
return new U8Span(untrimmed.Slice(0, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
private struct HeaderStruct
|
||||||
|
{
|
||||||
|
public int ElementCount;
|
||||||
|
public int NameTableOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Element
|
||||||
|
{
|
||||||
|
public int NameOffset;
|
||||||
|
public short Module;
|
||||||
|
public short DescriptionStart;
|
||||||
|
public short DescriptionEnd;
|
||||||
|
}
|
||||||
|
#pragma warning restore 649
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,8 +56,6 @@ namespace hactoolnet
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Result.SetNameResolver(new ResultNameResolver());
|
|
||||||
|
|
||||||
using (var logger = new ProgressBar())
|
using (var logger = new ProgressBar())
|
||||||
{
|
{
|
||||||
ctx.Logger = logger;
|
ctx.Logger = logger;
|
||||||
|
|
Loading…
Reference in a new issue