mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Merge branch 'master' into net
This commit is contained in:
commit
1d4ffede70
30 changed files with 1155 additions and 344 deletions
DotnetCliVersion.txtGitVersion.ymlREADME.mdappveyor.ymlbuild.ps1build.sh
build
src
LibHac.Nand
LibHac
BitReader.cs
IO
AesXtsDirectory.csAesXtsFile.csAesXtsFileHeader.csAesXtsFileSystem.csConcatenationStorage.csFileSystemExtensions.csLocalFileSystem.csNxFileStream.cs
Keyset.csLibHac.csprojNca.csNcaStructs.csUtil.csRomFs
StorageStream.cshactoolnet
tests/LibHac.Tests
|
@ -1 +1 @@
|
||||||
2.1.500
|
2.2.103
|
5
GitVersion.yml
Normal file
5
GitVersion.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mode: ContinuousDeployment
|
||||||
|
increment: Patch
|
||||||
|
branches:
|
||||||
|
master:
|
||||||
|
tag: alpha
|
|
@ -1,5 +1,9 @@
|
||||||
# LibHac
|
# LibHac
|
||||||
|
|
||||||
|
[![NuGet](https://img.shields.io/nuget/v/LibHac.svg?style=flat-square)](https://www.nuget.org/packages/LibHac)
|
||||||
|
[![MyGet](https://img.shields.io/myget/libhac/vpre/libhac.svg?label=myget&style=flat-square)](https://www.myget.org/feed/libhac/package/nuget/LibHac)
|
||||||
|
[![AppVeyor Build Status](https://img.shields.io/appveyor/ci/thealexbarney/LibHac/master.svg?style=flat-square)](https://ci.appveyor.com/project/Thealexbarney/libhac/history)
|
||||||
|
|
||||||
LibHac is a .NET and .NET Core library for opening, decrypting and extracting common content file formats used by the Nintendo Switch.
|
LibHac is a .NET and .NET Core library for opening, decrypting and extracting common content file formats used by the Nintendo Switch.
|
||||||
|
|
||||||
Most content is imported and exported using a standard `IStorage` interface. This means that reading nested file types and encryptions can easily be done by linking different file readers together.
|
Most content is imported and exported using a standard `IStorage` interface. This means that reading nested file types and encryptions can easily be done by linking different file readers together.
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
version: 0.1.3-{build}
|
version: 0.1.3-{build}
|
||||||
image: Visual Studio 2017
|
image: Visual Studio 2017
|
||||||
|
environment:
|
||||||
|
myget_api_key:
|
||||||
|
secure: 0xJoYAtR6psXCRvk1qm5czDObkeRjHKPjfe5gIExXVFPwA0VVODYv/hBZYUtz2F3
|
||||||
build_script:
|
build_script:
|
||||||
- ps: .\build.ps1
|
- ps: .\build.ps1
|
||||||
test: off
|
test: off
|
18
build.ps1
18
build.ps1
|
@ -1,6 +1,7 @@
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
Param(
|
Param(
|
||||||
#[switch]$CustomParam,
|
#[switch]$CustomParam,
|
||||||
|
[switch]$BuildDotnetCoreOnly,
|
||||||
[Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)]
|
[Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)]
|
||||||
[string[]]$BuildArguments
|
[string[]]$BuildArguments
|
||||||
)
|
)
|
||||||
|
@ -43,14 +44,17 @@ try {
|
||||||
if (Test-Path $DotNetGlobalFile) {
|
if (Test-Path $DotNetGlobalFile) {
|
||||||
$DotNetVersion = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json).sdk.version
|
$DotNetVersion = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json).sdk.version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
||||||
|
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
||||||
|
|
||||||
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
||||||
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
|
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
|
||||||
(!(Test-Path variable:DotNetVersion) -or $(& cmd.exe /c 'dotnet --version 2>&1') -eq $DotNetVersion)) {
|
(!(Test-Path variable:DotNetVersion) -or $(& cmd.exe /c 'dotnet --version 2>&1') -eq $DotNetVersion)) {
|
||||||
$env:DOTNET_EXE = (Get-Command "dotnet").Path
|
$env:DOTNET_EXE = (Get-Command "dotnet").Path
|
||||||
}
|
}
|
||||||
else {
|
elseif ($null -eq (Get-Command $env:DOTNET_EXE -ErrorAction SilentlyContinue) -and `
|
||||||
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
(!(Test-Path variable:DotNetVersion) -or $(& cmd.exe /c '$env:DOTNET_EXE --version 2>&1') -ne $DotNetVersion)) {
|
||||||
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
|
||||||
|
|
||||||
# Download install script
|
# Download install script
|
||||||
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
|
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
|
||||||
|
@ -68,8 +72,16 @@ try {
|
||||||
|
|
||||||
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
|
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
|
||||||
|
|
||||||
|
if($BuildDotnetCoreOnly) {
|
||||||
|
$BuildArguments += "-DoCoreBuildOnly"
|
||||||
|
$BuildArguments += "true"
|
||||||
|
}
|
||||||
|
|
||||||
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments }
|
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments }
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output $_.Exception.Message
|
||||||
|
}
|
||||||
finally {
|
finally {
|
||||||
if (Test-Path $DotNetGlobalFile) {
|
if (Test-Path $DotNetGlobalFile) {
|
||||||
Remove-Item $DotNetGlobalFile
|
Remove-Item $DotNetGlobalFile
|
||||||
|
|
16
build.sh
16
build.sh
|
@ -3,7 +3,7 @@
|
||||||
echo $(bash --version 2>&1 | head -n 1)
|
echo $(bash --version 2>&1 | head -n 1)
|
||||||
|
|
||||||
#CUSTOMPARAM=0
|
#CUSTOMPARAM=0
|
||||||
BUILD_ARGUMENTS=()
|
BUILD_ARGUMENTS=("-DoCoreBuildOnly")
|
||||||
for i in "$@"; do
|
for i in "$@"; do
|
||||||
case $(echo $1 | awk '{print tolower($0)}') in
|
case $(echo $1 | awk '{print tolower($0)}') in
|
||||||
# -custom-param) CUSTOMPARAM=1;;
|
# -custom-param) CUSTOMPARAM=1;;
|
||||||
|
@ -37,30 +37,34 @@ export NUGET_XMLDOC_MODE="skip"
|
||||||
function FirstJsonValue {
|
function FirstJsonValue {
|
||||||
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
|
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
|
||||||
}
|
}
|
||||||
trap "rm -f $DOTNET_GLOBAL_FILE" INT TERM EXIT
|
trap "rm -f \"$DOTNET_GLOBAL_FILE\"" INT TERM EXIT
|
||||||
|
|
||||||
dotnetCliVersion=$(cat DotnetCliVersion.txt)
|
dotnetCliVersion=$(cat DotnetCliVersion.txt)
|
||||||
|
|
||||||
json="{\"sdk\":{\"version\":\"$dotnetCliVersion\"}}"
|
json="{\"sdk\":{\"version\":\"$dotnetCliVersion\"}}"
|
||||||
echo "$json" > $DOTNET_GLOBAL_FILE
|
echo "$json" > "$DOTNET_GLOBAL_FILE"
|
||||||
|
|
||||||
# If global.json exists, load expected version
|
# If global.json exists, load expected version
|
||||||
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
|
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
|
||||||
DOTNET_VERSION=$dotnetCliVersion
|
DOTNET_VERSION=$dotnetCliVersion
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
|
||||||
|
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
|
||||||
|
|
||||||
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
||||||
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
|
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
|
||||||
export DOTNET_EXE="$(command -v dotnet)"
|
export DOTNET_EXE="$(command -v dotnet)"
|
||||||
else
|
elif [[ ! (-x "$DOTNET_EXE" && (-z ${DOTNET_VERSION+x} || $($DOTNET_EXE --version) == "$DOTNET_VERSION")) ]]; then
|
||||||
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
|
|
||||||
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
|
|
||||||
|
|
||||||
# Download install script
|
# Download install script
|
||||||
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
|
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
|
||||||
mkdir -p "$TEMP_DIRECTORY"
|
mkdir -p "$TEMP_DIRECTORY"
|
||||||
|
|
||||||
|
if [ ! -x "$DOTNET_INSTALL_FILE" ]; then
|
||||||
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
|
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
|
||||||
chmod +x "$DOTNET_INSTALL_FILE"
|
chmod +x "$DOTNET_INSTALL_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
# Install by channel or version
|
# Install by channel or version
|
||||||
if [ -z ${DOTNET_VERSION+x} ]; then
|
if [ -z ${DOTNET_VERSION+x} ]; then
|
||||||
|
|
329
build/Build.cs
329
build/Build.cs
|
@ -4,29 +4,41 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using ILRepacking;
|
using ILRepacking;
|
||||||
using Nuke.Common;
|
using Nuke.Common;
|
||||||
|
using Nuke.Common.BuildServers;
|
||||||
using Nuke.Common.Git;
|
using Nuke.Common.Git;
|
||||||
using Nuke.Common.ProjectModel;
|
using Nuke.Common.ProjectModel;
|
||||||
using Nuke.Common.Tools.DotNet;
|
using Nuke.Common.Tools.DotNet;
|
||||||
|
using Nuke.Common.Tools.GitVersion;
|
||||||
|
using Nuke.Common.Tools.SignTool;
|
||||||
using static Nuke.Common.IO.FileSystemTasks;
|
using static Nuke.Common.IO.FileSystemTasks;
|
||||||
using static Nuke.Common.IO.PathConstruction;
|
using static Nuke.Common.IO.PathConstruction;
|
||||||
using static Nuke.Common.Tools.DotNet.DotNetTasks;
|
using static Nuke.Common.Tools.DotNet.DotNetTasks;
|
||||||
|
|
||||||
class Build : NukeBuild
|
namespace LibHacBuild
|
||||||
{
|
{
|
||||||
|
partial class Build : NukeBuild
|
||||||
|
{
|
||||||
public static int Main() => Execute<Build>(x => x.Results);
|
public static int Main() => Execute<Build>(x => x.Results);
|
||||||
|
|
||||||
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
|
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
|
||||||
readonly string Configuration = IsLocalBuild ? "Debug" : "Release";
|
readonly string Configuration = IsLocalBuild ? "Debug" : "Release";
|
||||||
|
|
||||||
|
[Parameter("Build only .NET Core targets if true. Default is false on Windows")]
|
||||||
|
readonly bool DoCoreBuildOnly;
|
||||||
|
|
||||||
[Solution("LibHac.sln")] readonly Solution Solution;
|
[Solution("LibHac.sln")] readonly Solution Solution;
|
||||||
[GitRepository] readonly GitRepository GitRepository;
|
[GitRepository] readonly GitRepository GitRepository;
|
||||||
|
[GitVersion] readonly GitVersion GitVersion;
|
||||||
|
|
||||||
AbsolutePath SourceDirectory => RootDirectory / "src";
|
AbsolutePath SourceDirectory => RootDirectory / "src";
|
||||||
AbsolutePath TestsDirectory => RootDirectory / "tests";
|
AbsolutePath TestsDirectory => RootDirectory / "tests";
|
||||||
AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
|
AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
|
||||||
|
AbsolutePath SignedArtifactsDirectory => ArtifactsDirectory / "signed";
|
||||||
AbsolutePath TempDirectory => RootDirectory / ".tmp";
|
AbsolutePath TempDirectory => RootDirectory / ".tmp";
|
||||||
AbsolutePath CliCoreDir => TempDirectory / "hactoolnet_netcoreapp2.1";
|
AbsolutePath CliCoreDir => TempDirectory / "hactoolnet_netcoreapp2.1";
|
||||||
AbsolutePath CliFrameworkDir => TempDirectory / "hactoolnet_net46";
|
AbsolutePath CliFrameworkDir => TempDirectory / "hactoolnet_net46";
|
||||||
|
@ -39,6 +51,47 @@ class Build : NukeBuild
|
||||||
Project LibHacTestProject => Solution.GetProject("LibHac.Tests").NotNull();
|
Project LibHacTestProject => Solution.GetProject("LibHac.Tests").NotNull();
|
||||||
Project HactoolnetProject => Solution.GetProject("hactoolnet").NotNull();
|
Project HactoolnetProject => Solution.GetProject("hactoolnet").NotNull();
|
||||||
|
|
||||||
|
string AppVeyorVersion { get; set; }
|
||||||
|
Dictionary<string, object> VersionProps { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
const string CertFileName = "cert.pfx";
|
||||||
|
|
||||||
|
Target SetVersion => _ => _
|
||||||
|
.OnlyWhenStatic(() => GitRepository != null)
|
||||||
|
.Executes(() =>
|
||||||
|
{
|
||||||
|
AppVeyorVersion = $"{GitVersion.AssemblySemVer}";
|
||||||
|
if (!string.IsNullOrWhiteSpace(GitVersion.PreReleaseTag))
|
||||||
|
{
|
||||||
|
AppVeyorVersion += $"-{GitVersion.PreReleaseTag}+{GitVersion.Sha.Substring(0, 8)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
string suffix = GitVersion.PreReleaseTag;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(suffix))
|
||||||
|
{
|
||||||
|
if (!GitRepository.IsOnMasterBranch())
|
||||||
|
{
|
||||||
|
suffix = $"-{suffix}";
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix += $"+{GitVersion.Sha.Substring(0, 8)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
VersionProps = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["VersionPrefix"] = GitVersion.AssemblySemVer,
|
||||||
|
["VersionSuffix"] = suffix
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine($"Building version {AppVeyorVersion}");
|
||||||
|
|
||||||
|
if (Host == HostType.AppVeyor)
|
||||||
|
{
|
||||||
|
SetAppVeyorVersion(AppVeyorVersion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Target Clean => _ => _
|
Target Clean => _ => _
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
|
@ -56,21 +109,20 @@ class Build : NukeBuild
|
||||||
DotNetRestoreSettings settings = new DotNetRestoreSettings()
|
DotNetRestoreSettings settings = new DotNetRestoreSettings()
|
||||||
.SetProjectFile(Solution);
|
.SetProjectFile(Solution);
|
||||||
|
|
||||||
if (EnvironmentInfo.IsUnix) settings = settings.RemoveRuntimes("net46");
|
|
||||||
|
|
||||||
DotNetRestore(s => settings);
|
DotNetRestore(s => settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
Target Compile => _ => _
|
Target Compile => _ => _
|
||||||
.DependsOn(Restore)
|
.DependsOn(Restore, SetVersion)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
DotNetBuildSettings buildSettings = new DotNetBuildSettings()
|
DotNetBuildSettings buildSettings = new DotNetBuildSettings()
|
||||||
.SetProjectFile(Solution)
|
.SetProjectFile(Solution)
|
||||||
.EnableNoRestore()
|
.EnableNoRestore()
|
||||||
.SetConfiguration(Configuration);
|
.SetConfiguration(Configuration)
|
||||||
|
.SetProperties(VersionProps);
|
||||||
|
|
||||||
if (EnvironmentInfo.IsUnix) buildSettings = buildSettings.SetFramework("netcoreapp2.1");
|
if (DoCoreBuildOnly) buildSettings = buildSettings.SetFramework("netcoreapp2.1");
|
||||||
|
|
||||||
DotNetBuild(s => buildSettings);
|
DotNetBuild(s => buildSettings);
|
||||||
|
|
||||||
|
@ -81,14 +133,16 @@ class Build : NukeBuild
|
||||||
DotNetPublish(s => publishSettings
|
DotNetPublish(s => publishSettings
|
||||||
.SetProject(HactoolnetProject)
|
.SetProject(HactoolnetProject)
|
||||||
.SetFramework("netcoreapp2.1")
|
.SetFramework("netcoreapp2.1")
|
||||||
.SetOutput(CliCoreDir));
|
.SetOutput(CliCoreDir)
|
||||||
|
.SetProperties(VersionProps));
|
||||||
|
|
||||||
if (EnvironmentInfo.IsWin)
|
if (!DoCoreBuildOnly)
|
||||||
{
|
{
|
||||||
DotNetPublish(s => publishSettings
|
DotNetPublish(s => publishSettings
|
||||||
.SetProject(HactoolnetProject)
|
.SetProject(HactoolnetProject)
|
||||||
.SetFramework("net46")
|
.SetFramework("net46")
|
||||||
.SetOutput(CliFrameworkDir));
|
.SetOutput(CliFrameworkDir)
|
||||||
|
.SetProperties(VersionProps));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hack around OS newline differences
|
// Hack around OS newline differences
|
||||||
|
@ -110,17 +164,23 @@ class Build : NukeBuild
|
||||||
.EnableNoBuild()
|
.EnableNoBuild()
|
||||||
.SetConfiguration(Configuration)
|
.SetConfiguration(Configuration)
|
||||||
.EnableIncludeSymbols()
|
.EnableIncludeSymbols()
|
||||||
.SetOutputDirectory(ArtifactsDirectory);
|
.SetSymbolPackageFormat(DotNetSymbolPackageFormat.snupkg)
|
||||||
|
.SetOutputDirectory(ArtifactsDirectory)
|
||||||
|
.SetProperties(VersionProps);
|
||||||
|
|
||||||
if (EnvironmentInfo.IsUnix)
|
if (DoCoreBuildOnly)
|
||||||
settings = settings.SetProperties(
|
settings = settings.SetProperty("TargetFrameworks", "netcoreapp2.1");
|
||||||
new Dictionary<string, object> { ["TargetFrameworks"] = "netcoreapp2.1" });
|
|
||||||
|
|
||||||
DotNetPack(s => settings);
|
DotNetPack(s => settings);
|
||||||
|
|
||||||
|
foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.*nupkg"))
|
||||||
|
{
|
||||||
|
RepackNugetPackage(filename);
|
||||||
|
}
|
||||||
|
|
||||||
if (Host != HostType.AppVeyor) return;
|
if (Host != HostType.AppVeyor) return;
|
||||||
|
|
||||||
foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.nupkg"))
|
foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.*nupkg"))
|
||||||
{
|
{
|
||||||
PushArtifact(filename);
|
PushArtifact(filename);
|
||||||
}
|
}
|
||||||
|
@ -128,7 +188,7 @@ class Build : NukeBuild
|
||||||
|
|
||||||
Target Merge => _ => _
|
Target Merge => _ => _
|
||||||
.DependsOn(Compile)
|
.DependsOn(Compile)
|
||||||
.OnlyWhen(() => EnvironmentInfo.IsWin)
|
.OnlyWhenStatic(() => !DoCoreBuildOnly)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
string[] libraries = Directory.GetFiles(CliFrameworkDir, "*.dll");
|
string[] libraries = Directory.GetFiles(CliFrameworkDir, "*.dll");
|
||||||
|
@ -144,6 +204,11 @@ class Build : NukeBuild
|
||||||
|
|
||||||
new ILRepack(cliOptions).Repack();
|
new ILRepack(cliOptions).Repack();
|
||||||
|
|
||||||
|
foreach (AbsolutePath file in ArtifactsDirectory.GlobFiles("*.exe.config"))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
|
||||||
if (Host == HostType.AppVeyor)
|
if (Host == HostType.AppVeyor)
|
||||||
{
|
{
|
||||||
PushArtifact(CliMergedExe);
|
PushArtifact(CliMergedExe);
|
||||||
|
@ -159,7 +224,7 @@ class Build : NukeBuild
|
||||||
.EnableNoBuild()
|
.EnableNoBuild()
|
||||||
.SetConfiguration(Configuration);
|
.SetConfiguration(Configuration);
|
||||||
|
|
||||||
if (EnvironmentInfo.IsUnix) settings = settings.SetFramework("netcoreapp2.1");
|
if (DoCoreBuildOnly) settings = settings.SetFramework("netcoreapp2.1");
|
||||||
|
|
||||||
DotNetTest(s => settings);
|
DotNetTest(s => settings);
|
||||||
});
|
});
|
||||||
|
@ -176,7 +241,7 @@ class Build : NukeBuild
|
||||||
.Concat(Directory.EnumerateFiles(CliCoreDir, "*.dll"))
|
.Concat(Directory.EnumerateFiles(CliCoreDir, "*.dll"))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
if (EnvironmentInfo.IsWin)
|
if (!DoCoreBuildOnly)
|
||||||
{
|
{
|
||||||
ZipFiles(CliFrameworkZip, namesFx);
|
ZipFiles(CliFrameworkZip, namesFx);
|
||||||
Console.WriteLine($"Created {CliFrameworkZip}");
|
Console.WriteLine($"Created {CliFrameworkZip}");
|
||||||
|
@ -193,8 +258,28 @@ class Build : NukeBuild
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Target Publish => _ => _
|
||||||
|
.DependsOn(Test)
|
||||||
|
.OnlyWhenStatic(() => Host == HostType.AppVeyor)
|
||||||
|
.OnlyWhenStatic(() => AppVeyor.Instance.PullRequestTitle == null)
|
||||||
|
.Executes(() =>
|
||||||
|
{
|
||||||
|
AbsolutePath nupkgFile = ArtifactsDirectory.GlobFiles("*.nupkg").Single();
|
||||||
|
AbsolutePath snupkgFile = ArtifactsDirectory.GlobFiles("*.snupkg").Single();
|
||||||
|
|
||||||
|
string apiKey = EnvironmentInfo.Variable("myget_api_key");
|
||||||
|
DotNetNuGetPushSettings settings = new DotNetNuGetPushSettings()
|
||||||
|
.SetApiKey(apiKey)
|
||||||
|
.SetSymbolApiKey(apiKey)
|
||||||
|
.SetSource("https://www.myget.org/F/libhac/api/v2/package")
|
||||||
|
.SetSymbolSource("https://www.myget.org/F/libhac/symbols/api/v2/package");
|
||||||
|
|
||||||
|
DotNetNuGetPush(settings.SetTargetPath(nupkgFile));
|
||||||
|
DotNetNuGetPush(settings.SetTargetPath(snupkgFile));
|
||||||
|
});
|
||||||
|
|
||||||
Target Results => _ => _
|
Target Results => _ => _
|
||||||
.DependsOn(Test, Zip, Merge)
|
.DependsOn(Test, Zip, Merge, Sign, Publish)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
Console.WriteLine("SHA-1:");
|
Console.WriteLine("SHA-1:");
|
||||||
|
@ -211,7 +296,24 @@ class Build : NukeBuild
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public static void ZipFiles(string outFile, string[] files)
|
Target Sign => _ => _
|
||||||
|
.DependsOn(Test, Zip, Merge)
|
||||||
|
.OnlyWhenStatic(() => !DoCoreBuildOnly)
|
||||||
|
.OnlyWhenStatic(() => File.Exists(CertFileName))
|
||||||
|
.Executes(() =>
|
||||||
|
{
|
||||||
|
string pwd = ReadPassword();
|
||||||
|
|
||||||
|
if (pwd == string.Empty)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Skipping sign task");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignAndReZip(pwd);
|
||||||
|
});
|
||||||
|
|
||||||
|
public static void ZipFiles(string outFile, IEnumerable<string> files)
|
||||||
{
|
{
|
||||||
using (var s = new ZipOutputStream(File.Create(outFile)))
|
using (var s = new ZipOutputStream(File.Create(outFile)))
|
||||||
{
|
{
|
||||||
|
@ -232,6 +334,80 @@ class Build : NukeBuild
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ZipDirectory(string outFile, string directory)
|
||||||
|
{
|
||||||
|
using (var s = new ZipOutputStream(File.Create(outFile)))
|
||||||
|
{
|
||||||
|
s.SetLevel(9);
|
||||||
|
|
||||||
|
foreach (string filePath in Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
string relativePath = Path.GetRelativePath(directory, filePath);
|
||||||
|
|
||||||
|
var entry = new ZipEntry(relativePath);
|
||||||
|
entry.DateTime = DateTime.UnixEpoch;
|
||||||
|
|
||||||
|
using (FileStream fs = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
entry.Size = fs.Length;
|
||||||
|
s.PutNextEntry(entry);
|
||||||
|
fs.CopyTo(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ZipDirectory(string outFile, string directory, IEnumerable<string> files)
|
||||||
|
{
|
||||||
|
using (var s = new ZipOutputStream(File.Create(outFile)))
|
||||||
|
{
|
||||||
|
s.SetLevel(9);
|
||||||
|
|
||||||
|
foreach (string filePath in files)
|
||||||
|
{
|
||||||
|
string absolutePath = Path.Combine(directory, filePath);
|
||||||
|
|
||||||
|
var entry = new ZipEntry(filePath);
|
||||||
|
entry.DateTime = DateTime.UnixEpoch;
|
||||||
|
|
||||||
|
using (FileStream fs = File.OpenRead(absolutePath))
|
||||||
|
{
|
||||||
|
entry.Size = fs.Length;
|
||||||
|
s.PutNextEntry(entry);
|
||||||
|
fs.CopyTo(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnzipFiles(string zipFile, string outDir)
|
||||||
|
{
|
||||||
|
using (var s = new ZipInputStream(File.OpenRead(zipFile)))
|
||||||
|
{
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = s.GetNextEntry()) != null)
|
||||||
|
{
|
||||||
|
string outPath = Path.Combine(outDir, entry.Name);
|
||||||
|
|
||||||
|
string directoryName = Path.GetDirectoryName(outPath);
|
||||||
|
string fileName = Path.GetFileName(outPath);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(directoryName))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
using (FileStream outFile = File.Create(outPath))
|
||||||
|
{
|
||||||
|
s.CopyTo(outFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void PushArtifact(string path)
|
public static void PushArtifact(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
|
@ -260,9 +436,122 @@ class Build : NukeBuild
|
||||||
Console.WriteLine($"Added AppVeyor artifact {path}");
|
Console.WriteLine($"Added AppVeyor artifact {path}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetAppVeyorVersion(string version)
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "appveyor",
|
||||||
|
Arguments = $"UpdateBuild -Version \"{version}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var proc = new Process
|
||||||
|
{
|
||||||
|
StartInfo = psi
|
||||||
|
};
|
||||||
|
|
||||||
|
proc.Start();
|
||||||
|
|
||||||
|
proc.WaitForExit();
|
||||||
|
}
|
||||||
|
|
||||||
public static void ReplaceLineEndings(string filename)
|
public static void ReplaceLineEndings(string filename)
|
||||||
{
|
{
|
||||||
string text = File.ReadAllText(filename);
|
string text = File.ReadAllText(filename);
|
||||||
File.WriteAllText(filename, text.Replace("\n", "\r\n"));
|
File.WriteAllText(filename, Regex.Replace(text, @"\r\n|\n\r|\n|\r", "\r\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SignAssemblies(string password, params string[] fileNames)
|
||||||
|
{
|
||||||
|
SignToolSettings settings = new SignToolSettings()
|
||||||
|
.SetFileDigestAlgorithm("SHA256")
|
||||||
|
.SetFile(CertFileName)
|
||||||
|
.SetFiles(fileNames)
|
||||||
|
.SetPassword(password)
|
||||||
|
.SetTimestampServerDigestAlgorithm("SHA256")
|
||||||
|
.SetRfc3161TimestampServerUrl("http://timestamp.digicert.com");
|
||||||
|
|
||||||
|
SignToolTasks.SignTool(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignAndReZip(string password)
|
||||||
|
{
|
||||||
|
AbsolutePath nupkgFile = ArtifactsDirectory.GlobFiles("*.nupkg").Single();
|
||||||
|
AbsolutePath snupkgFile = ArtifactsDirectory.GlobFiles("*.snupkg").Single();
|
||||||
|
AbsolutePath nupkgDir = TempDirectory / ("sign_" + Path.GetFileName(nupkgFile));
|
||||||
|
AbsolutePath netFxDir = TempDirectory / ("sign_" + Path.GetFileName(CliFrameworkZip));
|
||||||
|
AbsolutePath coreFxDir = TempDirectory / ("sign_" + Path.GetFileName(CliCoreZip));
|
||||||
|
AbsolutePath signedMergedExe = SignedArtifactsDirectory / Path.GetFileName(CliMergedExe);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UnzipFiles(CliFrameworkZip, netFxDir);
|
||||||
|
UnzipFiles(CliCoreZip, coreFxDir);
|
||||||
|
List<string> pkgFileList = UnzipPackage(nupkgFile, nupkgDir);
|
||||||
|
|
||||||
|
var toSign = new List<AbsolutePath>();
|
||||||
|
toSign.AddRange(nupkgDir.GlobFiles("**/LibHac.dll"));
|
||||||
|
toSign.Add(netFxDir / "hactoolnet.exe");
|
||||||
|
toSign.Add(coreFxDir / "hactoolnet.dll");
|
||||||
|
toSign.Add(signedMergedExe);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(SignedArtifactsDirectory);
|
||||||
|
File.Copy(CliMergedExe, signedMergedExe, true);
|
||||||
|
|
||||||
|
SignAssemblies(password, toSign.Select(x => x.ToString()).ToArray());
|
||||||
|
|
||||||
|
// Avoid having multiple signed versions of the same file
|
||||||
|
File.Copy(nupkgDir / "lib" / "net46" / "LibHac.dll", netFxDir / "LibHac.dll", true);
|
||||||
|
File.Copy(nupkgDir / "lib" / "netcoreapp2.1" / "LibHac.dll", coreFxDir / "LibHac.dll", true);
|
||||||
|
|
||||||
|
ZipDirectory(SignedArtifactsDirectory / Path.GetFileName(nupkgFile), nupkgDir, pkgFileList);
|
||||||
|
ZipDirectory(SignedArtifactsDirectory / Path.GetFileName(CliFrameworkZip), netFxDir);
|
||||||
|
ZipDirectory(SignedArtifactsDirectory / Path.GetFileName(CliCoreZip), coreFxDir);
|
||||||
|
|
||||||
|
File.Copy(snupkgFile, SignedArtifactsDirectory / Path.GetFileName(snupkgFile));
|
||||||
|
|
||||||
|
SignNupkg(SignedArtifactsDirectory / Path.GetFileName(nupkgFile), password);
|
||||||
|
SignNupkg(SignedArtifactsDirectory / Path.GetFileName(snupkgFile), password);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Directory.Delete(SignedArtifactsDirectory, true);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(nupkgDir, true);
|
||||||
|
Directory.Delete(netFxDir, true);
|
||||||
|
Directory.Delete(coreFxDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadPassword()
|
||||||
|
{
|
||||||
|
var pwd = new StringBuilder();
|
||||||
|
ConsoleKeyInfo key;
|
||||||
|
|
||||||
|
Console.Write("Enter certificate password (Empty password to skip): ");
|
||||||
|
do
|
||||||
|
{
|
||||||
|
key = Console.ReadKey(true);
|
||||||
|
|
||||||
|
// Ignore any key out of range.
|
||||||
|
if (((int)key.Key) >= '!' && ((int)key.Key <= '~'))
|
||||||
|
{
|
||||||
|
// Append the character to the password.
|
||||||
|
pwd.Append(key.KeyChar);
|
||||||
|
Console.Write("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit if Enter key is pressed.
|
||||||
|
} while (key.Key != ConsoleKey.Enter);
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
return pwd.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
136
build/RepackNuget.cs
Normal file
136
build/RepackNuget.cs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Nuke.Common.Tools.NuGet;
|
||||||
|
using static Nuke.Common.IO.FileSystemTasks;
|
||||||
|
using static Nuke.Common.IO.PathConstruction;
|
||||||
|
|
||||||
|
namespace LibHacBuild
|
||||||
|
{
|
||||||
|
public partial class Build
|
||||||
|
{
|
||||||
|
public void RepackNugetPackage(string path)
|
||||||
|
{
|
||||||
|
AbsolutePath tempDir = TempDirectory / Path.GetFileName(path);
|
||||||
|
AbsolutePath libDir = tempDir / "lib";
|
||||||
|
AbsolutePath relsFile = tempDir / "_rels" / ".rels";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EnsureCleanDirectory(tempDir);
|
||||||
|
List<string> fileList = UnzipPackage(path, tempDir);
|
||||||
|
|
||||||
|
string newPsmdcpName = CalcPsmdcpName(libDir);
|
||||||
|
string newPsmdcpPath = RenamePsmdcp(tempDir, newPsmdcpName);
|
||||||
|
EditManifestRelationships(relsFile, newPsmdcpPath);
|
||||||
|
|
||||||
|
int index = fileList.FindIndex(x => x.Contains(".psmdcp"));
|
||||||
|
fileList[index] = newPsmdcpPath;
|
||||||
|
|
||||||
|
IEnumerable<string> files = Directory.EnumerateFiles(tempDir, "*.json", SearchOption.AllDirectories)
|
||||||
|
.Concat(Directory.EnumerateFiles(tempDir, "*.xml", SearchOption.AllDirectories))
|
||||||
|
.Concat(Directory.EnumerateFiles(tempDir, "*.rels", SearchOption.AllDirectories))
|
||||||
|
.Concat(Directory.EnumerateFiles(tempDir, "*.psmdcp", SearchOption.AllDirectories))
|
||||||
|
.Concat(Directory.EnumerateFiles(tempDir, "*.nuspec", SearchOption.AllDirectories));
|
||||||
|
|
||||||
|
foreach (string filename in files)
|
||||||
|
{
|
||||||
|
Console.WriteLine(filename);
|
||||||
|
ReplaceLineEndings(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipDirectory(path, tempDir, fileList);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> UnzipPackage(string package, string dest)
|
||||||
|
{
|
||||||
|
var fileList = new List<string>();
|
||||||
|
|
||||||
|
UnzipFiles(package, dest);
|
||||||
|
|
||||||
|
using (var s = new ZipInputStream(File.OpenRead(package)))
|
||||||
|
{
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = s.GetNextEntry()) != null)
|
||||||
|
{
|
||||||
|
fileList.Add(entry.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CalcPsmdcpName(string libDir)
|
||||||
|
{
|
||||||
|
using (SHA256 sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
foreach (string file in Directory.EnumerateFiles(libDir))
|
||||||
|
{
|
||||||
|
byte[] data = File.ReadAllBytes(file);
|
||||||
|
sha.TransformBlock(data, 0, data.Length, data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sha.TransformFinalBlock(new byte[0], 0, 0);
|
||||||
|
|
||||||
|
return ToHexString(sha.Hash).ToLower().Substring(0, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RenamePsmdcp(string packageDir, string name)
|
||||||
|
{
|
||||||
|
string fileName = Directory.EnumerateFiles(packageDir, "*.psmdcp", SearchOption.AllDirectories).Single();
|
||||||
|
string newFileName = Path.Combine(Path.GetDirectoryName(fileName), name + ".psmdcp");
|
||||||
|
Directory.Move(fileName, newFileName);
|
||||||
|
|
||||||
|
return Path.GetRelativePath(packageDir, newFileName).Replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
|
||||||
|
public void EditManifestRelationships(string path, string psmdcpPath)
|
||||||
|
{
|
||||||
|
XDocument doc = XDocument.Load(path);
|
||||||
|
XNamespace ns = doc.Root.GetDefaultNamespace();
|
||||||
|
|
||||||
|
foreach (XElement rs in doc.Root.Elements(ns + "Relationship"))
|
||||||
|
{
|
||||||
|
using (SHA256 sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
if (rs.Attribute("Target").Value.Contains(".psmdcp"))
|
||||||
|
{
|
||||||
|
rs.Attribute("Target").Value = "/" + psmdcpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
string s = "/" + psmdcpPath + rs.Attribute("Target").Value;
|
||||||
|
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(s));
|
||||||
|
string id = "R" + ToHexString(hash).Substring(0, 16);
|
||||||
|
rs.Attribute("Id").Value = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignNupkg(string pkgPath, string password)
|
||||||
|
{
|
||||||
|
NuGetTasks.NuGet(
|
||||||
|
$"sign \"{pkgPath}\" -CertificatePath cert.pfx -CertificatePassword {password} -Timestamper http://timestamp.digicert.com",
|
||||||
|
outputFilter: x => x.Replace(password, "hunter2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToHexString(byte[] arr)
|
||||||
|
{
|
||||||
|
return BitConverter.ToString(arr).ToLower().Replace("-", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,17 @@
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<RootNamespace></RootNamespace>
|
<RootNamespace>LibHacBuild</RootNamespace>
|
||||||
<IsPackable>False</IsPackable>
|
<IsPackable>False</IsPackable>
|
||||||
<NoWarn>CS0649;CS0169</NoWarn>
|
<NoWarn>CS0649;CS0169</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Nuke.Common" Version="0.12.0" />
|
<PackageReference Include="GitVersion.CommandLine.DotNetCore" Version="4.0.0" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.0.0" />
|
|
||||||
<PackageReference Include="ILRepack.Lib" Version="2.0.16" NoWarn="NU1701" />
|
<PackageReference Include="ILRepack.Lib" Version="2.0.16" NoWarn="NU1701" />
|
||||||
|
<PackageReference Include="NuGet.CommandLine" Version="4.9.3" />
|
||||||
|
<PackageReference Include="Nuke.Common" Version="0.16.0" />
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace LibHac.Nand
|
||||||
{
|
{
|
||||||
path = ToDiscUtilsPath(PathTools.Normalize(path));
|
path = ToDiscUtilsPath(PathTools.Normalize(path));
|
||||||
|
|
||||||
if (path == @"\\") return true;
|
if (path == @"\") return true;
|
||||||
|
|
||||||
return Fs.DirectoryExists(path);
|
return Fs.DirectoryExists(path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace LibHac.Nand
|
||||||
{
|
{
|
||||||
private GuidPartitionInfo ProdInfo { get; }
|
private GuidPartitionInfo ProdInfo { get; }
|
||||||
private GuidPartitionInfo ProdInfoF { get; }
|
private GuidPartitionInfo ProdInfoF { get; }
|
||||||
|
private GuidPartitionInfo[] Package2 { get; }
|
||||||
private GuidPartitionInfo Safe { get; }
|
private GuidPartitionInfo Safe { get; }
|
||||||
private GuidPartitionInfo System { get; }
|
private GuidPartitionInfo System { get; }
|
||||||
private GuidPartitionInfo User { get; }
|
private GuidPartitionInfo User { get; }
|
||||||
|
@ -23,6 +24,15 @@ namespace LibHac.Nand
|
||||||
GuidPartitionInfo[] partitions = disc.Partitions.Select(x => (GuidPartitionInfo)x).ToArray();
|
GuidPartitionInfo[] partitions = disc.Partitions.Select(x => (GuidPartitionInfo)x).ToArray();
|
||||||
ProdInfo = partitions.FirstOrDefault(x => x.Name == "PRODINFO");
|
ProdInfo = partitions.FirstOrDefault(x => x.Name == "PRODINFO");
|
||||||
ProdInfoF = partitions.FirstOrDefault(x => x.Name == "PRODINFOF");
|
ProdInfoF = partitions.FirstOrDefault(x => x.Name == "PRODINFOF");
|
||||||
|
Package2 = new[]
|
||||||
|
{
|
||||||
|
partitions.FirstOrDefault(x => x.Name == "BCPKG2-1-Normal-Main"),
|
||||||
|
partitions.FirstOrDefault(x => x.Name == "BCPKG2-2-Normal-Sub"),
|
||||||
|
partitions.FirstOrDefault(x => x.Name == "BCPKG2-3-SafeMode-Main"),
|
||||||
|
partitions.FirstOrDefault(x => x.Name == "BCPKG2-4-SafeMode-Sub"),
|
||||||
|
partitions.FirstOrDefault(x => x.Name == "BCPKG2-5-Repair-Main"),
|
||||||
|
partitions.FirstOrDefault(x => x.Name == "BCPKG2-6-Repair-Sub")
|
||||||
|
};
|
||||||
Safe = partitions.FirstOrDefault(x => x.Name == "SAFE");
|
Safe = partitions.FirstOrDefault(x => x.Name == "SAFE");
|
||||||
System = partitions.FirstOrDefault(x => x.Name == "SYSTEM");
|
System = partitions.FirstOrDefault(x => x.Name == "SYSTEM");
|
||||||
User = partitions.FirstOrDefault(x => x.Name == "USER");
|
User = partitions.FirstOrDefault(x => x.Name == "USER");
|
||||||
|
@ -44,6 +54,11 @@ namespace LibHac.Nand
|
||||||
return new FatFileSystemProvider(fat);
|
return new FatFileSystemProvider(fat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IStorage OpenPackage2(int index)
|
||||||
|
{
|
||||||
|
return Package2[index].Open().AsStorage().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
public FatFileSystemProvider OpenSafePartition()
|
public FatFileSystemProvider OpenSafePartition()
|
||||||
{
|
{
|
||||||
IStorage encStorage = Safe.Open().AsStorage();
|
IStorage encStorage = Safe.Open().AsStorage();
|
||||||
|
|
|
@ -122,7 +122,7 @@ namespace LibHac
|
||||||
/// <remarks>Example:
|
/// <remarks>Example:
|
||||||
/// A 4-bit offset binary value with a positive bias can store
|
/// A 4-bit offset binary value with a positive bias can store
|
||||||
/// the values 8 through -7 inclusive.
|
/// the values 8 through -7 inclusive.
|
||||||
/// A 4-bit offset binary value with a positive bias can store
|
/// A 4-bit offset binary value with a negative bias can store
|
||||||
/// the values 7 through -8 inclusive.</remarks>
|
/// the values 7 through -8 inclusive.</remarks>
|
||||||
public enum OffsetBias
|
public enum OffsetBias
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,14 +5,16 @@ namespace LibHac.IO
|
||||||
{
|
{
|
||||||
public class AesXtsDirectory : IDirectory
|
public class AesXtsDirectory : IDirectory
|
||||||
{
|
{
|
||||||
public IFileSystem ParentFileSystem { get; }
|
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||||
|
public AesXtsFileSystem ParentFileSystem { get; }
|
||||||
|
|
||||||
public string FullPath { get; }
|
public string FullPath { get; }
|
||||||
public OpenDirectoryMode Mode { get; }
|
public OpenDirectoryMode Mode { get; }
|
||||||
|
|
||||||
private IFileSystem BaseFileSystem { get; }
|
private IFileSystem BaseFileSystem { get; }
|
||||||
private IDirectory BaseDirectory { get; }
|
private IDirectory BaseDirectory { get; }
|
||||||
|
|
||||||
public AesXtsDirectory(IFileSystem parentFs, IDirectory baseDir, OpenDirectoryMode mode)
|
public AesXtsDirectory(AesXtsFileSystem parentFs, IDirectory baseDir, OpenDirectoryMode mode)
|
||||||
{
|
{
|
||||||
ParentFileSystem = parentFs;
|
ParentFileSystem = parentFs;
|
||||||
BaseDirectory = baseDir;
|
BaseDirectory = baseDir;
|
||||||
|
@ -66,7 +68,7 @@ namespace LibHac.IO
|
||||||
if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return 0;
|
if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return 0;
|
||||||
|
|
||||||
file.Read(buffer, 0x48);
|
file.Read(buffer, 0x48);
|
||||||
return BitConverter.ToInt32(buffer, 0);
|
return BitConverter.ToInt64(buffer, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
catch (ArgumentOutOfRangeException)
|
||||||
|
|
|
@ -31,10 +31,19 @@ namespace LibHac.IO
|
||||||
throw new ArgumentException("NAX0 key derivation failed.");
|
throw new ArgumentException("NAX0 key derivation failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
IStorage encStorage = BaseFile.AsStorage().Slice(HeaderLength, Header.Size);
|
IStorage encStorage = BaseFile.AsStorage().Slice(HeaderLength, Util.AlignUp(Header.Size, 0x10));
|
||||||
BaseStorage = new CachedStorage(new Aes128XtsStorage(encStorage, Header.DecryptedKey1, Header.DecryptedKey2, BlockSize, true), 4, true);
|
BaseStorage = new CachedStorage(new Aes128XtsStorage(encStorage, Header.DecryptedKey1, Header.DecryptedKey2, BlockSize, true), 4, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] GetKey()
|
||||||
|
{
|
||||||
|
var key = new byte[0x20];
|
||||||
|
Array.Copy(Header.DecryptedKey1, 0, key, 0, 0x10);
|
||||||
|
Array.Copy(Header.DecryptedKey2, 0, key, 0x10, 0x10);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
public override int Read(Span<byte> destination, long offset)
|
public override int Read(Span<byte> destination, long offset)
|
||||||
{
|
{
|
||||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||||
|
@ -63,6 +72,8 @@ namespace LibHac.IO
|
||||||
|
|
||||||
public override void SetSize(long size)
|
public override void SetSize(long size)
|
||||||
{
|
{
|
||||||
|
Header.SetSize(size, VerificationKey);
|
||||||
|
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace LibHac.IO
|
||||||
public uint Magic { get; }
|
public uint Magic { get; }
|
||||||
public byte[] EncryptedKey1 { get; } = new byte[0x10];
|
public byte[] EncryptedKey1 { get; } = new byte[0x10];
|
||||||
public byte[] EncryptedKey2 { get; } = new byte[0x10];
|
public byte[] EncryptedKey2 { get; } = new byte[0x10];
|
||||||
public long Size { get; }
|
public long Size { get; private set; }
|
||||||
|
|
||||||
public byte[] DecryptedKey1 { get; } = new byte[0x10];
|
public byte[] DecryptedKey1 { get; } = new byte[0x10];
|
||||||
public byte[] DecryptedKey2 { get; } = new byte[0x10];
|
public byte[] DecryptedKey2 { get; } = new byte[0x10];
|
||||||
|
@ -37,10 +37,10 @@ namespace LibHac.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AesXtsFileHeader(byte[] key1, byte[] key2, long fileSize, string path, byte[] kekSeed, byte[] verificationKey)
|
public AesXtsFileHeader(byte[] key, long fileSize, string path, byte[] kekSeed, byte[] verificationKey)
|
||||||
{
|
{
|
||||||
Array.Copy(key1, DecryptedKey1, 0x10);
|
Array.Copy(key, 0, DecryptedKey1, 0, 0x10);
|
||||||
Array.Copy(key2, DecryptedKey2, 0x10);
|
Array.Copy(key, 0x10, DecryptedKey2, 0, 0x10);
|
||||||
Magic = AesXtsFileMagic;
|
Magic = AesXtsFileMagic;
|
||||||
Size = fileSize;
|
Size = fileSize;
|
||||||
|
|
||||||
|
@ -63,6 +63,12 @@ namespace LibHac.IO
|
||||||
return Util.ArraysEqual(hmac, Signature);
|
return Util.ArraysEqual(hmac, Signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetSize(long size, byte[] verificationKey)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
Signature = CalculateHmac(verificationKey);
|
||||||
|
}
|
||||||
|
|
||||||
private void DecryptKeys()
|
private void DecryptKeys()
|
||||||
{
|
{
|
||||||
Crypto.DecryptEcb(Kek1, EncryptedKey1, DecryptedKey1, 0x10);
|
Crypto.DecryptEcb(Kek1, EncryptedKey1, DecryptedKey1, 0x10);
|
||||||
|
|
|
@ -33,10 +33,23 @@ namespace LibHac.IO
|
||||||
|
|
||||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||||
{
|
{
|
||||||
long containerSize = AesXtsFile.HeaderLength + Util.AlignUp(size, 0x16);
|
CreateFile(path, size, options, new byte[0x20]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="AesXtsFile"/> using the provided key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The full path of the file to create.</param>
|
||||||
|
/// <param name="size">The initial size of the created file.</param>
|
||||||
|
/// <param name="options">Flags to control how the file is created.
|
||||||
|
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||||
|
/// <param name="key">The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key.</param>
|
||||||
|
public void CreateFile(string path, long size, CreateFileOptions options, byte[] key)
|
||||||
|
{
|
||||||
|
long containerSize = AesXtsFile.HeaderLength + Util.AlignUp(size, 0x10);
|
||||||
BaseFileSystem.CreateFile(path, containerSize, options);
|
BaseFileSystem.CreateFile(path, containerSize, options);
|
||||||
|
|
||||||
var header = new AesXtsFileHeader(new byte[0x10], new byte[0x10], size, path, KekSource, ValidationKey);
|
var header = new AesXtsFileHeader(key, size, path, KekSource, ValidationKey);
|
||||||
|
|
||||||
using (IFile baseFile = BaseFileSystem.OpenFile(path, OpenMode.Write))
|
using (IFile baseFile = BaseFileSystem.OpenFile(path, OpenMode.Write))
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,18 +29,21 @@ namespace LibHac.IO
|
||||||
long inPos = offset;
|
long inPos = offset;
|
||||||
int outPos = 0;
|
int outPos = 0;
|
||||||
int remaining = destination.Length;
|
int remaining = destination.Length;
|
||||||
|
int sourceIndex = FindSource(inPos);
|
||||||
|
|
||||||
while (remaining > 0)
|
while (remaining > 0)
|
||||||
{
|
{
|
||||||
ConcatSource entry = FindSource(inPos);
|
ConcatSource entry = Sources[sourceIndex];
|
||||||
long sourcePos = inPos - entry.StartOffset;
|
long entryPos = inPos - entry.StartOffset;
|
||||||
|
long entryRemain = entry.StartOffset + entry.Size - inPos;
|
||||||
|
|
||||||
int bytesToRead = (int)Math.Min(entry.EndOffset - inPos, remaining);
|
int bytesToRead = (int)Math.Min(entryRemain, remaining);
|
||||||
entry.Storage.Read(destination.Slice(outPos, bytesToRead), sourcePos);
|
entry.Storage.Read(destination.Slice(outPos, bytesToRead), entryPos);
|
||||||
|
|
||||||
outPos += bytesToRead;
|
outPos += bytesToRead;
|
||||||
inPos += bytesToRead;
|
inPos += bytesToRead;
|
||||||
remaining -= bytesToRead;
|
remaining -= bytesToRead;
|
||||||
|
sourceIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,18 +52,21 @@ namespace LibHac.IO
|
||||||
long inPos = offset;
|
long inPos = offset;
|
||||||
int outPos = 0;
|
int outPos = 0;
|
||||||
int remaining = source.Length;
|
int remaining = source.Length;
|
||||||
|
int sourceIndex = FindSource(inPos);
|
||||||
|
|
||||||
while (remaining > 0)
|
while (remaining > 0)
|
||||||
{
|
{
|
||||||
ConcatSource storage = FindSource(inPos);
|
ConcatSource entry = Sources[sourceIndex];
|
||||||
long sourcePos = inPos - storage.StartOffset;
|
long entryPos = inPos - entry.StartOffset;
|
||||||
|
long entryRemain = entry.StartOffset + entry.Size - inPos;
|
||||||
|
|
||||||
int bytesToWrite = (int)Math.Min(storage.EndOffset - inPos, remaining);
|
int bytesToWrite = (int)Math.Min(entryRemain, remaining);
|
||||||
storage.Storage.Write(source.Slice(outPos, bytesToWrite), sourcePos);
|
entry.Storage.Write(source.Slice(outPos, bytesToWrite), entryPos);
|
||||||
|
|
||||||
outPos += bytesToWrite;
|
outPos += bytesToWrite;
|
||||||
inPos += bytesToWrite;
|
inPos += bytesToWrite;
|
||||||
remaining -= bytesToWrite;
|
remaining -= bytesToWrite;
|
||||||
|
sourceIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,27 +78,46 @@ namespace LibHac.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConcatSource FindSource(long offset)
|
private int FindSource(long offset)
|
||||||
{
|
{
|
||||||
foreach (ConcatSource info in Sources)
|
if (offset < 0 || offset >= Length)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), offset, "The Storage does not contain this offset.");
|
||||||
|
|
||||||
|
int lo = 0;
|
||||||
|
int hi = Sources.Length - 1;
|
||||||
|
|
||||||
|
while (lo <= hi)
|
||||||
{
|
{
|
||||||
if (info.EndOffset > offset) return info;
|
int mid = lo + ((hi - lo) >> 1);
|
||||||
|
|
||||||
|
long val = Sources[mid].StartOffset;
|
||||||
|
|
||||||
|
if (val == offset) return mid;
|
||||||
|
|
||||||
|
if (val < offset)
|
||||||
|
{
|
||||||
|
lo = mid + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hi = mid - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(offset), offset, "The Storage does not contain this offset.");
|
return lo - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConcatSource
|
private class ConcatSource
|
||||||
{
|
{
|
||||||
public IStorage Storage { get; }
|
public IStorage Storage { get; }
|
||||||
public long StartOffset { get; }
|
public long StartOffset { get; }
|
||||||
public long EndOffset { get; }
|
public long Size { get; }
|
||||||
|
|
||||||
public ConcatSource(IStorage storage, long startOffset, long length)
|
public ConcatSource(IStorage storage, long startOffset, long length)
|
||||||
{
|
{
|
||||||
Storage = storage;
|
Storage = storage;
|
||||||
StartOffset = startOffset;
|
StartOffset = startOffset;
|
||||||
EndOffset = startOffset + length;
|
Size = length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace LibHac.IO
|
||||||
destFs.CreateFile(subDstPath, entry.Size, options);
|
destFs.CreateFile(subDstPath, entry.Size, options);
|
||||||
|
|
||||||
using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read))
|
using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read))
|
||||||
using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write))
|
using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write | OpenMode.Append))
|
||||||
{
|
{
|
||||||
logger?.LogMessage(subSrcPath);
|
logger?.LogMessage(subSrcPath);
|
||||||
srcFile.CopyTo(dstFile, logger);
|
srcFile.CopyTo(dstFile, logger);
|
||||||
|
@ -61,7 +61,17 @@ namespace LibHac.IO
|
||||||
|
|
||||||
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem)
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
return fileSystem.OpenDirectory("/", OpenDirectoryMode.All).EnumerateEntries("*", SearchOptions.RecurseSubdirectories);
|
return fileSystem.EnumerateEntries("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem, string searchPattern)
|
||||||
|
{
|
||||||
|
return fileSystem.EnumerateEntries(searchPattern, SearchOptions.RecurseSubdirectories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem, string searchPattern, SearchOptions searchOptions)
|
||||||
|
{
|
||||||
|
return fileSystem.OpenDirectory("/", OpenDirectoryMode.All).EnumerateEntries(searchPattern, searchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
|
||||||
|
|
|
@ -6,9 +6,19 @@ namespace LibHac.IO
|
||||||
{
|
{
|
||||||
private string BasePath { get; }
|
private string BasePath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a directory on local storage as an <see cref="IFileSystem"/>.
|
||||||
|
/// The directory will be created if it does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="basePath">The path that will be the root of the <see cref="LocalFileSystem"/>.</param>
|
||||||
public LocalFileSystem(string basePath)
|
public LocalFileSystem(string basePath)
|
||||||
{
|
{
|
||||||
BasePath = Path.GetFullPath(basePath);
|
BasePath = Path.GetFullPath(basePath);
|
||||||
|
|
||||||
|
if (!Directory.Exists(BasePath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(BasePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string ResolveLocalPath(string path)
|
internal string ResolveLocalPath(string path)
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace LibHac.IO
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
int toRead = (int)Math.Min(count, Length - Position);
|
int toRead = (int)Math.Min(count, Length - Position);
|
||||||
BaseFile.Read(buffer.AsSpan(offset, count), Position);
|
BaseFile.Read(buffer.AsSpan(offset, toRead), Position);
|
||||||
|
|
||||||
Position += toRead;
|
Position += toRead;
|
||||||
return toRead;
|
return toRead;
|
||||||
|
|
|
@ -207,6 +207,8 @@ namespace LibHac.IO.RomFs
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
ReadOnlySpan<byte> pathBytes = GetUtf8Bytes(path);
|
ReadOnlySpan<byte> pathBytes = GetUtf8Bytes(path);
|
||||||
|
|
||||||
|
if(path == "/") throw new ArgumentException("Path cannot be empty");
|
||||||
|
|
||||||
CreateFileRecursiveInternal(pathBytes, ref fileInfo);
|
CreateFileRecursiveInternal(pathBytes, ref fileInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +349,7 @@ namespace LibHac.IO.RomFs
|
||||||
{
|
{
|
||||||
ref FileRomEntry entry = ref FileTable.AddOrGet(ref key, out int offset, out bool alreadyExists, out _);
|
ref FileRomEntry entry = ref FileTable.AddOrGet(ref key, out int offset, out bool alreadyExists, out _);
|
||||||
entry.Info = fileInfo;
|
entry.Info = fileInfo;
|
||||||
if (alreadyExists) entry.NextSibling = -1;
|
if (!alreadyExists) entry.NextSibling = -1;
|
||||||
|
|
||||||
ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset);
|
ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace LibHac.IO.RomFs
|
namespace LibHac.IO.RomFs
|
||||||
|
@ -13,8 +14,7 @@ namespace LibHac.IO.RomFs
|
||||||
private int[] Buckets { get; set; }
|
private int[] Buckets { get; set; }
|
||||||
private byte[] Entries { get; set; }
|
private byte[] Entries { get; set; }
|
||||||
|
|
||||||
// Hack around not being able to get the size of generic structures
|
private readonly int _sizeOfEntry = Unsafe.SizeOf<RomFsEntry>();
|
||||||
private readonly int _sizeOfEntry = 12 + Marshal.SizeOf<T>();
|
|
||||||
|
|
||||||
public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage)
|
public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage)
|
||||||
{
|
{
|
||||||
|
@ -49,7 +49,7 @@ namespace LibHac.IO.RomFs
|
||||||
|
|
||||||
public bool TryGetValue(int offset, out RomKeyValuePair<T> value)
|
public bool TryGetValue(int offset, out RomKeyValuePair<T> value)
|
||||||
{
|
{
|
||||||
if (offset < 0 || offset + _sizeOfEntry >= Entries.Length)
|
if (offset < 0 || offset + _sizeOfEntry > Entries.Length)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
return false;
|
return false;
|
||||||
|
@ -68,13 +68,12 @@ namespace LibHac.IO.RomFs
|
||||||
|
|
||||||
public ref T GetValueReference(int offset)
|
public ref T GetValueReference(int offset)
|
||||||
{
|
{
|
||||||
ref RomFsEntry entry = ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];
|
return ref Unsafe.As<byte, RomFsEntry>(ref Entries[offset]).Value;
|
||||||
return ref entry.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref T GetValueReference(int offset, out Span<byte> name)
|
public ref T GetValueReference(int offset, out Span<byte> name)
|
||||||
{
|
{
|
||||||
ref RomFsEntry entry = ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];
|
ref RomFsEntry entry = ref Unsafe.As<byte, RomFsEntry>(ref Entries[offset]);
|
||||||
|
|
||||||
name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength);
|
name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength);
|
||||||
return ref entry.Value;
|
return ref entry.Value;
|
||||||
|
@ -249,7 +248,7 @@ namespace LibHac.IO.RomFs
|
||||||
|
|
||||||
private ref RomFsEntry GetEntryReference(int offset, out Span<byte> name)
|
private ref RomFsEntry GetEntryReference(int offset, out Span<byte> name)
|
||||||
{
|
{
|
||||||
ref RomFsEntry entry = ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];
|
ref RomFsEntry entry = ref Unsafe.As<byte, RomFsEntry>(ref Entries[offset]);
|
||||||
|
|
||||||
name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength);
|
name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength);
|
||||||
return ref entry;
|
return ref entry;
|
||||||
|
@ -257,7 +256,7 @@ namespace LibHac.IO.RomFs
|
||||||
|
|
||||||
private ref RomFsEntry GetEntryReference(int offset, out Span<byte> name, int nameLength)
|
private ref RomFsEntry GetEntryReference(int offset, out Span<byte> name, int nameLength)
|
||||||
{
|
{
|
||||||
ref RomFsEntry entry = ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];
|
ref RomFsEntry entry = ref Unsafe.As<byte, RomFsEntry>(ref Entries[offset]);
|
||||||
|
|
||||||
name = Entries.AsSpan(offset + _sizeOfEntry, nameLength);
|
name = Entries.AsSpan(offset + _sizeOfEntry, nameLength);
|
||||||
return ref entry;
|
return ref entry;
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace LibHac.IO
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
int toRead = (int) Math.Min(count, Length - Position);
|
int toRead = (int) Math.Min(count, Length - Position);
|
||||||
BaseStorage.Read(buffer.AsSpan(offset, count), Position);
|
BaseStorage.Read(buffer.AsSpan(offset, toRead), Position);
|
||||||
|
|
||||||
Position += toRead;
|
Position += toRead;
|
||||||
return toRead;
|
return toRead;
|
||||||
|
|
|
@ -10,12 +10,20 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
public class Keyset
|
public class Keyset
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The number of keyblobs that were used for < 6.2.0 crypto
|
||||||
|
/// </summary>
|
||||||
|
private const int UsedKeyblobCount = 6;
|
||||||
|
|
||||||
public byte[][] KeyblobKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
public byte[][] KeyblobKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
||||||
public byte[][] KeyblobMacKeys { 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[][] EncryptedKeyblobs { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0xB0);
|
||||||
public byte[][] Keyblobs { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x90);
|
public byte[][] Keyblobs { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x90);
|
||||||
public byte[][] KeyblobKeySources { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
public byte[][] KeyblobKeySources { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
||||||
public byte[] KeyblobMacKeySource { get; } = new byte[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[] MasterKeySource { get; } = new byte[0x10];
|
public byte[] MasterKeySource { get; } = new byte[0x10];
|
||||||
public byte[][] MasterKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
public byte[][] MasterKeys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
||||||
public byte[][] Package1Keys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
public byte[][] Package1Keys { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
||||||
|
@ -28,29 +36,31 @@ namespace LibHac
|
||||||
public byte[] KeyAreaKeySystemSource { get; } = new byte[0x10];
|
public byte[] KeyAreaKeySystemSource { get; } = new byte[0x10];
|
||||||
public byte[] SaveMacKekSource { get; } = new byte[0x10];
|
public byte[] SaveMacKekSource { get; } = new byte[0x10];
|
||||||
public byte[] SaveMacKeySource { get; } = new byte[0x10];
|
public byte[] SaveMacKeySource { get; } = new byte[0x10];
|
||||||
public byte[] TitlekekSource { get; } = new byte[0x10];
|
public byte[] TitleKekSource { get; } = new byte[0x10];
|
||||||
public byte[] HeaderKekSource { get; } = new byte[0x10];
|
public byte[] HeaderKekSource { get; } = new byte[0x10];
|
||||||
public byte[] SdCardKekSource { get; } = new byte[0x10];
|
public byte[] SdCardKekSource { get; } = new byte[0x10];
|
||||||
public byte[][] SdCardKeySources { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
public byte[][] SdCardKeySources { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
||||||
public byte[][] SdCardKeySourcesSpecific { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
|
||||||
public byte[] HeaderKeySource { get; } = new byte[0x20];
|
public byte[] HeaderKeySource { get; } = new byte[0x20];
|
||||||
public byte[] HeaderKey { get; } = new byte[0x20];
|
public byte[] HeaderKey { get; } = new byte[0x20];
|
||||||
public byte[] XciHeaderKey { get; } = new byte[0x10];
|
public byte[] XciHeaderKey { get; } = new byte[0x10];
|
||||||
public byte[][] Titlekeks { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
public byte[][] TitleKeks { get; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
||||||
public byte[][][] KeyAreaKeys { get; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
|
public byte[][][] KeyAreaKeys { get; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
|
||||||
public byte[] SaveMacKey { get; } = new byte[0x10];
|
|
||||||
public byte[][] SdCardKeys { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
|
||||||
public byte[] EticketRsaKek { get; } = new byte[0x10];
|
public byte[] EticketRsaKek { get; } = new byte[0x10];
|
||||||
public byte[] RetailSpecificAesKeySource { get; } = new byte[0x10];
|
public byte[] RetailSpecificAesKeySource { get; } = new byte[0x10];
|
||||||
public byte[] PerConsoleKeySource { get; } = new byte[0x10];
|
public byte[] PerConsoleKeySource { get; } = new byte[0x10];
|
||||||
public byte[] BisKekSource { get; } = new byte[0x10];
|
public byte[] BisKekSource { get; } = new byte[0x10];
|
||||||
public byte[][] BisKeySource { get; } = Util.CreateJaggedArray<byte[][]>(3, 0x20);
|
public byte[][] BisKeySource { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x20);
|
||||||
|
public byte[] SslRsaKek { get; } = new byte[0x10];
|
||||||
|
|
||||||
|
// Device-specific keys
|
||||||
public byte[] SecureBootKey { get; } = new byte[0x10];
|
public byte[] SecureBootKey { get; } = new byte[0x10];
|
||||||
public byte[] TsecKey { get; } = new byte[0x10];
|
public byte[] TsecKey { get; } = new byte[0x10];
|
||||||
public byte[] DeviceKey { 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.CreateJaggedArray<byte[][]>(4, 0x20);
|
||||||
|
public byte[] SaveMacKey { get; } = new byte[0x10];
|
||||||
public byte[] SdSeed { get; } = new byte[0x10];
|
public byte[] SdSeed { get; } = new byte[0x10];
|
||||||
|
public byte[][] SdCardKeySourcesSpecific { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
||||||
|
public byte[][] SdCardKeys { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
||||||
|
|
||||||
public RSAParameters EticketExtKeyRsa { get; set; }
|
public RSAParameters EticketExtKeyRsa { get; set; }
|
||||||
|
|
||||||
|
@ -128,6 +138,9 @@ namespace LibHac
|
||||||
DecryptKeyblobs(logger);
|
DecryptKeyblobs(logger);
|
||||||
ReadKeyblobs();
|
ReadKeyblobs();
|
||||||
|
|
||||||
|
Derive620MasterKeks();
|
||||||
|
DeriveMasterKeys();
|
||||||
|
|
||||||
DerivePerConsoleKeys();
|
DerivePerConsoleKeys();
|
||||||
DerivePerFirmwareKeys();
|
DerivePerFirmwareKeys();
|
||||||
DeriveNcaHeaderKey();
|
DeriveNcaHeaderKey();
|
||||||
|
@ -141,7 +154,7 @@ namespace LibHac
|
||||||
bool haveKeyblobMacKeySource = !MasterKeySource.IsEmpty();
|
bool haveKeyblobMacKeySource = !MasterKeySource.IsEmpty();
|
||||||
var temp = new byte[0x10];
|
var temp = new byte[0x10];
|
||||||
|
|
||||||
for (int i = 0; i < 0x20; i++)
|
for (int i = 0; i < UsedKeyblobCount; i++)
|
||||||
{
|
{
|
||||||
if (KeyblobKeySources[i].IsEmpty()) continue;
|
if (KeyblobKeySources[i].IsEmpty()) continue;
|
||||||
|
|
||||||
|
@ -160,7 +173,7 @@ namespace LibHac
|
||||||
var expectedCmac = new byte[0x10];
|
var expectedCmac = new byte[0x10];
|
||||||
var counter = new byte[0x10];
|
var counter = new byte[0x10];
|
||||||
|
|
||||||
for (int i = 0; i < 0x20; i++)
|
for (int i = 0; i < UsedKeyblobCount; i++)
|
||||||
{
|
{
|
||||||
if (KeyblobKeys[i].IsEmpty() || KeyblobMacKeys[i].IsEmpty() || EncryptedKeyblobs[i].IsEmpty())
|
if (KeyblobKeys[i].IsEmpty() || KeyblobMacKeys[i].IsEmpty() || EncryptedKeyblobs[i].IsEmpty())
|
||||||
{
|
{
|
||||||
|
@ -187,21 +200,34 @@ namespace LibHac
|
||||||
|
|
||||||
private void ReadKeyblobs()
|
private void ReadKeyblobs()
|
||||||
{
|
{
|
||||||
var masterKek = new byte[0x10];
|
for (int i = 0; i < UsedKeyblobCount; i++)
|
||||||
|
|
||||||
bool haveMasterKeySource = !MasterKeySource.IsEmpty();
|
|
||||||
|
|
||||||
for (int i = 0; i < 0x20; i++)
|
|
||||||
{
|
{
|
||||||
if (Keyblobs[i].IsEmpty()) continue;
|
if (Keyblobs[i].IsEmpty()) continue;
|
||||||
|
|
||||||
Array.Copy(Keyblobs[i], 0x80, Package1Keys[i], 0, 0x10);
|
Array.Copy(Keyblobs[i], 0x80, Package1Keys[i], 0, 0x10);
|
||||||
|
Array.Copy(Keyblobs[i], MasterKeks[i], 0x10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!haveMasterKeySource) continue;
|
private void Derive620MasterKeks()
|
||||||
|
{
|
||||||
|
for (int i = UsedKeyblobCount; i < 0x20; i++)
|
||||||
|
{
|
||||||
|
if (TsecRootKeys[i - UsedKeyblobCount].IsEmpty() || MasterKekSources[i].IsEmpty()) continue;
|
||||||
|
|
||||||
Array.Copy(Keyblobs[i], masterKek, 0x10);
|
Crypto.DecryptEcb(TsecRootKeys[i - UsedKeyblobCount], MasterKekSources[i], MasterKeks[i], 0x10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Crypto.DecryptEcb(masterKek, MasterKeySource, MasterKeys[i], 0x10);
|
private void DeriveMasterKeys()
|
||||||
|
{
|
||||||
|
if (MasterKeySource.IsEmpty()) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x20; i++)
|
||||||
|
{
|
||||||
|
if (MasterKeks[i].IsEmpty()) continue;
|
||||||
|
|
||||||
|
Crypto.DecryptEcb(MasterKeks[i], MasterKeySource, MasterKeys[i], 0x10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,9 +268,7 @@ namespace LibHac
|
||||||
|
|
||||||
Crypto.DecryptEcb(kek, BisKeySource[1], BisKeys[1], 0x20);
|
Crypto.DecryptEcb(kek, BisKeySource[1], BisKeys[1], 0x20);
|
||||||
Crypto.DecryptEcb(kek, BisKeySource[2], BisKeys[2], 0x20);
|
Crypto.DecryptEcb(kek, BisKeySource[2], BisKeys[2], 0x20);
|
||||||
|
Crypto.DecryptEcb(kek, BisKeySource[3], BisKeys[3], 0x20);
|
||||||
// BIS keys 2 and 3 are the same
|
|
||||||
Array.Copy(BisKeys[2], BisKeys[3], 0x20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DerivePerFirmwareKeys()
|
private void DerivePerFirmwareKeys()
|
||||||
|
@ -252,7 +276,7 @@ namespace LibHac
|
||||||
bool haveKakSource0 = !KeyAreaKeyApplicationSource.IsEmpty();
|
bool haveKakSource0 = !KeyAreaKeyApplicationSource.IsEmpty();
|
||||||
bool haveKakSource1 = !KeyAreaKeyOceanSource.IsEmpty();
|
bool haveKakSource1 = !KeyAreaKeyOceanSource.IsEmpty();
|
||||||
bool haveKakSource2 = !KeyAreaKeySystemSource.IsEmpty();
|
bool haveKakSource2 = !KeyAreaKeySystemSource.IsEmpty();
|
||||||
bool haveTitleKekSource = !TitlekekSource.IsEmpty();
|
bool haveTitleKekSource = !TitleKekSource.IsEmpty();
|
||||||
bool havePackage2KeySource = !Package2KeySource.IsEmpty();
|
bool havePackage2KeySource = !Package2KeySource.IsEmpty();
|
||||||
|
|
||||||
for (int i = 0; i < 0x20; i++)
|
for (int i = 0; i < 0x20; i++)
|
||||||
|
@ -282,7 +306,7 @@ namespace LibHac
|
||||||
|
|
||||||
if (haveTitleKekSource)
|
if (haveTitleKekSource)
|
||||||
{
|
{
|
||||||
Crypto.DecryptEcb(MasterKeys[i], TitlekekSource, Titlekeks[i], 0x10);
|
Crypto.DecryptEcb(MasterKeys[i], TitleKekSource, TitleKeks[i], 0x10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (havePackage2KeySource)
|
if (havePackage2KeySource)
|
||||||
|
@ -322,7 +346,7 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static readonly string[] KakNames = {"application", "ocean", "system"};
|
internal static readonly string[] KakNames = { "application", "ocean", "system" };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExternalKeys
|
public static class ExternalKeys
|
||||||
|
@ -343,14 +367,19 @@ namespace LibHac
|
||||||
AllKeyDict = uniqueKeys.Concat(commonKeys).ToDictionary(k => k.Name, k => k);
|
AllKeyDict = uniqueKeys.Concat(commonKeys).ToDictionary(k => k.Name, k => k);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Keyset ReadKeyFile(string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null)
|
public static void ReadKeyFile(Keyset keyset, string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null)
|
||||||
{
|
{
|
||||||
var keyset = new Keyset();
|
|
||||||
|
|
||||||
if (filename != null) ReadMainKeys(keyset, filename, AllKeyDict, logger);
|
if (filename != null) ReadMainKeys(keyset, filename, AllKeyDict, logger);
|
||||||
if (consoleKeysFilename != null) ReadMainKeys(keyset, consoleKeysFilename, AllKeyDict, logger);
|
if (consoleKeysFilename != null) ReadMainKeys(keyset, consoleKeysFilename, AllKeyDict, logger);
|
||||||
if (titleKeysFilename != null) ReadTitleKeys(keyset, titleKeysFilename, logger);
|
if (titleKeysFilename != null) ReadTitleKeys(keyset, titleKeysFilename, logger);
|
||||||
keyset.DeriveKeys(logger);
|
keyset.DeriveKeys(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Keyset ReadKeyFile(string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null)
|
||||||
|
{
|
||||||
|
var keyset = new Keyset();
|
||||||
|
|
||||||
|
ReadKeyFile(keyset, filename, titleKeysFilename, consoleKeysFilename, logger);
|
||||||
|
|
||||||
return keyset;
|
return keyset;
|
||||||
}
|
}
|
||||||
|
@ -459,12 +488,19 @@ namespace LibHac
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
int maxNameLength = dict.Values.Max(x => x.Name.Length);
|
int maxNameLength = dict.Values.Max(x => x.Name.Length);
|
||||||
|
int currentGroup = 0;
|
||||||
|
|
||||||
foreach (KeyValue keySlot in dict.Values.OrderBy(x => x.Name))
|
foreach (KeyValue keySlot in dict.Values.Where(x => x.Group >= 0).OrderBy(x => x.Group).ThenBy(x => x.Name))
|
||||||
{
|
{
|
||||||
byte[] key = keySlot.GetKey(keyset);
|
byte[] key = keySlot.GetKey(keyset);
|
||||||
if (key.IsEmpty()) continue;
|
if (key.IsEmpty()) continue;
|
||||||
|
|
||||||
|
if (keySlot.Group > currentGroup)
|
||||||
|
{
|
||||||
|
if (currentGroup > 0) sb.AppendLine();
|
||||||
|
currentGroup = keySlot.Group;
|
||||||
|
}
|
||||||
|
|
||||||
string line = $"{keySlot.Name.PadRight(maxNameLength)} = {key.ToHexString()}";
|
string line = $"{keySlot.Name.PadRight(maxNameLength)} = {key.ToHexString()}";
|
||||||
sb.AppendLine(line);
|
sb.AppendLine(line);
|
||||||
}
|
}
|
||||||
|
@ -491,7 +527,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
foreach (KeyValuePair<byte[], byte[]> kv in keyset.TitleKeys)
|
foreach (KeyValuePair<byte[], byte[]> kv in keyset.TitleKeys.OrderBy(x => x.Key.ToHexString()))
|
||||||
{
|
{
|
||||||
string line = $"{kv.Key.ToHexString()} = {kv.Value.ToHexString()}";
|
string line = $"{kv.Key.ToHexString()} = {kv.Value.ToHexString()}";
|
||||||
sb.AppendLine(line);
|
sb.AppendLine(line);
|
||||||
|
@ -504,49 +540,61 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
var keys = new List<KeyValue>
|
var keys = new List<KeyValue>
|
||||||
{
|
{
|
||||||
new KeyValue("aes_kek_generation_source", 0x10, set => set.AesKekGenerationSource),
|
new KeyValue("keyblob_mac_key_source", 0x10, 0, set => set.KeyblobMacKeySource),
|
||||||
new KeyValue("aes_key_generation_source", 0x10, set => set.AesKeyGenerationSource),
|
|
||||||
new KeyValue("key_area_key_application_source", 0x10, set => set.KeyAreaKeyApplicationSource),
|
new KeyValue("master_key_source", 0x10, 60, set => set.MasterKeySource),
|
||||||
new KeyValue("key_area_key_ocean_source", 0x10, set => set.KeyAreaKeyOceanSource),
|
new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource),
|
||||||
new KeyValue("key_area_key_system_source", 0x10, set => set.KeyAreaKeySystemSource),
|
|
||||||
new KeyValue("titlekek_source", 0x10, set => set.TitlekekSource),
|
new KeyValue("aes_kek_generation_source", 0x10, 70, set => set.AesKekGenerationSource),
|
||||||
new KeyValue("header_kek_source", 0x10, set => set.HeaderKekSource),
|
new KeyValue("aes_key_generation_source", 0x10, 70, set => set.AesKeyGenerationSource),
|
||||||
new KeyValue("header_key_source", 0x20, set => set.HeaderKeySource),
|
|
||||||
new KeyValue("header_key", 0x20, set => set.HeaderKey),
|
new KeyValue("bis_kek_source", 0x10, 80, set => set.BisKekSource),
|
||||||
new KeyValue("xci_header_key", 0x10, set => set.XciHeaderKey),
|
|
||||||
new KeyValue("package2_key_source", 0x10, set => set.Package2KeySource),
|
new KeyValue("retail_specific_aes_key_source", 0x10, 90, set => set.RetailSpecificAesKeySource),
|
||||||
new KeyValue("sd_card_kek_source", 0x10, set => set.SdCardKekSource),
|
new KeyValue("per_console_key_source", 0x10, 90, set => set.PerConsoleKeySource),
|
||||||
new KeyValue("sd_card_nca_key_source", 0x20, set => set.SdCardKeySources[1]),
|
|
||||||
new KeyValue("sd_card_save_key_source", 0x20, set => set.SdCardKeySources[0]),
|
new KeyValue("header_kek_source", 0x10, 100, set => set.HeaderKekSource),
|
||||||
new KeyValue("master_key_source", 0x10, set => set.MasterKeySource),
|
new KeyValue("header_key_source", 0x20, 100, set => set.HeaderKeySource),
|
||||||
new KeyValue("keyblob_mac_key_source", 0x10, set => set.KeyblobMacKeySource),
|
new KeyValue("key_area_key_application_source", 0x10, 100, set => set.KeyAreaKeyApplicationSource),
|
||||||
new KeyValue("eticket_rsa_kek", 0x10, set => set.EticketRsaKek),
|
new KeyValue("key_area_key_ocean_source", 0x10, 100, set => set.KeyAreaKeyOceanSource),
|
||||||
new KeyValue("retail_specific_aes_key_source", 0x10, set => set.RetailSpecificAesKeySource),
|
new KeyValue("key_area_key_system_source", 0x10, 100, set => set.KeyAreaKeySystemSource),
|
||||||
new KeyValue("per_console_key_source", 0x10, set => set.PerConsoleKeySource),
|
new KeyValue("titlekek_source", 0x10, 100, set => set.TitleKekSource),
|
||||||
new KeyValue("bis_kek_source", 0x10, set => set.BisKekSource),
|
|
||||||
new KeyValue("save_mac_kek_source", 0x10, set => set.SaveMacKekSource),
|
new KeyValue("save_mac_kek_source", 0x10, 110, set => set.SaveMacKekSource),
|
||||||
new KeyValue("save_mac_key_source", 0x10, set => set.SaveMacKeySource),
|
new KeyValue("save_mac_key_source", 0x10, 110, set => set.SaveMacKeySource),
|
||||||
new KeyValue("save_mac_key", 0x10, set => set.SaveMacKey)
|
new KeyValue("sd_card_kek_source", 0x10, 110, set => set.SdCardKekSource),
|
||||||
|
new KeyValue("sd_card_nca_key_source", 0x20, 110, set => set.SdCardKeySources[1]),
|
||||||
|
new KeyValue("sd_card_save_key_source", 0x20, 110, set => set.SdCardKeySources[0]),
|
||||||
|
|
||||||
|
new KeyValue("eticket_rsa_kek", 0x10, 120, set => set.EticketRsaKek),
|
||||||
|
new KeyValue("ssl_rsa_kek", 0x10, 120, set => set.SslRsaKek),
|
||||||
|
new KeyValue("xci_header_key", 0x10, 130, set => set.XciHeaderKey),
|
||||||
|
|
||||||
|
new KeyValue("header_key", 0x20, 220, set => set.HeaderKey)
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int slot = 0; slot < 0x20; slot++)
|
for (int slot = 0; slot < 0x20; slot++)
|
||||||
{
|
{
|
||||||
int i = slot;
|
int i = slot;
|
||||||
keys.Add(new KeyValue($"keyblob_key_source_{i:x2}", 0x10, set => set.KeyblobKeySources[i]));
|
keys.Add(new KeyValue($"keyblob_key_source_{i:x2}", 0x10, 0, set => set.KeyblobKeySources[i]));
|
||||||
keys.Add(new KeyValue($"keyblob_{i:x2}", 0x90, set => set.Keyblobs[i]));
|
keys.Add(new KeyValue($"keyblob_{i:x2}", 0x90, 10, set => set.Keyblobs[i]));
|
||||||
keys.Add(new KeyValue($"master_key_{i:x2}", 0x10, set => set.MasterKeys[i]));
|
keys.Add(new KeyValue($"tsec_root_key_{i:x2}", 0x10, 20, set => set.TsecRootKeys[i]));
|
||||||
keys.Add(new KeyValue($"package1_key_{i:x2}", 0x10, set => set.Package1Keys[i]));
|
keys.Add(new KeyValue($"master_kek_source_{i:x2}", 0x10, 30, set => set.MasterKekSources[i]));
|
||||||
keys.Add(new KeyValue($"package2_key_{i:x2}", 0x10, set => set.Package2Keys[i]));
|
keys.Add(new KeyValue($"master_kek_{i:x2}", 0x10, 40, set => set.MasterKeks[i]));
|
||||||
keys.Add(new KeyValue($"titlekek_{i:x2}", 0x10, set => set.Titlekeks[i]));
|
keys.Add(new KeyValue($"package1_key_{i:x2}", 0x10, 50, set => set.Package1Keys[i]));
|
||||||
keys.Add(new KeyValue($"key_area_key_application_{i:x2}", 0x10, set => set.KeyAreaKeys[i][0]));
|
|
||||||
keys.Add(new KeyValue($"key_area_key_ocean_{i:x2}", 0x10, set => set.KeyAreaKeys[i][1]));
|
keys.Add(new KeyValue($"master_key_{i:x2}", 0x10, 200, set => set.MasterKeys[i]));
|
||||||
keys.Add(new KeyValue($"key_area_key_system_{i:x2}", 0x10, set => set.KeyAreaKeys[i][2]));
|
keys.Add(new KeyValue($"package2_key_{i:x2}", 0x10, 210, set => set.Package2Keys[i]));
|
||||||
|
keys.Add(new KeyValue($"titlekek_{i:x2}", 0x10, 230, set => set.TitleKeks[i]));
|
||||||
|
keys.Add(new KeyValue($"key_area_key_application_{i:x2}", 0x10, 240, set => set.KeyAreaKeys[i][0]));
|
||||||
|
keys.Add(new KeyValue($"key_area_key_ocean_{i:x2}", 0x10, 250, set => set.KeyAreaKeys[i][1]));
|
||||||
|
keys.Add(new KeyValue($"key_area_key_system_{i:x2}", 0x10, 260, set => set.KeyAreaKeys[i][2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int slot = 0; slot < 3; slot++)
|
for (int slot = 0; slot < 4; slot++)
|
||||||
{
|
{
|
||||||
int i = slot;
|
int i = slot;
|
||||||
keys.Add(new KeyValue($"bis_key_source_{i:x2}", 0x20, set => set.BisKeySource[i]));
|
keys.Add(new KeyValue($"bis_key_source_{i:x2}", 0x20, 80, set => set.BisKeySource[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
|
@ -556,24 +604,26 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
var keys = new List<KeyValue>
|
var keys = new List<KeyValue>
|
||||||
{
|
{
|
||||||
new KeyValue("secure_boot_key", 0x10, set => set.SecureBootKey),
|
new KeyValue("secure_boot_key", 0x10, 0, set => set.SecureBootKey),
|
||||||
new KeyValue("tsec_key", 0x10, set => set.TsecKey),
|
new KeyValue("tsec_key", 0x10, 0, set => set.TsecKey),
|
||||||
new KeyValue("device_key", 0x10, set => set.DeviceKey),
|
new KeyValue("sd_seed", 0x10, 10, set => set.SdSeed),
|
||||||
new KeyValue("sd_seed", 0x10, set => set.SdSeed)
|
|
||||||
|
new KeyValue("device_key", 0x10, 40, set => set.DeviceKey),
|
||||||
|
new KeyValue("save_mac_key", 0x10, 60, set => set.SaveMacKey)
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int slot = 0; slot < 0x20; slot++)
|
for (int slot = 0; slot < 0x20; slot++)
|
||||||
{
|
{
|
||||||
int i = slot;
|
int i = slot;
|
||||||
keys.Add(new KeyValue($"keyblob_key_{i:x2}", 0x10, set => set.KeyblobKeys[i]));
|
keys.Add(new KeyValue($"keyblob_mac_key_{i:x2}", 0x10, 20, set => set.KeyblobMacKeys[i]));
|
||||||
keys.Add(new KeyValue($"keyblob_mac_key_{i:x2}", 0x10, set => set.KeyblobMacKeys[i]));
|
keys.Add(new KeyValue($"keyblob_key_{i:x2}", 0x10, 30, set => set.KeyblobKeys[i]));
|
||||||
keys.Add(new KeyValue($"encrypted_keyblob_{i:x2}", 0xB0, set => set.EncryptedKeyblobs[i]));
|
keys.Add(new KeyValue($"encrypted_keyblob_{i:x2}", 0xB0, 100, set => set.EncryptedKeyblobs[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int slot = 0; slot < 4; slot++)
|
for (int slot = 0; slot < 4; slot++)
|
||||||
{
|
{
|
||||||
int i = slot;
|
int i = slot;
|
||||||
keys.Add(new KeyValue($"bis_key_{i:x2}", 0x20, set => set.BisKeys[i]));
|
keys.Add(new KeyValue($"bis_key_{i:x2}", 0x20, 50, set => set.BisKeys[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
|
@ -583,12 +633,14 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public readonly int Size;
|
public readonly int Size;
|
||||||
|
public readonly int Group;
|
||||||
public readonly Func<Keyset, byte[]> GetKey;
|
public readonly Func<Keyset, byte[]> GetKey;
|
||||||
|
|
||||||
public KeyValue(string name, int size, Func<Keyset, byte[]> retrieveFunc)
|
public KeyValue(string name, int size, int group, Func<Keyset, byte[]> retrieveFunc)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Size = size;
|
Size = size;
|
||||||
|
Group = group;
|
||||||
GetKey = retrieveFunc;
|
GetKey = retrieveFunc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<RepositoryUrl>https://github.com/Thealexbarney/LibHac</RepositoryUrl>
|
<RepositoryUrl>https://github.com/Thealexbarney/LibHac</RepositoryUrl>
|
||||||
|
|
||||||
<VersionPrefix>0.2.0</VersionPrefix>
|
<VersionPrefix>0.3.1</VersionPrefix>
|
||||||
<PathMap>$(MSBuildProjectDirectory)=C:/LibHac/</PathMap>
|
<PathMap>$(MSBuildProjectDirectory)=C:/LibHac/</PathMap>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<IncludeSource>true</IncludeSource>
|
<IncludeSource>true</IncludeSource>
|
||||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
<NoWarn>$(NoWarn);1591;NU5105</NoWarn>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -34,8 +35,12 @@
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||||
<PackageReference Include="System.Memory" Version="4.5.1" />
|
<PackageReference Include="System.Memory" Version="4.5.2" />
|
||||||
<PackageReference Include="System.Buffers" Version="4.5.0" />
|
<PackageReference Include="System.Buffers" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||||
|
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -48,13 +48,13 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
else if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
|
else if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
|
||||||
{
|
{
|
||||||
if (keyset.Titlekeks[CryptoType].IsEmpty())
|
if (keyset.TitleKeks[CryptoType].IsEmpty())
|
||||||
{
|
{
|
||||||
MissingKeyName = $"titlekek_{CryptoType:x2}";
|
MissingKeyName = $"titlekek_{CryptoType:x2}";
|
||||||
}
|
}
|
||||||
|
|
||||||
TitleKey = titleKey;
|
TitleKey = titleKey;
|
||||||
Crypto.DecryptEcb(keyset.Titlekeks[CryptoType], titleKey, TitleKeyDec, 0x10);
|
Crypto.DecryptEcb(keyset.TitleKeks[CryptoType], titleKey, TitleKeyDec, 0x10);
|
||||||
DecryptedKeys[2] = TitleKeyDec;
|
DecryptedKeys[2] = TitleKeyDec;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -268,13 +268,13 @@ namespace LibHac
|
||||||
Control,
|
Control,
|
||||||
Manual,
|
Manual,
|
||||||
Data,
|
Data,
|
||||||
AocData
|
PublicData
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DistributionType
|
public enum DistributionType
|
||||||
{
|
{
|
||||||
Download,
|
Download,
|
||||||
Gamecard
|
GameCard
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NcaEncryptionType
|
public enum NcaEncryptionType
|
||||||
|
|
|
@ -424,6 +424,8 @@ namespace LibHac
|
||||||
case 3: return "4.0.0-4.1.0";
|
case 3: return "4.0.0-4.1.0";
|
||||||
case 4: return "5.0.0-5.1.0";
|
case 4: return "5.0.0-5.1.0";
|
||||||
case 5: return "6.0.0-6.0.1";
|
case 5: return "6.0.0-6.0.1";
|
||||||
|
case 6: return "6.2.0";
|
||||||
|
case 7: return "7.0.0";
|
||||||
default: return "Unknown";
|
default: return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>0.2.0</VersionPrefix>
|
<VersionPrefix>0.3.1</VersionPrefix>
|
||||||
<PathMap>$(MSBuildProjectDirectory)=C:/hactoolnet/</PathMap>
|
<PathMap>$(MSBuildProjectDirectory)=C:/hactoolnet/</PathMap>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
204
tests/LibHac.Tests/RomFsTests.cs
Normal file
204
tests/LibHac.Tests/RomFsTests.cs
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
using System;
|
||||||
|
using LibHac.IO.RomFs;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace LibHac.Tests
|
||||||
|
{
|
||||||
|
public class RomFsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SimpleAddAndRead()
|
||||||
|
{
|
||||||
|
const string path = "/a/b";
|
||||||
|
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
var item = new RomFileInfo { Length = 1, Offset = 1 };
|
||||||
|
|
||||||
|
table.AddFile(path, ref item);
|
||||||
|
bool success = table.TryOpenFile(path, out RomFileInfo readItem);
|
||||||
|
|
||||||
|
Assert.True(success, "Table read failed");
|
||||||
|
Assert.Equal(item, readItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UpdateExistingFile()
|
||||||
|
{
|
||||||
|
const string path = "/a/b";
|
||||||
|
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
var originalItem = new RomFileInfo { Length = 1, Offset = 1 };
|
||||||
|
var newItem = new RomFileInfo { Length = 1, Offset = 1 };
|
||||||
|
|
||||||
|
table.AddFile(path, ref originalItem);
|
||||||
|
table.AddFile(path, ref newItem);
|
||||||
|
|
||||||
|
bool success = table.TryOpenFile(path, out RomFileInfo readItem);
|
||||||
|
|
||||||
|
Assert.True(success, "Table read failed");
|
||||||
|
Assert.Equal(newItem, readItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddingDirectory()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
var expectedPosition = new FindPosition { NextDirectory = -1, NextFile = -1 };
|
||||||
|
|
||||||
|
table.AddDirectory("/dir");
|
||||||
|
bool success = table.TryOpenDirectory("/dir", out FindPosition position);
|
||||||
|
|
||||||
|
Assert.True(success, "Opening directory failed");
|
||||||
|
Assert.Equal(expectedPosition, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddingEmptyPathThrows()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
var item = new RomFileInfo();
|
||||||
|
|
||||||
|
Assert.Throws<ArgumentException>(() => table.AddFile("", ref item));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OpeningNonexistentFileFails()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
|
||||||
|
bool success = table.TryOpenFile("/foo", out _);
|
||||||
|
Assert.False(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OpeningNonexistentDirectoryFails()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
|
||||||
|
bool success = table.TryOpenDirectory("/foo", out _);
|
||||||
|
Assert.False(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OpeningFileAsDirectoryFails()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
var fileInfo = new RomFileInfo();
|
||||||
|
table.AddFile("/file", ref fileInfo);
|
||||||
|
|
||||||
|
bool success = table.TryOpenDirectory("/file", out _);
|
||||||
|
Assert.False(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OpeningDirectoryAsFileFails()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
table.AddDirectory("/dir");
|
||||||
|
|
||||||
|
bool success = table.TryOpenFile("/dir", out _);
|
||||||
|
Assert.False(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ChildFileIteration()
|
||||||
|
{
|
||||||
|
const int fileCount = 10;
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
|
||||||
|
for (int i = 0; i < fileCount; i++)
|
||||||
|
{
|
||||||
|
var item = new RomFileInfo { Length = i, Offset = i };
|
||||||
|
table.AddFile($"/a/{i}", ref item);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool openDirSuccess = table.TryOpenDirectory("/a", out FindPosition position);
|
||||||
|
Assert.True(openDirSuccess, "Error opening directory");
|
||||||
|
|
||||||
|
for (int i = 0; i < fileCount; i++)
|
||||||
|
{
|
||||||
|
var expectedItem = new RomFileInfo { Length = i, Offset = i };
|
||||||
|
string expectedName = i.ToString();
|
||||||
|
|
||||||
|
bool success = table.FindNextFile(ref position, out RomFileInfo actualItem, out string actualName);
|
||||||
|
|
||||||
|
Assert.True(success, $"Failed reading file {i}");
|
||||||
|
Assert.Equal(expectedItem, actualItem);
|
||||||
|
Assert.Equal(expectedName, actualName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool endOfFilesSuccess = table.FindNextFile(ref position, out _, out _);
|
||||||
|
Assert.False(endOfFilesSuccess, "Table returned more files than it should");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ChildFileIterationPeek()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
|
||||||
|
var itemA = new RomFileInfo { Length = 1, Offset = 1 };
|
||||||
|
var itemB = new RomFileInfo { Length = 2, Offset = 2 };
|
||||||
|
|
||||||
|
table.AddFile("/a/a", ref itemA);
|
||||||
|
table.AddFile("/a/b", ref itemB);
|
||||||
|
|
||||||
|
table.TryOpenDirectory("/a", out FindPosition position);
|
||||||
|
|
||||||
|
table.TryOpenFile(position.NextFile, out RomFileInfo peekItemA);
|
||||||
|
Assert.Equal(itemA, peekItemA);
|
||||||
|
|
||||||
|
table.FindNextFile(ref position, out RomFileInfo iterateItemA, out _);
|
||||||
|
Assert.Equal(itemA, iterateItemA);
|
||||||
|
|
||||||
|
table.TryOpenFile(position.NextFile, out RomFileInfo peekItemB);
|
||||||
|
Assert.Equal(itemB, peekItemB);
|
||||||
|
|
||||||
|
table.FindNextFile(ref position, out RomFileInfo iterateItemB, out _);
|
||||||
|
Assert.Equal(itemB, iterateItemB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddingCousinFiles()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
|
||||||
|
var itemB1 = new RomFileInfo { Length = 1, Offset = 1 };
|
||||||
|
var itemB2 = new RomFileInfo { Length = 2, Offset = 2 };
|
||||||
|
var itemB3 = new RomFileInfo { Length = 3, Offset = 3 };
|
||||||
|
|
||||||
|
table.AddFile("/a/b1/c", ref itemB1);
|
||||||
|
table.AddFile("/a/b2/c", ref itemB2);
|
||||||
|
table.AddFile("/a/b3/c", ref itemB3);
|
||||||
|
|
||||||
|
table.TryOpenFile("/a/b1/c", out RomFileInfo actualItemB1);
|
||||||
|
table.TryOpenFile("/a/b2/c", out RomFileInfo actualItemB2);
|
||||||
|
table.TryOpenFile("/a/b3/c", out RomFileInfo actualItemB3);
|
||||||
|
|
||||||
|
Assert.Equal(itemB1, actualItemB1);
|
||||||
|
Assert.Equal(itemB2, actualItemB2);
|
||||||
|
Assert.Equal(itemB3, actualItemB3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddingSiblingFiles()
|
||||||
|
{
|
||||||
|
var table = new HierarchicalRomFileTable();
|
||||||
|
|
||||||
|
var itemC1 = new RomFileInfo { Length = 1, Offset = 1 };
|
||||||
|
var itemC2 = new RomFileInfo { Length = 2, Offset = 2 };
|
||||||
|
var itemC3 = new RomFileInfo { Length = 3, Offset = 3 };
|
||||||
|
|
||||||
|
table.AddFile("/a/b/c1", ref itemC1);
|
||||||
|
table.AddFile("/a/b/c2", ref itemC2);
|
||||||
|
table.AddFile("/a/b/c3", ref itemC3);
|
||||||
|
|
||||||
|
table.TryOpenFile("/a/b/c1", out RomFileInfo actualItemC1);
|
||||||
|
table.TryOpenFile("/a/b/c2", out RomFileInfo actualItemC2);
|
||||||
|
table.TryOpenFile("/a/b/c3", out RomFileInfo actualItemC3);
|
||||||
|
|
||||||
|
Assert.Equal(itemC1, actualItemC1);
|
||||||
|
Assert.Equal(itemC2, actualItemC2);
|
||||||
|
Assert.Equal(itemC3, actualItemC3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue