Remove use of code that involves reflection

This allows over 40% of the CoreRT native binary size to be removed by removing reflection capabilities.

The "--noreflection" option for the build script can be used to build hactoolnet with no reflection.

The Linux build won't always work because creating a new thread for the progress bar runs into some issue with EventSource being removed.
This commit is contained in:
Alex Barney 2020-06-27 16:01:46 -07:00
parent d3c95d14d3
commit 88983d39e5
6 changed files with 196 additions and 29 deletions

View file

@ -34,6 +34,9 @@ namespace LibHacBuild
[Parameter("Don't enable any size-reducing settings on native builds.")]
public readonly bool Untrimmed;
[Parameter("Disable reflection in native builds.")]
public readonly bool NoReflection;
[Solution("LibHac.sln")] readonly Solution _solution;
AbsolutePath SourceDirectory => RootDirectory / "src";
@ -358,6 +361,11 @@ namespace LibHacBuild
{
string buildType = Untrimmed ? "native-untrimmed" : "native";
if (NoReflection)
{
buildType = "native-noreflection";
}
DotNetPublishSettings publishSettings = new DotNetPublishSettings()
.SetConfiguration(Configuration)
.SetProject(HactoolnetProject)
@ -368,7 +376,7 @@ namespace LibHacBuild
DotNetPublish(publishSettings);
if (EnvironmentInfo.IsUnix && !Untrimmed)
if (EnvironmentInfo.IsUnix && !Untrimmed && !NoReflection)
{
File.Copy(CliNativeExe, CliNativeExe + "_unstripped", true);
ProcessTasks.StartProcess("strip", CliNativeExe).AssertZeroExitCode();

View file

@ -20,19 +20,19 @@ namespace LibHac
private const int SdCardKeyIdCount = 3;
public byte[][] KeyblobKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] KeyblobMacKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] EncryptedKeyblobs { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0xB0);
public byte[][] Keyblobs { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x90);
public byte[][] KeyblobKeySources { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] KeyblobKeys { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][] KeyblobMacKeys { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][] EncryptedKeyblobs { get; } = Util.CreateJaggedByteArray(0x20, 0xB0);
public byte[][] Keyblobs { get; } = Util.CreateJaggedByteArray(0x20, 0x90);
public byte[][] KeyblobKeySources { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[] KeyblobMacKeySource { get; } = new byte[0x10];
public byte[][] TsecRootKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] MasterKekSources { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] MasterKeks { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] TsecRootKeys { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][] MasterKekSources { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][] MasterKeks { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[] MasterKeySource { get; } = new byte[0x10];
public byte[][] MasterKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] Package1Keys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] Package2Keys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][] MasterKeys { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][] Package1Keys { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][] Package2Keys { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[] Package2KeySource { get; } = new byte[0x10];
public byte[] AesKekGenerationSource { get; } = new byte[0x10];
public byte[] AesKeyGenerationSource { get; } = new byte[0x10];
@ -46,29 +46,29 @@ namespace LibHac
public byte[] TitleKekSource { get; } = new byte[0x10];
public byte[] HeaderKekSource { get; } = new byte[0x10];
public byte[] SdCardKekSource { get; } = new byte[0x10];
public byte[][] SdCardKeySources { get; } = Util.CreateJaggedArray<byte[][]>(SdCardKeyIdCount, 0x20);
public byte[][] SdCardKeySources { get; } = Util.CreateJaggedByteArray(SdCardKeyIdCount, 0x20);
public byte[] HeaderKeySource { get; } = new byte[0x20];
public byte[] HeaderKey { get; } = new byte[0x20];
public byte[] XciHeaderKey { get; } = new byte[0x10];
public byte[][] TitleKeks { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
public byte[][][] KeyAreaKeys { get; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
public byte[][] TitleKeks { get; } = Util.CreateJaggedByteArray(0x20, 0x10);
public byte[][][] KeyAreaKeys { get; } = Util.CreateJaggedByteArray(0x20, 3, 0x10);
public byte[] EticketRsaKek { get; } = new byte[0x10];
public byte[] RetailSpecificAesKeySource { get; } = new byte[0x10];
public byte[] PerConsoleKeySource { get; } = new byte[0x10];
public byte[] BisKekSource { get; } = new byte[0x10];
public byte[][] BisKeySource { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x20);
public byte[][] BisKeySource { get; } = Util.CreateJaggedByteArray(4, 0x20);
public byte[] SslRsaKek { get; } = new byte[0x10];
// Device-specific keys
public byte[] SecureBootKey { get; } = new byte[0x10];
public byte[] TsecKey { get; } = new byte[0x10];
public byte[] DeviceKey { get; } = new byte[0x10];
public byte[][] BisKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x20);
public byte[][] BisKeys { get; } = Util.CreateJaggedByteArray(4, 0x20);
public byte[] SaveMacKey { get; } = new byte[0x10];
public byte[] SaveMacSdCardKey { get; } = new byte[0x10];
public byte[] SdSeed { get; } = new byte[0x10];
public byte[][] SdCardKeySourcesSpecific { get; } = Util.CreateJaggedArray<byte[][]>(SdCardKeyIdCount, 0x20);
public byte[][] SdCardKeys { get; } = Util.CreateJaggedArray<byte[][]>(SdCardKeyIdCount, 0x20);
public byte[][] SdCardKeySourcesSpecific { get; } = Util.CreateJaggedByteArray(SdCardKeyIdCount, 0x20);
public byte[][] SdCardKeys { get; } = Util.CreateJaggedByteArray(SdCardKeyIdCount, 0x20);
public RSAParameters EticketExtKeyRsa { get; set; }

View file

@ -11,21 +11,30 @@ namespace LibHac
{
private const int MediaSize = 0x200;
public static T CreateJaggedArray<T>(params int[] lengths)
public static byte[][] CreateJaggedByteArray(int len1, int len2)
{
return (T)InitializeJaggedArray(typeof(T).GetElementType(), 0, lengths);
var array = new byte[len1][];
for (int i = 0; i < array.Length; i++)
{
array[i] = new byte[len2];
}
return array;
}
private static object InitializeJaggedArray(Type type, int index, int[] lengths)
public static byte[][][] CreateJaggedByteArray(int len1, int len2, int len3)
{
var array = Array.CreateInstance(type, lengths[index]);
var array = new byte[len1][][];
Type elementType = type.GetElementType();
if (elementType == null) return array;
for (int i = 0; i < lengths[index]; i++)
for (int i = 0; i < array.Length; i++)
{
array.SetValue(InitializeJaggedArray(elementType, index + 1, lengths), i);
array[i] = new byte[len2][];
for (int j = 0; j < array[i].Length; j++)
{
array[i][j] = new byte[len3];
}
}
return array;

View file

@ -0,0 +1,142 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Environment.GetFolderPath calls Enum.IsDefined which currently doesn't work under CoreRT (2020-06-27)
// This code is copied from the .NET runtime with modifications to avoid that.
// The downside is that it won't work in Linux unless the HOME environmental variable is set.
#if CORERT_NO_REFLECTION
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace hactoolnet
{
internal static class HomeFolder
{
public static string GetFolderPath(Environment.SpecialFolder folder) =>
GetFolderPath(folder, Environment.SpecialFolderOption.None);
public static string GetFolderPath(Environment.SpecialFolder folder, Environment.SpecialFolderOption option)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return GetFolderPathCoreWin(folder, option);
}
else
{
return GetFolderPathCoreWithoutValidation(folder);
}
}
/// <summary>
/// (CSIDL_PROFILE) The root users profile folder "%USERPROFILE%"
/// ("%SystemDrive%\Users\%USERNAME%")
/// </summary>
internal const string Profile = "{5E6C858F-0E22-4760-9AFE-EA3317B67173}";
private static string GetFolderPathCoreWin(Environment.SpecialFolder folder,
Environment.SpecialFolderOption option)
{
// We're using SHGetKnownFolderPath instead of SHGetFolderPath as SHGetFolderPath is
// capped at MAX_PATH.
//
// Because we validate both of the input enums we shouldn't have to care about CSIDL and flag
// definitions we haven't mapped. If we remove or loosen the checks we'd have to account
// for mapping here (this includes tweaking as SHGetFolderPath would do).
//
// The only SpecialFolderOption defines we have are equivalent to KnownFolderFlags.
string folderGuid;
switch (folder)
{
case Environment.SpecialFolder.UserProfile:
folderGuid = Profile;
break;
default:
throw new NotSupportedException();
}
return GetKnownFolderPath(folderGuid, option);
}
private static string GetKnownFolderPath(string folderGuid, Environment.SpecialFolderOption option)
{
var folderId = new Guid(folderGuid);
int hr = Shell32.SHGetKnownFolderPath(folderId, (uint)option, IntPtr.Zero, out string path);
if (hr != 0) // Not S_OK
{
return string.Empty;
}
return path;
}
private static string GetFolderPathCoreWithoutValidation(Environment.SpecialFolder folder)
{
// All other paths are based on the XDG Base Directory Specification:
// https://specifications.freedesktop.org/basedir-spec/latest/
string home = null;
try
{
home = GetHomeDirectory();
}
catch (Exception exc)
{
Debug.Fail($"Unable to get home directory: {exc}");
}
// Fall back to '/' when we can't determine the home directory.
// This location isn't writable by non-root users which provides some safeguard
// that the application doesn't write data which is meant to be private.
if (string.IsNullOrEmpty(home))
{
home = "/";
}
// TODO: Consider caching (or precomputing and caching) all subsequent results.
// This would significantly improve performance for repeated access, at the expense
// of not being responsive to changes in the underlying environment variables,
// configuration files, etc.
switch (folder)
{
case Environment.SpecialFolder.UserProfile:
case Environment.SpecialFolder.MyDocuments: // same value as Personal
return home;
}
// No known path for the SpecialFolder
return string.Empty;
}
/// <summary>Gets the current user's home directory.</summary>
/// <returns>The path to the home directory, or null if it could not be determined.</returns>
internal static string GetHomeDirectory()
{
// First try to get the user's home directory from the HOME environment variable.
// This should work in most cases.
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
if (!string.IsNullOrEmpty(userHomeDirectory))
return userHomeDirectory;
throw new NotSupportedException(
"Unable to get your home directory. Please report this on the LibHac GitHub repository." +
"You can use the netcore build in the GitHub repo's releases for now.");
}
}
internal class Shell32
{
[DllImport("shell32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
internal static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid,
uint dwFlags,
IntPtr hToken,
out string ppszPath);
}
}
#endif

View file

@ -37,7 +37,10 @@ namespace hactoolnet
Console.Error.WriteLine($"\nERROR: {ex.Message}\n");
Console.Error.WriteLine("Additional information:");
#if !CORERT_NO_REFLECTION
Console.Error.WriteLine(ex.GetType().FullName);
#endif
Console.Error.WriteLine(ex.StackTrace);
}
@ -167,7 +170,11 @@ namespace hactoolnet
{
string keyFileName = ctx.Options.UseDevKeys ? "dev.keys" : "prod.keys";
#if CORERT_NO_REFLECTION
string home = HomeFolder.GetFolderPath(Environment.SpecialFolder.UserProfile);
#else
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
#endif
string homeKeyFile = Path.Combine(home, ".switch", keyFileName);
string homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys");
string homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys");

View file

@ -36,9 +36,10 @@
<IlcArg Include="--removefeature:EventSource" />
</ItemGroup>
<!-- No-reflection mode doesn't work yet -->
<!-- No-reflection mode might not work on Linux if the HOME environment variable is not set -->
<PropertyGroup Condition=" '$(BuildType)' == 'native-noreflection' ">
<IlcDisableReflection>true</IlcDisableReflection>
<DefineConstants>CORERT_NO_REFLECTION;$(DefineConstants)</DefineConstants>
</PropertyGroup>
</Project>