Target netstandard2.1 and netcoreapp3.0 (#99)

.NET Core 2.1 introduced some runtime changes to support Span<T> and ByReference. Along with this comes the ability to do things like reinterpret memory as a different type. In .NET Framework the garbage collector couldn't track these references. These features proved useful enough that support for .NET Framework was dropped.

* Target netstandard2.1 and netcoreapp3.0
* Build: Zip native builds. Put version in zip filename
* Always build native exe on AppVeyor
This commit is contained in:
Alex Barney 2019-11-29 13:11:04 -06:00 committed by GitHub
parent f304f664f4
commit 57586d75fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 170 additions and 827 deletions

View file

@ -1,6 +1,6 @@
mode: ContinuousDeployment
increment: Patch
next-version: 0.7.0
next-version: 0.8.0
branches:
master:
tag: alpha

View file

@ -4,5 +4,5 @@ environment:
myget_api_key:
secure: 0xJoYAtR6psXCRvk1qm5czDObkeRjHKPjfe5gIExXVFPwA0VVODYv/hBZYUtz2F3
build_script:
- ps: .\build.ps1
- ps: .\build.ps1 appveyorbuild
test: off

View file

@ -4,18 +4,17 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using ICSharpCode.SharpZipLib.Zip;
using ILRepacking;
using Nuke.Common;
using Nuke.Common.CI.AppVeyor;
using Nuke.Common.Git;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.GitVersion;
using Nuke.Common.Tools.SignTool;
@ -27,7 +26,7 @@ namespace LibHacBuild
{
partial class Build : NukeBuild
{
public static int Main() => Execute<Build>(x => x.Results);
public static int Main() => Execute<Build>(x => x.Standard);
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
public readonly string Configuration = IsLocalBuild ? "Debug" : "Release";
@ -45,23 +44,21 @@ namespace LibHacBuild
AbsolutePath SignedArtifactsDirectory => ArtifactsDirectory / "signed";
AbsolutePath TempDirectory => RootDirectory / ".tmp";
AbsolutePath CliCoreDir => TempDirectory / "hactoolnet_netcoreapp3.0";
AbsolutePath CliFrameworkDir => TempDirectory / "hactoolnet_net46";
AbsolutePath CliNativeDir => TempDirectory / "hactoolnet_native";
AbsolutePath CliFrameworkZip => ArtifactsDirectory / "hactoolnet.zip";
AbsolutePath CliCoreZip => ArtifactsDirectory / "hactoolnet_netcore.zip";
AbsolutePath CliNativeDir => TempDirectory / $"hactoolnet_{HostOsName}";
AbsolutePath CliNativeExe => CliNativeDir / $"hactoolnet_native{NativeProgramExtension}";
AbsolutePath CliCoreZip => ArtifactsDirectory / $"hactoolnet-{VersionString}-netcore.zip";
AbsolutePath CliNativeZip => ArtifactsDirectory / $"hactoolnet-{VersionString}-{HostOsName}.zip";
AbsolutePath NugetConfig => RootDirectory / "nuget.config";
AbsolutePath CliMergedExe => ArtifactsDirectory / "hactoolnet.exe";
AbsolutePath CliNativeExe => ArtifactsDirectory / NativeProgramFilename;
Project LibHacProject => _solution.GetProject("LibHac").NotNull();
Project LibHacTestProject => _solution.GetProject("LibHac.Tests").NotNull();
Project HactoolnetProject => _solution.GetProject("hactoolnet").NotNull();
private string NativeRuntime { get; set; }
private string NativeProgramFilename { get; set; }
private string HostOsName { get; set; }
private string NativeProgramExtension { get; set; }
string AppVeyorVersion { get; set; }
string VersionString { get; set; }
Dictionary<string, object> VersionProps { get; set; } = new Dictionary<string, object>();
private const string DotNetFeedSource = "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json";
@ -69,20 +66,23 @@ namespace LibHacBuild
public Build()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (EnvironmentInfo.IsWin)
{
NativeRuntime = "win-x64";
NativeProgramFilename = "hactoolnet_native.exe";
NativeProgramExtension = ".exe";
HostOsName = "win";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
else if (EnvironmentInfo.IsLinux)
{
NativeRuntime = "linux-x64";
NativeProgramFilename = "hactoolnet_native";
NativeProgramExtension = "";
HostOsName = "linux";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
else if (EnvironmentInfo.IsOsx)
{
NativeRuntime = "osx-x64";
NativeProgramFilename = "hactoolnet_native";
NativeProgramExtension = "";
HostOsName = "macos";
}
}
@ -92,10 +92,10 @@ namespace LibHacBuild
.OnlyWhenStatic(() => _gitRepository != null)
.Executes(() =>
{
AppVeyorVersion = $"{_gitVersion.AssemblySemVer}";
VersionString = $"{_gitVersion.MajorMinorPatch}";
if (!string.IsNullOrWhiteSpace(_gitVersion.PreReleaseTag))
{
AppVeyorVersion += $"-{_gitVersion.PreReleaseTag}+{_gitVersion.Sha.Substring(0, 8)}";
VersionString += $"-{_gitVersion.PreReleaseTag}+{_gitVersion.Sha.Substring(0, 8)}";
}
string suffix = _gitVersion.PreReleaseTag;
@ -116,11 +116,11 @@ namespace LibHacBuild
["VersionSuffix"] = suffix
};
Console.WriteLine($"Building version {AppVeyorVersion}");
Logger.Normal($"Building version {VersionString}");
if (Host == HostType.AppVeyor)
{
SetAppVeyorVersion(AppVeyorVersion);
SetAppVeyorVersion(VersionString);
}
});
@ -137,7 +137,6 @@ namespace LibHacBuild
EnsureCleanDirectory(ArtifactsDirectory);
EnsureCleanDirectory(CliCoreDir);
EnsureCleanDirectory(CliFrameworkDir);
EnsureCleanDirectory(CliNativeDir);
});
@ -175,13 +174,6 @@ namespace LibHacBuild
.SetNoBuild(true)
.SetProperties(VersionProps));
DotNetPublish(s => publishSettings
.SetProject(HactoolnetProject)
.SetFramework("net46")
.SetOutput(CliFrameworkDir)
.SetNoBuild(true)
.SetProperties(VersionProps));
// Hack around OS newline differences
if (EnvironmentInfo.IsUnix)
{
@ -220,36 +212,6 @@ namespace LibHacBuild
}
});
Target Merge => _ => _
.DependsOn(Compile)
// Merging on Linux blocked by https://github.com/gluck/il-repack/issues/230
.OnlyWhenStatic(() => !EnvironmentInfo.IsUnix)
.Executes(() =>
{
string[] libraries = Directory.GetFiles(CliFrameworkDir, "*.dll");
var cliList = new List<string> { CliFrameworkDir / "hactoolnet.exe" };
cliList.AddRange(libraries);
var cliOptions = new RepackOptions
{
OutputFile = CliMergedExe,
InputAssemblies = cliList.ToArray(),
SearchDirectories = new[] { "." }
};
new ILRepack(cliOptions).Repack();
foreach (AbsolutePath file in ArtifactsDirectory.GlobFiles("*.exe.config"))
{
File.Delete(file);
}
if (Host == HostType.AppVeyor)
{
PushArtifact(CliMergedExe);
}
});
Target Test => _ => _
.DependsOn(Compile)
.Executes(() =>
@ -266,32 +228,24 @@ namespace LibHacBuild
Target Zip => _ => _
.DependsOn(Pack)
.After(Native)
.Executes(() =>
{
string[] namesFx = Directory.EnumerateFiles(CliFrameworkDir, "*.exe")
.Concat(Directory.EnumerateFiles(CliFrameworkDir, "*.dll"))
.ToArray();
string[] namesCore = Directory.EnumerateFiles(CliCoreDir, "*.json")
.Concat(Directory.EnumerateFiles(CliCoreDir, "*.dll"))
.ToArray();
ZipFiles(CliFrameworkZip, namesFx);
Console.WriteLine($"Created {CliFrameworkZip}");
ZipFiles(CliCoreZip, namesCore);
Console.WriteLine($"Created {CliCoreZip}");
Logger.Normal($"Created {CliCoreZip}");
if (Host == HostType.AppVeyor)
{
PushArtifact(CliFrameworkZip);
PushArtifact(CliCoreZip);
PushArtifact(CliMergedExe);
}
});
Target Publish => _ => _
.DependsOn(Test)
.DependsOn(Test, Pack)
.OnlyWhenStatic(() => AppVeyor.Instance != null && AppVeyor.Instance.PullRequestTitle == null)
.Executes(() =>
{
@ -309,101 +263,115 @@ namespace LibHacBuild
DotNetNuGetPush(settings.SetTargetPath(snupkgFile));
});
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
Target Native => _ => _
.DependsOn(SetVersion)
.OnlyWhenStatic(() => AppVeyor.Instance != null && IsMasterBranch)
.Executes(() =>
{
AbsolutePath nativeProject = HactoolnetProject.Path.Parent / "hactoolnet_native.csproj";
try
{
File.Copy(HactoolnetProject, nativeProject, true);
DotNet("new nuget --force");
XDocument doc = XDocument.Load(NugetConfig);
doc.Element("configuration").Element("packageSources").Add(new XElement("add",
new XAttribute("key", "myget"), new XAttribute("value", DotNetFeedSource)));
doc.Save(NugetConfig);
DotNet($"add {nativeProject} package Microsoft.DotNet.ILCompiler --version 1.0.0-alpha-*");
DotNetPublishSettings publishSettings = new DotNetPublishSettings()
.SetConfiguration(Configuration)
.SetProject(nativeProject)
.SetFramework("netcoreapp3.0")
.SetRuntime(NativeRuntime)
.SetOutput(CliNativeDir)
.SetProperties(VersionProps);
if (!Untrimmed)
{
publishSettings = publishSettings
.AddProperty("RootAllApplicationAssemblies", false)
.AddProperty("IlcGenerateCompleteTypeMetadata", false)
.AddProperty("IlcGenerateStackTraceData", false)
.AddProperty("IlcFoldIdenticalMethodBodies", true)
;
}
DotNetPublish(publishSettings);
AbsolutePath tempExe = CliNativeDir / NativeProgramFilename;
File.Copy(tempExe, CliNativeExe, true);
if (Host == HostType.AppVeyor)
{
AbsolutePath zipFile = CliNativeExe.Parent / "hactoolnet_native.zip";
ZipFiles(zipFile, new[] { CliNativeExe.ToString() });
PushArtifact(zipFile);
}
}
finally
{
File.Delete(nativeProject);
File.Delete(NugetConfig);
}
});
Target Results => _ => _
.DependsOn(Test, Zip, Merge, Sign, Native, Publish)
.Executes(() =>
{
Console.WriteLine("SHA-1:");
using (SHA1 sha = SHA1.Create())
{
foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory))
{
using (var stream = new FileStream(filename, FileMode.Open))
{
string hash = BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "");
Console.WriteLine($"{hash} - {Path.GetFileName(filename)}");
}
}
}
});
Target Sign => _ => _
.DependsOn(Test, Zip, Merge)
.DependsOn(Test, Zip)
.OnlyWhenStatic(() => File.Exists(CertFileName))
.OnlyWhenStatic(() => !EnvironmentInfo.IsUnix)
.OnlyWhenStatic(() => EnvironmentInfo.IsWin)
.Executes(() =>
{
string pwd = ReadPassword();
if (pwd == string.Empty)
{
Console.WriteLine("Skipping sign task");
Logger.Normal("Skipping sign task");
return;
}
SignAndReZip(pwd);
});
Target Native => _ => _
.DependsOn(SetVersion)
.After(Compile)
.Executes(BuildNative);
Target AppVeyorBuild => _ => _
.DependsOn(Zip, Native, Publish)
.Unlisted()
.Executes(PrintResults);
Target Standard => _ => _
.DependsOn(Test, Zip)
.Executes(PrintResults);
Target Full => _ => _
.DependsOn(Sign, Native)
.Executes(PrintResults);
public void PrintResults()
{
Logger.Normal("SHA-1:");
using (SHA1 sha = SHA1.Create())
{
foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory))
{
using (var stream = new FileStream(filename, FileMode.Open))
{
string hash = BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "");
Logger.Normal($"{hash} - {Path.GetFileName(filename)}");
}
}
}
}
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
public void BuildNative()
{
AbsolutePath nativeProject = HactoolnetProject.Path.Parent / "hactoolnet_native.csproj";
try
{
File.Copy(HactoolnetProject, nativeProject, true);
DotNet("new nuget --force");
XDocument doc = XDocument.Load(NugetConfig);
doc.Element("configuration").Element("packageSources").Add(new XElement("add",
new XAttribute("key", "myget"), new XAttribute("value", DotNetFeedSource)));
doc.Save(NugetConfig);
DotNet($"add {nativeProject} package Microsoft.DotNet.ILCompiler --version 1.0.0-alpha-*");
DotNetPublishSettings publishSettings = new DotNetPublishSettings()
.SetConfiguration(Configuration)
.SetProject(nativeProject)
.SetFramework("netcoreapp3.0")
.SetRuntime(NativeRuntime)
.SetOutput(CliNativeDir)
.SetProperties(VersionProps);
if (!Untrimmed)
{
publishSettings = publishSettings
.AddProperty("RootAllApplicationAssemblies", false)
.AddProperty("IlcGenerateCompleteTypeMetadata", false)
.AddProperty("IlcGenerateStackTraceData", false)
.AddProperty("IlcFoldIdenticalMethodBodies", true)
;
}
DotNetPublish(publishSettings);
if (EnvironmentInfo.IsUnix && !Untrimmed)
{
ProcessTasks.StartProcess("strip", CliNativeExe).AssertZeroExitCode();
}
ZipFile(CliNativeZip, CliNativeExe, $"hactoolnet{NativeProgramExtension}");
Logger.Normal($"Created {CliNativeZip}");
if (Host == HostType.AppVeyor)
{
PushArtifact(CliNativeZip);
}
}
finally
{
File.Delete(nativeProject);
File.Delete(NugetConfig);
}
}
public static void ZipFiles(string outFile, IEnumerable<string> files)
{
using (var s = new ZipOutputStream(File.Create(outFile)))
@ -425,6 +393,24 @@ namespace LibHacBuild
}
}
public static void ZipFile(string outFile, string file, string nameInsideZip)
{
using (var s = new ZipOutputStream(File.Create(outFile)))
{
s.SetLevel(9);
var entry = new ZipEntry(nameInsideZip);
entry.DateTime = DateTime.UnixEpoch;
using (FileStream fs = File.OpenRead(file))
{
entry.Size = fs.Length;
s.PutNextEntry(entry);
fs.CopyTo(s);
}
}
}
public static void ZipDirectory(string outFile, string directory)
{
using (var s = new ZipOutputStream(File.Create(outFile)))
@ -503,7 +489,7 @@ namespace LibHacBuild
{
if (!File.Exists(path))
{
Console.WriteLine($"Unable to add artifact {path}");
Logger.Warn($"Unable to add artifact {path}");
}
var psi = new ProcessStartInfo
@ -524,7 +510,7 @@ namespace LibHacBuild
proc.WaitForExit();
Console.WriteLine($"Added AppVeyor artifact {path}");
Logger.Normal($"Added AppVeyor artifact {path}");
}
public static void SetAppVeyorVersion(string version)
@ -572,35 +558,41 @@ namespace LibHacBuild
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);
AbsolutePath nativeZipDir = TempDirectory / ("sign_" + Path.GetFileName(CliNativeZip));
bool signNative = FileExists(CliNativeExe);
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);
if (signNative)
{
UnzipFiles(CliNativeZip, nativeZipDir);
toSign.Add(nativeZipDir / "hactoolnet.exe");
}
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" / "netcoreapp3.0" / "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);
if (signNative)
{
ZipDirectory(SignedArtifactsDirectory / Path.GetFileName(CliNativeZip), nativeZipDir);
}
File.Copy(snupkgFile, SignedArtifactsDirectory / Path.GetFileName(snupkgFile));
SignNupkg(SignedArtifactsDirectory / Path.GetFileName(nupkgFile), password);
@ -614,7 +606,6 @@ namespace LibHacBuild
finally
{
Directory.Delete(nupkgDir, true);
Directory.Delete(netFxDir, true);
Directory.Delete(coreFxDir, true);
}
}

View file

@ -10,8 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageDownload Include="GitVersion.Tool" Version="[5.1.2]" />
<PackageReference Include="ILRepack.Lib" Version="2.0.18" NoWarn="NU1701" />
<PackageDownload Include="GitVersion.Tool" Version="[5.0.1]" />
<PackageReference Include="NuGet.CommandLine" Version="5.3.1" />
<PackageReference Include="Nuke.Common" Version="0.23.4" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />

View file

@ -1,26 +1,16 @@
using System;
using System.Runtime.CompilerServices;
#if NETCOREAPP
using System.Runtime.InteropServices;
#endif
namespace LibHac.Common
{
public static class SpanHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if NETCOREAPP
public static Span<T> CreateSpan<T>(ref T reference, int length)
{
return MemoryMarshal.CreateSpan(ref reference, length);
}
#else
public static unsafe Span<T> CreateSpan<T>(ref T reference, int length)
{
return new Span<T>(Unsafe.AsPointer(ref reference), length);
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(ref T reference) where T : unmanaged
@ -43,17 +33,10 @@ namespace LibHac.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if NETCOREAPP
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length)
{
return MemoryMarshal.CreateReadOnlySpan(ref reference, length);
}
#else
public static unsafe ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length)
{
return new ReadOnlySpan<T>(Unsafe.AsPointer(ref reference), length);
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<T> AsReadOnlySpan<T>(ref T reference) where T : unmanaged

View file

@ -80,11 +80,7 @@ namespace LibHac.Common
public static string Utf8ToString(ReadOnlySpan<byte> value)
{
#if STRING_SPAN
return Encoding.UTF8.GetString(value);
#else
return Encoding.UTF8.GetString(value.ToArray());
#endif
}
public static string Utf8ZToString(ReadOnlySpan<byte> value)

View file

@ -1,13 +0,0 @@
using System;
namespace LibHac.Compatibility
{
/// <summary>
/// Contains variables describing runtime environment info
/// needed for compatibility code.
/// </summary>
internal static class Env
{
public static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null;
}
}

View file

@ -1,367 +0,0 @@

#if !HAS_FILE_SYSTEM_NAME
// This code was introduced in .NET Core 2.1
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace LibHac.Compatibility
{
/// <summary>
/// Provides methods for matching file system names.
/// </summary>
internal static class FileSystemName
{
private static readonly char[] WildcardChars =
{
'\"', '<', '>', '*', '?'
};
private static readonly char[] SimpleWildcardChars =
{
'*', '?'
};
/// <summary>
/// Return true if the given expression matches the given name. '*' and '?' are wildcards, '\' escapes.
/// </summary>
public static bool MatchesSimpleExpression(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true)
{
return MatchPattern(expression, name, ignoreCase, useExtendedWildcards: false);
}
// Matching routine description
// ============================
// (copied from native impl)
//
// This routine compares a Dbcs name and an expression and tells the caller
// if the name is in the language defined by the expression. The input name
// cannot contain wildcards, while the expression may contain wildcards.
//
// Expression wild cards are evaluated as shown in the nondeterministic
// finite automatons below. Note that ~* and ~? are DOS_STAR and DOS_QM.
//
// ~* is DOS_STAR, ~? is DOS_QM, and ~. is DOS_DOT
//
// S
// <-----<
// X | | e Y
// X * Y == (0)----->-(1)->-----(2)-----(3)
//
// S-.
// <-----<
// X | | e Y
// X ~* Y == (0)----->-(1)->-----(2)-----(3)
//
// X S S Y
// X ?? Y == (0)---(1)---(2)---(3)---(4)
//
// X . . Y
// X ~.~. Y == (0)---(1)----(2)------(3)---(4)
// | |________|
// | ^ |
// |_______________|
// ^EOF or .^
//
// X S-. S-. Y
// X ~?~? Y == (0)---(1)-----(2)-----(3)---(4)
// | |________|
// | ^ |
// |_______________|
// ^EOF or .^
//
// where S is any single character
// S-. is any single character except the final .
// e is a null character transition
// EOF is the end of the name string
//
// In words:
//
// * matches 0 or more characters.
// ? matches exactly 1 character.
// DOS_STAR matches 0 or more characters until encountering and matching
// the final . in the name.
// DOS_QM matches any single character, or upon encountering a period or
// end of name string, advances the expression to the end of the
// set of contiguous DOS_QMs.
// DOS_DOT matches either a . or zero characters beyond name string.
private static bool MatchPattern(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase, bool useExtendedWildcards)
{
// The idea behind the algorithm is pretty simple. We keep track of all possible locations
// in the regular expression that are matching the name. When the name has been exhausted,
// if one of the locations in the expression is also just exhausted, the name is in the
// language defined by the regular expression.
if (expression.Length == 0 || name.Length == 0)
return false;
if (expression[0] == '*')
{
// Just * matches everything
if (expression.Length == 1)
return true;
ReadOnlySpan<char> expressionEnd = expression.Slice(1);
if (expressionEnd.IndexOfAny(useExtendedWildcards ? WildcardChars : SimpleWildcardChars) == -1)
{
// Handle the special case of a single starting *, which essentially means "ends with"
// If the name doesn't have enough characters to match the remaining expression, it can't be a match.
if (name.Length < expressionEnd.Length)
return false;
// See if we end with the expression
return name.EndsWith(expressionEnd, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
}
int nameOffset = 0;
int expressionOffset;
int priorMatch;
int currentMatch;
int priorMatchCount;
int matchCount = 1;
char nameChar = '\0';
char expressionChar;
// ReSharper disable once RedundantAssignment
Span<int> temp = stackalloc int[0];
Span<int> currentMatches = stackalloc int[16];
Span<int> priorMatches = stackalloc int[16];
priorMatches[0] = 0;
int maxState = expression.Length * 2;
int currentState;
bool nameFinished = false;
// Walk through the name string, picking off characters. We go one
// character beyond the end because some wild cards are able to match
// zero characters beyond the end of the string.
//
// With each new name character we determine a new set of states that
// match the name so far. We use two arrays that we swap back and forth
// for this purpose. One array lists the possible expression states for
// all name characters up to but not including the current one, and other
// array is used to build up the list of states considering the current
// name character as well. The arrays are then switched and the process
// repeated.
//
// There is not a one-to-one correspondence between state number and
// offset into the expression. State numbering is not continuous.
// This allows a simple conversion between state number and expression
// offset. Each character in the expression can represent one or two
// states. * and DOS_STAR generate two states: expressionOffset * 2 and
// expressionOffset * 2 + 1. All other expression characters can produce
// only a single state. Thus expressionOffset = currentState / 2.
while (!nameFinished)
{
if (nameOffset < name.Length)
{
// Not at the end of the name. Grab the current character and move the offset forward.
nameChar = name[nameOffset++];
}
else
{
// At the end of the name. If the expression is exhausted, exit.
if (priorMatches[matchCount - 1] == maxState)
break;
nameFinished = true;
}
// Now, for each of the previous stored expression matches, see what
// we can do with this name character.
priorMatch = 0;
currentMatch = 0;
priorMatchCount = 0;
while (priorMatch < matchCount)
{
// We have to carry on our expression analysis as far as possible for each
// character of name, so we loop here until the expression stops matching.
expressionOffset = (priorMatches[priorMatch++] + 1) / 2;
while (expressionOffset < expression.Length)
{
currentState = expressionOffset * 2;
expressionChar = expression[expressionOffset];
// We may be about to exhaust the local space for matches,
// so we have to reallocate if this is the case.
if (currentMatch >= currentMatches.Length - 2)
{
int newSize = currentMatches.Length * 2;
temp = new int[newSize];
currentMatches.CopyTo(temp);
currentMatches = temp;
temp = new int[newSize];
priorMatches.CopyTo(temp);
priorMatches = temp;
}
if (expressionChar == '*')
{
// '*' matches any character zero or more times.
// ReSharper disable once RedundantJumpStatement
goto MatchZeroOrMore;
}
else if (useExtendedWildcards && expressionChar == '<')
{
// '<' (DOS_STAR) matches any character except '.' zero or more times.
// If we are at a period, determine if we are allowed to
// consume it, i.e. make sure it is not the last one.
bool notLastPeriod = false;
if (!nameFinished && nameChar == '.')
{
for (int offset = nameOffset; offset < name.Length; offset++)
{
if (name[offset] == '.')
{
notLastPeriod = true;
break;
}
}
}
if (nameFinished || nameChar != '.' || notLastPeriod)
{
// ReSharper disable once RedundantJumpStatement
goto MatchZeroOrMore;
}
else
{
// We are at a period. We can only match zero
// characters (i.e. the epsilon transition).
goto MatchZero;
}
}
else
{
// The remaining expression characters all match by consuming a character,
// so we need to force the expression and state forward.
currentState += 2;
if (useExtendedWildcards && expressionChar == '>')
{
// '>' (DOS_QM) is the most complicated. If the name is finished,
// we can match zero characters. If this name is a '.', we
// don't match, but look at the next expression. Otherwise
// we match a single character.
if (nameFinished || nameChar == '.')
goto NextExpressionCharacter;
currentMatches[currentMatch++] = currentState;
goto ExpressionFinished;
}
else if (useExtendedWildcards && expressionChar == '"')
{
// A '"' (DOS_DOT) can match either a period, or zero characters
// beyond the end of name.
if (nameFinished)
{
goto NextExpressionCharacter;
}
else if (nameChar == '.')
{
currentMatches[currentMatch++] = currentState;
}
goto ExpressionFinished;
}
else
{
if (expressionChar == '\\')
{
// Escape character, try to move the expression forward again and match literally.
if (++expressionOffset == expression.Length)
{
currentMatches[currentMatch++] = maxState;
goto ExpressionFinished;
}
currentState = expressionOffset * 2 + 2;
expressionChar = expression[expressionOffset];
}
// From this point on a name character is required to even
// continue, let alone make a match.
if (nameFinished) goto ExpressionFinished;
if (expressionChar == '?')
{
// If this expression was a '?' we can match it once.
currentMatches[currentMatch++] = currentState;
}
else if (ignoreCase
? char.ToUpperInvariant(expressionChar) == char.ToUpperInvariant(nameChar)
: expressionChar == nameChar)
{
// Matched a non-wildcard character
currentMatches[currentMatch++] = currentState;
}
goto ExpressionFinished;
}
}
MatchZeroOrMore:
currentMatches[currentMatch++] = currentState;
MatchZero:
currentMatches[currentMatch++] = currentState + 1;
NextExpressionCharacter:
if (++expressionOffset == expression.Length)
currentMatches[currentMatch++] = maxState;
} // while (expressionOffset < expression.Length)
ExpressionFinished:
// Prevent duplication in the destination array.
//
// Each of the arrays is monotonically increasing and non-duplicating, thus we skip
// over any source element in the source array if we just added the same element to
// the destination array. This guarantees non-duplication in the destination array.
if ((priorMatch < matchCount) && (priorMatchCount < currentMatch))
{
while (priorMatchCount < currentMatch)
{
int previousLength = priorMatches.Length;
while ((priorMatch < previousLength) && (priorMatches[priorMatch] < currentMatches[priorMatchCount]))
{
priorMatch++;
}
priorMatchCount++;
}
}
} // while (sourceCount < matchesCount)
// If we found no matches in the just finished iteration it's time to bail.
if (currentMatch == 0)
return false;
// Swap the meaning the two arrays
temp = priorMatches;
priorMatches = currentMatches;
currentMatches = temp;
matchCount = currentMatch;
} // while (!nameFinished)
currentState = priorMatches[matchCount - 1];
return currentState == maxState;
}
}
}
#endif

View file

@ -1,69 +0,0 @@
#if NETFRAMEWORK
using System;
using System.Numerics;
using LibHac.Crypto;
namespace LibHac.Compatibility
{
internal static class Rsa
{
public static bool Rsa2048PssVerifyMono(byte[] data, byte[] signature, byte[] modulus)
{
const int rsaLen = 0x100;
const int digestLen = 0x20;
const int hashOffset = rsaLen - digestLen - 1;
const int saltOffset = hashOffset - digestLen;
const int padEnd = saltOffset - 1;
var message = new byte[rsaLen];
BigInteger decInt = BigInteger.ModPow(CryptoOld.GetBigInteger(signature), new BigInteger(65537), CryptoOld.GetBigInteger(modulus));
byte[] decBytes = decInt.ToByteArray();
if (decBytes[0] != 0xBC) return false;
Array.Reverse(decBytes);
Array.Copy(decBytes, 0, message, message.Length - decBytes.Length, decBytes.Length);
var hashBuf = new byte[0x24];
Array.Copy(message, hashOffset, hashBuf, 0, digestLen);
ref byte seed = ref hashBuf[0x23];
Span<byte> digestBuffer = stackalloc byte[Sha256.DigestSize];
for (int i = 0; i < hashOffset; i += 0x20)
{
Sha256.GenerateSha256Hash(hashBuf, digestBuffer);
Util.XorArrays(message.AsSpan(i, digestLen), digestBuffer);
seed++;
}
message[0] &= 0x7F;
if (!Util.IsEmpty(message.AsSpan(0, padEnd)) || message[padEnd] != 1)
{
return false;
}
Span<byte> prefix = stackalloc byte[8];
Span<byte> digest = stackalloc byte[Sha256.DigestSize];
Sha256.GenerateSha256Hash(data, digest);
IHash sha2 = Sha256.CreateSha256Generator();
sha2.Initialize();
sha2.Update(prefix);
sha2.Update(digest);
sha2.Update(message.AsSpan(saltOffset, digestLen));
sha2.GetHash(digest);
return Util.SpansEqual(hashBuf.AsSpan(0, 0x20), digest);
}
}
}
#endif

View file

@ -152,20 +152,7 @@ namespace LibHac
public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus)
{
#if NETFRAMEWORK
if (!Compatibility.Env.IsMono)
{
return Compatibility.Rsa.Rsa2048PssVerifyMono(data, signature, modulus)
? Validity.Valid
: Validity.Invalid;
}
#endif
#if USE_RSA_CNG
using (RSA rsa = new RSACng())
#else
using (RSA rsa = RSA.Create())
#endif
{
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
@ -177,12 +164,8 @@ namespace LibHac
public static byte[] DecryptTitleKey(byte[] titleKeyblock, RSAParameters rsaParams)
{
// todo: Does this work on Mono?
#if USE_RSA_CNG
RSA rsa = new RSACng();
#else
RSA rsa = RSA.Create();
#endif
rsa.ImportParameters(rsaParams);
return rsa.Decrypt(titleKeyblock, RSAEncryptionPadding.OaepSHA256);
}

View file

@ -90,18 +90,7 @@ namespace LibHac.FsService
lock (_locker)
{
int newCapacity = Math.Max(capacity, ExternalKeys.Count);
#if NETCOREAPP
ExternalKeys.TrimExcess(newCapacity);
#else
var trimmedDict = new Dictionary<RightsId, AccessKey>(newCapacity);
foreach (KeyValuePair<RightsId, AccessKey> kvp in ExternalKeys)
{
trimmedDict.Add(kvp.Key, kvp.Value);
}
ExternalKeys = trimmedDict;
#endif
}
}
}

View file

@ -1,9 +1,7 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
#if CROSS_PLATFORM
using System.Runtime.InteropServices;
#endif
namespace LibHac.FsSystem
{
@ -99,7 +97,6 @@ namespace LibHac.FsSystem
private bool IsConcatenationFile(DirectoryEntry entry)
{
#if CROSS_PLATFORM
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);
@ -111,9 +108,6 @@ namespace LibHac.FsSystem
return ParentFileSystem.IsConcatenationFile(fullPath);
}
#else
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);
#endif
}
}
}

View file

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using LibHac.Fs;
#if CROSS_PLATFORM
using System.Runtime.InteropServices;
#endif
using LibHac.Fs;
namespace LibHac.FsSystem
{
@ -51,7 +49,6 @@ namespace LibHac.FsSystem
// but writing still won't work properly on those platforms
internal bool IsConcatenationFile(string path)
{
#if CROSS_PLATFORM
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Result rc = BaseFileSystem.GetFileAttributes(path, out NxFileAttributes attributes);
@ -63,15 +60,8 @@ namespace LibHac.FsSystem
{
return IsConcatenationFileHeuristic(path);
}
#else
Result rc = BaseFileSystem.GetFileAttributes(path, out NxFileAttributes attributes);
if (rc.IsFailure()) return false;
return HasConcatenationFileAttribute(attributes);
#endif
}
#if CROSS_PLATFORM
private bool IsConcatenationFileHeuristic(string path)
{
// Check if the path is a directory
@ -92,7 +82,6 @@ namespace LibHac.FsSystem
// Should be enough checks to avoid most false positives. Maybe
return true;
}
#endif
internal static bool HasConcatenationFileAttribute(NxFileAttributes attributes)
{

View file

@ -1,13 +1,10 @@
using System;
using System.Diagnostics;
using System.IO.Enumeration;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
#if HAS_FILE_SYSTEM_NAME
using System.IO.Enumeration;
#endif
namespace LibHac.FsSystem
{
public static class PathTools
@ -455,13 +452,8 @@ namespace LibHac.FsSystem
public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase)
{
#if HAS_FILE_SYSTEM_NAME
return FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(),
name.AsSpan(), ignoreCase);
#else
return Compatibility.FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(),
name.AsSpan(), ignoreCase);
#endif
}
private static bool IsValidMountNameChar(char c)

View file

@ -2,10 +2,6 @@
using System.IO;
using LibHac.Fs;
#if !STREAM_SPAN
using System.Buffers;
#endif
namespace LibHac.FsSystem
{
/// <summary>
@ -32,7 +28,6 @@ namespace LibHac.FsSystem
Result rc = ValidateReadParams(out long toRead, offset, destination.Length, Mode);
if (rc.IsFailure()) return rc;
#if STREAM_SPAN
lock (Locker)
{
if (BaseStream.Position != offset)
@ -43,26 +38,6 @@ namespace LibHac.FsSystem
bytesRead = BaseStream.Read(destination.Slice(0, (int)toRead));
return Result.Success;
}
#else
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)toRead);
try
{
lock (Locker)
{
if (BaseStream.Position != offset)
{
BaseStream.Position = offset;
}
bytesRead = BaseStream.Read(buffer, 0, (int)toRead);
}
new Span<byte>(buffer, 0, (int)bytesRead).CopyTo(destination);
return Result.Success;
}
finally { ArrayPool<byte>.Shared.Return(buffer); }
#endif
}
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source, WriteOption options)
@ -70,26 +45,11 @@ namespace LibHac.FsSystem
Result rc = ValidateWriteParams(offset, source.Length, Mode, out _);
if (rc.IsFailure()) return rc;
#if STREAM_SPAN
lock (Locker)
{
BaseStream.Position = offset;
BaseStream.Write(source);
}
#else
byte[] buffer = ArrayPool<byte>.Shared.Rent(source.Length);
try
{
source.CopyTo(buffer);
lock (Locker)
{
BaseStream.Position = offset;
BaseStream.Write(buffer, 0, source.Length);
}
}
finally { ArrayPool<byte>.Shared.Return(buffer); }
#endif
if (options.HasFlag(WriteOption.Flush))
{

View file

@ -2,10 +2,6 @@
using System.IO;
using LibHac.Fs;
#if !STREAM_SPAN
using System.Buffers;
#endif
namespace LibHac.FsSystem
{
public class StreamStorage : StorageBase
@ -26,7 +22,6 @@ namespace LibHac.FsSystem
protected override Result ReadImpl(long offset, Span<byte> destination)
{
#if STREAM_SPAN
lock (Locker)
{
if (BaseStream.Position != offset)
@ -36,31 +31,12 @@ namespace LibHac.FsSystem
BaseStream.Read(destination);
}
#else
byte[] buffer = ArrayPool<byte>.Shared.Rent(destination.Length);
try
{
lock (Locker)
{
if (BaseStream.Position != offset)
{
BaseStream.Position = offset;
}
BaseStream.Read(buffer, 0, destination.Length);
}
buffer.AsSpan(0, destination.Length).CopyTo(destination);
}
finally { ArrayPool<byte>.Shared.Return(buffer); }
#endif
return Result.Success;
}
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
{
#if STREAM_SPAN
lock (Locker)
{
if (BaseStream.Position != offset)
@ -70,24 +46,6 @@ namespace LibHac.FsSystem
BaseStream.Write(source);
}
#else
byte[] buffer = ArrayPool<byte>.Shared.Rent(source.Length);
try
{
source.CopyTo(buffer);
lock (Locker)
{
if (BaseStream.Position != offset)
{
BaseStream.Position = offset;
}
BaseStream.Write(buffer, 0, source.Length);
}
}
finally { ArrayPool<byte>.Shared.Return(buffer); }
#endif
return Result.Success;
}

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netcoreapp3.0;netstandard2.0;net46</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0;netstandard2.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
@ -26,31 +26,15 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net46' ">
<DefineConstants>$(DefineConstants);USE_RSA_CNG</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' ">
<DefineConstants>$(DefineConstants);STREAM_SPAN;STRING_SPAN;HAS_FILE_SYSTEM_NAME;CROSS_PLATFORM;HAS_INTRINSICS</DefineConstants>
<DefineConstants>$(DefineConstants);HAS_INTRINSICS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);CROSS_PLATFORM</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0-preview.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="All"/>
</ItemGroup>

View file

@ -109,11 +109,7 @@ namespace LibHac
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetUtf8String(ReadOnlySpan<byte> value)
{
#if STRING_SPAN
return Encoding.UTF8.GetString(value);
#else
return Encoding.UTF8.GetString(value.ToArray());
#endif
}
public static string GetUtf8StringNullTerminated(ReadOnlySpan<byte> value)
@ -123,11 +119,7 @@ namespace LibHac
value = value.Slice(0, i);
#if STRING_SPAN
return Encoding.UTF8.GetString(value);
#else
return Encoding.UTF8.GetString(value.ToArray());
#endif
}
public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan<byte>)array).IsEmpty();

View file

@ -1,17 +1,14 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using LibHac;
using LibHac.Crypto;
using LibHac.Fs;
using LibHac.FsSystem;
#if NETCOREAPP
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
#endif
namespace hactoolnet
{
internal static class ProcessBench
@ -234,7 +231,6 @@ namespace hactoolnet
// ReSharper disable once UnusedParameter.Local
private static void RegisterAesSingleBlockBenchmarks(MultiBenchmark bench)
{
#if NETCOREAPP
var input = new byte[SingleBlockCipherBenchSize];
var output = new byte[SingleBlockCipherBenchSize];
@ -284,7 +280,6 @@ namespace hactoolnet
outBlock = ref Unsafe.Add(ref outBlock, Aes.BlockSize);
}
}
#endif
}
private static void RegisterShaBenchmarks(MultiBenchmark bench)

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using LibHac;
using LibHac.Fs;
@ -9,10 +10,6 @@ using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.FsSystem.Save;
#if NETCOREAPP
using System.Runtime.InteropServices;
#endif
namespace hactoolnet
{
internal static class ProcessSwitchFs
@ -310,10 +307,8 @@ namespace hactoolnet
private static void CheckForNcaFolders(Context ctx, SwitchFs switchFs)
{
#if NETCOREAPP
// Skip this until Linux gets FAT attribute support
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
#endif
IFileSystem fs = switchFs.ContentFs;

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.0;net46</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
@ -20,8 +20,4 @@
<ProjectReference Include="..\LibHac\LibHac.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0-preview.2" />
</ItemGroup>
</Project>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net46;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
@ -20,8 +20,4 @@
<EmbeddedResource Include="CryptoTests\TestVectors\*.rsp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0-preview.2" />
</ItemGroup>
</Project>